---
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
---

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

2026-09-08 · WatchFor Team · Security · security, web

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:

1. **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.
2. **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).

---

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