Content-Type and MIME Sniffing
Why Content-Type matters, how MIME sniffing works, X-Content-Type-Options: nosniff, and XSS-ish pitfalls.
- 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
- Store and serve with a safe type (often
application/octet-stream+Content-Disposition: attachmentfor untrusted files). - Never serve user HTML as
text/htmlon your main origin if avoidable — use a separate cookie-less domain. - Send
X-Content-Type-Options: nosniff. - 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.
Related
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
- CORS Explained for FrontendWhy the browser blocks cross-origin responses, what Access-Control-* headers mean, preflight, credentials, and how to fix real frontend errors.
- Browser Networking 101DNS, TCP/TLS, HTTP/1.1 vs H2/H3, connection reuse, and what frontend code can actually influence.
- Cross-Origin Embedder PolicyCOEP require-corp / credentialless: cross-origin isolation, SharedArrayBuffer, and embedding requirements.
- Cross-Origin Opener PolicyCOOP same-origin and same-origin-allow-popups: sever window.opener, process isolation, and OAuth popups.
- Feature Policy Legacy NotesFeature-Policy renamed to Permissions-Policy: migration notes, old header syntax, and what still matters.