---
title: MIME Sniffing and nosniff: when the browser guesses what your file is
description: Browsers don't always believe you about what a file is — they look inside and decide for themselves. That guess is how an uploaded 'image' occasionally ends up running as code. One header ends the guessing.
canonical: https://watchfor.io/blog/mime-sniffing-explained
---

[All posts](/blog) [Security](/blog/category/security) Sep 08, 2026 · 4 min read · WatchFor Team

# MIME Sniffing and nosniff: when the browser guesses what your file is

Browsers don't always believe you about what a file is — they look inside and decide for themselves. That guess is how an uploaded 'image' occasionally ends up running as code. One header ends the guessing.

When your server sends a file, it also sends a label saying what the file is: Content-Type: image/png , text/html , application/json . Reasonable system.

Except browsers have never entirely trusted that label. If the contents look like something else, some browsers will quietly ignore what you said and treat the file as what they think it is . That behaviour has a name — MIME sniffing — and it exists for a sad historical reason: for years, a large share of servers labelled files wrongly, and a browser that believed them showed broken pages. Guessing made the web work.

It also created a small, specific hole. X-Content-Type-Options: nosniff closes it.

## The problem, concretely

Say your app lets people upload a profile picture. You do the sensible things: check the extension, store it, serve it back with Content-Type: image/png .

Someone uploads a file that is a valid image and contains HTML with a script inside it. (This is easier than it sounds; several image formats happily carry arbitrary bytes.) A browser that sniffs looks at the beginning of the file, sees markup, decides "this is really HTML" and renders it — which means running the script.

The script now runs on your domain. It can read your cookies, read the page, and act as the logged-in user, because as far as the browser is concerned it came from you. It did come from you. You served it.

The same trick works with anything a user can put bytes into: uploaded documents, CSV exports, error messages echoed into a response, a JSON endpoint returning attacker-controlled text.

## Why nothing looks wrong

Your site works. The upload feature works. The image displays. There's no error, no warning, no failed request — the file behaves exactly as expected for every ordinary visitor, and differently only for the one who crafted it.

That's the recurring theme with security headers, and the reason a [website audit](/smart-website-audit) reports them even when everything is green: nothing here is broken. Something is merely permitted that you never meant to permit.

## The fix

X-Content-Type-Options: nosniff

That's the whole header. It has one value; there are no options. It tells the browser: the Content-Type I sent is the truth — if it says image/png , treat it as an image even if it looks like something else, and refuse rather than guess.

With it in place, the crafted upload is served as an image, the markup inside is never parsed, and the script never runs.

It does one more useful thing: it stops a stylesheet or script being loaded from a URL whose type doesn't match. A <script src> pointing at something served as text/plain is blocked instead of executed.

## What could break

This is the safest header on the list to add — with one honest caveat: if your server labels files wrongly today, nosniff stops the browser covering for you.

That's not the header breaking your site; it's the header revealing a mislabelling that was already there. Two things to check before you ship it:

- Are your JavaScript and CSS files served with the right types? text/javascript and text/css . If a misconfigured server sends text/plain , they'll stop loading — which you want to know about anyway.

- Are downloads labelled honestly? A PDF served as application/octet-stream still downloads fine. A PDF served as text/html will not display, and shouldn't.

For the overwhelming majority of sites, nothing changes at all except the sniffing.

## And while you're there

nosniff closes the guessing; it doesn't make user uploads safe on its own. The advice that pairs with it:

- Serve user-uploaded files from a different domain — not a path on your main site. Then even a file that does execute has no access to your cookies or your session.

- Never trust the filename or extension. Validate the actual contents server-side.

- Send Content-Disposition: attachment for anything that should be downloaded rather than displayed.

- Add a [Content Security Policy](/blog/content-security-policy-explained). It is the layer that catches what a Content-Type mistake lets through.

## Keeping it there

One header, one value, and it can disappear in a deploy without a single visible symptom. The free [HTTP header checker](/http-header-checker) shows what you send today; the [Smart Website Checker](/smart-website-audit) checks it alongside the other five. If you'd rather be told than remember to look, an [API monitor](/docs/monitors/api) can assert it on every check.

## The bottom line

In one line

What A header telling browsers to believe your Content-Type instead of guessing.

Why Stops a file being reinterpreted as HTML — and running as code on your domain.

Danger without it An uploaded "image" executes a script with your users' sessions.

Add X-Content-Type-Options: nosniff — the only value it has.

Watch for Files your server currently labels wrongly; they stop being rescued by the guess.

If you only add one header this week, this one is the cheapest: a single line, no tuning, no rollout plan, and it removes an entire category of "but it was just an image" incidents.

Related: [Content Security Policy explained](/blog/content-security-policy-explained), [Clickjacking explained](/blog/clickjacking-explained), [Mixed content warnings](/blog/mixed-content-warnings).

[#security](/blog/tag/security)[#web](/blog/tag/web)

## Monitor your website & APIs

Continuous HTTP/HTTPS checks with status-code, latency and content assertions — plus instant alerts when something breaks.

[Learn more](/http-monitoring)[Start free](/auth/sign-up)

Share this article

---

Canonical page: https://watchfor.io/blog/mime-sniffing-explained · Site guide: https://watchfor.io/llms.txt
