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: nosniffValues and what each does
| Value | Status | Description |
|---|---|---|
nosniff | ✅ Good | The 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-8The 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.pngthat 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. Withnosniffthe 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/plainorapplication/octet-stream(a bare file server, an Amazon S3 object uploaded without a type) is blocked the momentnosniffships. 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
- Audit your
Content-Typemappings: every script served with a JavaScript MIME type, every stylesheet astext/css, every HTML response astext/html; charset=UTF-8. Pay attention to file servers and object storage, where the type is whatever was set at upload. - Add
X-Content-Type-Options: nosniffto 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. - 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: nosniffThe 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
- Security headers overview
- What is Content Security Policy,
which restricts what correctly labeled content can do;
nosniffonly enforces the labels - Strict-Transport-Security, the transport-layer sibling in this section
- Security headers scanner to check your deployed headers
Sources
Cookie security
The Set-Cookie attributes that protect sessions, Secure, HttpOnly, SameSite, and the __Host- and __Secure- prefixes, explained simply.
Referrer-Policy
Referrer-Policy controls how much of the current URL leaks in the Referer header when users navigate away. Values, defaults, and the safe choice.