Skip to main content

MIME sniffing & X-Content-Type-Options

An old browser feature that sounds useful at first — but is a dangerous gateway for XSS and drive-by-downloads.

What is MIME sniffing?

When a browser loads a file from the server, the server announces what it is via the Content-Type header — text/html, image/png, application/javascript and so on. But what happens when the server doesn't send a Content-Type at all or sends an obviously wrong one (e.g. a .gif file with text/plain)?

Historically, this is where MIME sniffing came in: browsers peeked at the first few bytes of the file and tried to detect the type themselves. Looks like HTML? Then render it as HTML, no matter what the header says. That was pragmatic in the early web — many servers used to set Content-Types sloppily — but today it's a security hole.

The security problem

Imagine this: on your website, users can upload files — for example profile pictures. The attacker uploads a file that looks like a valid PNG but contains HTML with embedded JavaScript in its body. The server serves it with image/png — so far, harmless. A browser without sniffing would load the image, the script would never execute.

But: if the browser has sniffing enabled and discovers HTML markup in the body, it suddenly renders the file as HTML — the embedded JavaScript runs in the context of your domain and can access cookies, sessions and all data of logged-in users. A cozy file upload turns into a full-blown XSS attack.

The same problem exists in reverse: depending on browser sniffing logic, a harmless text file can suddenly be executed as a script, a PDF as HTML and so on. Another classic: old Adobe Flash-based sniffing bypasses.

The solution: nosniff

The header X-Content-Type-Options: nosniff tells the browser unambiguously:

"Trust the Content-Type header exclusively. Don't sniff. If I say something is image/png, then load it as an image — and nothing else."

With nosniff, the upload attack described above becomes ineffective: the browser sees Content-Type: image/png and treats the file strictly as an image. Even if HTML is embedded — no rendering, no script. Prerequisite: the server sends a sensible Content-Type. With a missing header, nosniff stays conservative and won't load the file as executable.

Values

The header only knows one meaningful value: nosniff. Everything else is ignored.

X-Content-Type-Options: nosniff

Configuration examples

nginx:

add_header X-Content-Type-Options "nosniff" always;

Apache:

Header always set X-Content-Type-Options "nosniff"

Express:

app.use(helmet.noSniff())

Cloudflare: in the dashboard under Rules → Transform Rules → Modify Response Header.

Prerequisite: correct Content-Types

nosniff only works if your server actually delivers the correct Content-Types. An image served as text/plain won't load anymore — nosniff blocks it. Poorly configured servers may suddenly run into functional issues after enabling nosniff.

Solution: before enabling, check that all common file types receive correct MIME types — on nginx this is handled via /etc/nginx/mime.types, which usually fits out of the box. On Apache, no intervention is typically needed.

Bottom line

X-Content-Type-Options: nosniff is one of the simplest security headers — no configuration options, no trade-off, just set it. Mandatory for every website with file uploads, user-generated content or API endpoints. In Webscan Radar you'll find this header in the list of the six checked security headers.

Check it yourself: HTTP-Header

HSTS, CSP, X-Frame-Options & Co. Enter a domain and see in seconds how your MIME-SNIFFING is doing.

Also in the glossary