X-Content-Type-Options

A user uploads a file to your site. You serve it back with Content-Type: text/plain. If the file happens to contain something that looks like HTML, some browsers will look at the bytes, decide "this is actually HTML," and render it. Any script or event handler inside runs with your origin's cookies.

That is the MIME-sniffing problem. The fix is a one-line header that tells the browser to trust the Content-Type you sent and stop second-guessing.

The problem

Historical browsers treated Content-Type as a hint, not a contract. The reasoning was pragmatic: plenty of early web servers shipped everything as text/plain or even no type at all. Sniffing kept those sites working. It also created a persistent class of bugs where user-uploaded content escaped the type the server declared.

Without nosniffscript executes
  1. 01user uploads comment.txt containing <img src=x onerror=steal()>
  2. 02server serves it as Content-Type: text/plain
  3. 03browser peeks at the bytes, guesses "HTML"
  4. 04inline script executes as same-origin
With nosniffbytes stay as text
  1. 01user uploads comment.txt containing <img src=x onerror=steal()>
  2. 02server serves it as text/plain AND sends X-Content-Type-Options: nosniff
  3. 03browser trusts the Content-Type, renders as text
  4. 04bytes display literally, no code runs

The attack class is called "MIME confusion." User uploads anywhere (avatars, attachments, issue comments) can trigger it. The patch is the shortest security header in the catalogue.

The fix

X-Content-Type-Options: nosniff

One name, one value, one behaviour. Introduced by Microsoft for IE 8 in 2008, then adopted by every other browser. Formally specified today in the WHATWG Fetch standard §3.6. Any value other than nosniff is ignored.

What it changes

What nosniff changesby resource destination
destination
script
must be
a JavaScript MIME type
(e.g. served as text/html or text/plain)
if the MIME is wrong
request is blocked
destination
style
must be
text/css
(e.g. served as text/html or application/json)
if the MIME is wrong
request is blocked
destination
anything else
must be
correct Content-Type
(e.g. content and declared type disagree)
if the MIME is wrong
sniffing disabled, type honoured as declared

For script and style destinations, the browser goes further than disabling sniffing: it refuses the response outright if the MIME does not match. A stylesheet served as text/html will not load. A script served as text/plain will not execute. That is stricter than old-style sniffing would have allowed; the intent is to close the last remaining gap.

For everything else (HTML, images, JSON, files in downloads), the rule is simpler: trust the Content-Type the server sent, do not peek at the bytes.

Where to ship it

Send this header on every response your server serves. Not just HTML pages. Every JSON endpoint, every image, every downloadable file, every static asset. The misconfigurations this header catches are by definition unpredictable.

Most modern frameworks set it by default. Nginx, Apache, Caddy, and every CDN expose a one-line config to enable it globally. If you have a single place that could set one security header, make it this one.

Common mistakes

header missing entirely

Every byte served by your origin is a potential MIME-confusion vector. Ship nosniff site-wide.

value other than "nosniff"

X-Content-Type-Options: yes or nosniff; mode=block or similar extensions are silently ignored. The only valid value is the exact string nosniff.

nosniff set but Content-Type wrong

nosniff makes the browser trust your Content-Type. If your server sends text/plain for actual JavaScript, nosniff will refuse to load it. Fix both.

only on HTML responses, not on static assets

A CDN serving JavaScript from /static/ also needs the header. Set it at the edge or in every origin response.

serving user uploads from the app origin

Even with nosniff, user content served from the same origin as the app is risky. Prefer a separate uploads subdomain or a CDN with Content-Disposition: attachment and restrictive CSP.


Check whether your site ships nosniff on every response: scan your domain.