ESC

Type to search the knowledge base.

Content-Type and MIME Sniffing

Why Content-Type matters, how MIME sniffing works, X-Content-Type-Options: nosniff, and XSS-ish pitfalls.

intermediate3 min read
  • browser
  • content-type
  • mime
  • security
  • http

Browsers decide how to interpret bytes using Content-Type and, historically, MIME sniffing (guessing from content). Wrong types break fonts/modules; sniffing without guardrails has been an XSS vector when user-uploaded content is served as the wrong kind of document.

Docs: MDN Content-Type, X-Content-Type-Options, MIME sniffing standard.

Declare what you mean

Content-Type: text/html; charset=utf-8
Content-Type: application/javascript; charset=utf-8
Content-Type: application/json
Content-Type: image/png
Content-Type: font/woff2

ES modules and classic scripts are picky: wrong MIME for type="module" scripts can fail to execute. JSON APIs should be application/json, not text/html.

Sniffing in one sentence

If the type is missing or generic (application/octet-stream, some text/plain cases), browsers may inspect bytes and treat them as HTML or media. That “helpfulness” is dangerous when an attacker uploads evil.html served as something the browser sniffs into executable context.

The fix: nosniff

X-Content-Type-Options: nosniff

This tells the browser: trust the declared type; don’t sniff into a different script/style context. Pair with correct Content-Type on all responses, especially user-generated downloads and CDNs.

Modern browsers are stricter than a decade ago, but nosniff remains standard hardening (and often a security checklist item).

Frontend-visible failures

Symptom Likely MIME issue
Font doesn’t apply Missing CORS + wrong font type, or blocked
Module script fails Served as text/html (SPA fallback!)
CSS ignored Stylesheet MIME not CSS (nosniff blocks wrong types)
Download instead of preview Attachment disposition + type

SPA fallback trap: CDN routes *.js miss to index.html with text/html → blank app. Fix routing so hashed assets 404 or serve real JS.

Charset

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

Mojibake and injection edge cases show up when charset is wrong or inconsistent. Prefer UTF-8 everywhere.

User uploads checklist

  1. Store and serve with a safe type (often application/octet-stream + Content-Disposition: attachment for untrusted files).
  2. Never serve user HTML as text/html on your main origin if avoidable — use a separate cookie-less domain.
  3. Send X-Content-Type-Options: nosniff.
  4. Validate magic bytes server-side; don’t trust client extension alone.
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="report.bin"
X-Content-Type-Options: nosniff

Interview out-loud

“Content-Type tells the browser how to interpret bytes. Sniffing can reclassify content and historically enabled XSS from uploads. I set correct types, send X-Content-Type-Options: nosniff, and avoid serving untrusted HTML on the app origin.”

CDN and SPA routing config

Many “MIME type” production incidents are routing: the CDN serves index.html for missing /static/app.js with text/html. Module scripts then fail with cryptic CORS/MIME errors. Fix: immutable asset paths should 404, not fall back to the app shell. Assert this in deploy smoke tests.

Further depth

Teams often under-invest in this topic until an incident or CWV regression. Schedule a one-hour drill: reproduce the failure mode in DevTools, list the top three mitigations for your stack, and file tickets with owners. Revisit after the next major feature that touches networking, rendering, auth, or third parties — those are the moments regressions land. Keep primary documentation links in the runbook so on-call is not searching chat history at 2am.

Concrete artifacts to leave behind: a short architecture note, a CI assertion or header snapshot, and a dashboard panel (lab or field) that would have caught the last bug. Teaching the rest of the team the mental model matters as much as the one-line fix.

Further reading

Related guides