CentralCSP
Security headers

X-Content-Type-Options

The nosniff header stops browsers from second-guessing Content-Type, closing MIME-sniffing attacks. What it does and how to use it.

Last update:

Browsers used to inspect the first bytes of a response and guess its real type whenever the declared Content-Type was missing or looked wrong, a behavior called MIME sniffing. X-Content-Type-Options: nosniff turns the guessing off: the browser trusts the type your server declares and treats the response as that type only.

The guessing was the attack surface: a file uploaded as a harmless image that really contains HTML and JavaScript could be sniffed into a page and executed in your site's origin, a stored XSS. This header closes that path.

This is the whole header. It only does its job when the Content-Type on each response is correct:

X-Content-Type-Options: nosniff

Values and what each does

ValueStatusDescription
nosniff✅ GoodThe only value. The browser trusts the declared Content-Type and blocks scripts and stylesheets whose type does not match.

nosniff, defined in the Fetch standard, has two effects.

First, a hard block for scripts and stylesheets. Any script-like load (a <script>, a worker, a shared worker, a service worker, an audio or paint worklet) must arrive with a JavaScript MIME type, and a stylesheet must arrive as exactly text/css. On a mismatch the browser blocks the response at the network level and it never executes; Chromium logs a console error saying the script was refused because "strict MIME type checking is enabled". Without the header, Chromium still executes scripts served as text/plain, text/html, or application/octet-stream (image, audio, video, and CSV types are blocked as scripts either way).

Second, no byte guessing anywhere else. When a response carries a Content-Type, the declared type is final. When it carries none, the browser may still classify the body as text or binary, but it will never sniff it toward HTML, XML, or PDF.

Chrome also ships Opaque Response Blocking (ORB), which blocks cross-origin no-cors reads of responses that look like HTML, JSON, or XML for every site, whatever headers it sends. nosniff makes that blocking deterministic: a blocklisted, text/plain, or missing type fails closed instead of depending on confirmation sniffing. ORB does nothing for same-origin loads, so the header remains the only thing enforcing MIME correctness for your own scripts and stylesheets.

The Content-Type side

The header enforces the label, so the label has to be right on every resource: scripts served with a JavaScript MIME type (text/javascript), stylesheets as text/css, HTML as text/html. On HTML responses, declare the character encoding too:

Content-Type: text/html; charset=UTF-8

The OWASP HTTP headers cheat sheet puts it plainly: "the charset attribute is necessary to prevent XSS in HTML pages". The classic version of that attack, smuggling script through UTF-7, is dead because browsers dropped the encoding, but omitting the charset is still exploitable. Research published by Sonar in 2024 showed that when a page omits its charset, Chrome and Firefox auto-detect ISO-2022-JP, and an attacker who controls part of the page can use that encoding's escape sequences to break out of escaping mid-document, a working XSS in 2024. Declare the charset on every HTML response.

What it protects against

  • Mislabeled uploads executing as HTML or script. An avatar.png that is really an HTML file, served with a missing or unknown type, could be sniffed into a page and run in your origin as stored XSS. With nosniff the browser takes the file at its declared type.
  • Misconfigured and type-stripping infrastructure. A server that omits Content-Type, or a proxy that strips it, leaves the browser to guess. The header removes the guess, so a configuration slip stays a broken resource instead of becoming executable content.
  • Mislabeled worker scripts. The hard block covers workers, shared workers, service workers, and worklets, so a resource pulled in as a worker must carry a JavaScript MIME type too.
  • Cross-origin data leaks. It strengthens the browser's cross-origin read blocking (ORB) against Spectre-class attacks by making the block deterministic instead of sniffing-based.

The header enforces the label and nothing more. A response correctly labeled text/html still renders, correctly labeled JavaScript still runs, so it does not replace Content Security Policy (CSP) or upload handling (validation, a separate origin for user content).

Risks without it

Without the header, every response needs a perfect Content-Type, because any response without one is open to reinterpretation. One user-content route that serves files with a missing or unknown type, one proxy that strips the header, and a sniffing engine can promote a harmless-looking file to HTML in your origin. Modern engines have narrowed sniffing (they only sniff toward HTML when the type is missing or unknown), so the practical exposure concentrates in misconfigured surfaces and legacy engines, which is exactly what defense in depth is for.

Risks and gotchas when using it

  • Wrongly typed scripts stop loading. A legitimate script served as text/plain or application/octet-stream (a bare file server, an Amazon S3 object uploaded without a type) is blocked the moment nosniff ships. Fix the MIME mappings before adding the header, not after.
  • Stylesheets must be exactly text/css. Anything else is blocked, the same way scripts require a JavaScript MIME type.
  • Header form only. There is no <meta> equivalent; it must be sent as a response header.
  • No violation reports. Blocked loads surface as console errors only. Unlike CSP, the header has no Reporting API integration, so nothing tells you a resource broke in the field; test before and after deploying.

How to set it up

  1. Audit your Content-Type mappings: every script served with a JavaScript MIME type, every stylesheet as text/css, every HTML response as text/html; charset=UTF-8. Pay attention to file servers and object storage, where the type is whatever was set at upload.
  2. Add X-Content-Type-Options: nosniff to every response, API responses included. Setting it once at the server or CDN level is easier than per route, and there is no value to tune.
  3. Check the deployed header, and the rest of your response headers, with the security headers scanner, and watch the browser console for refused scripts or stylesheets after the rollout.

Recommendation

Send X-Content-Type-Options: nosniff on every response:

X-Content-Type-Options: nosniff

The OWASP HTTP headers cheat sheet recommends sending it on all responses browsers may consume, and the OWASP REST security cheat sheet extends that to API responses. Pair it with a correct Content-Type on every resource and a charset on HTML (text/html; charset=UTF-8): the header enforces the label, so the label has to be right.

Browser support

Supported in every modern engine. Microsoft introduced the header in Internet Explorer in 2008; their demo was an HTML file served as text/plain that rendered as HTML in older Internet Explorer but as plain text in the version that shipped nosniff. Chrome, Firefox, and Safari all enforce it fully in current versions (some early Chrome builds did not enforce the stylesheet check). Firefox also applies nosniff to top-level page loads (see Mozilla's announcement): a navigation whose declared type does not match shows an error page, and a missing type renders as plain text or downloads.

FAQ

Do I need nosniff on API responses?

Yes. The OWASP REST security cheat sheet recommends X-Content-Type-Options: nosniff on every response, API responses included. It costs nothing to send site-wide, stops a JSON response from being sniffed toward HTML, and there is no value to tune, so set it once at the server or CDN level rather than per route.

Does nosniff break anything?

Only resources sent with the wrong Content-Type. A script served as text/plain, or a stylesheet that is not exactly text/css, gets blocked the moment the header ships. Fix your MIME mappings first, then add nosniff, so correctly labeled content keeps loading and only the previously mislabeled resources surface as errors.

Does nosniff stop XSS?

No. It closes one specific path: a mislabeled upload being sniffed and executed as HTML or script in your origin. Correctly labeled HTML still renders and correctly labeled JavaScript still runs, so you still need Content Security Policy and proper upload handling. Treat nosniff as defense in depth, not an XSS fix.

See also

Sources

On this page