A user uploads a profile picture. Your server checks the declared content type, sees image/png, stores the file, and serves it back with that same content type header whenever anyone views the profile. Everyone involved has been reasonable. The file is not actually a PNG.
Browsers do not always trust the content type header a server sends. For a class of ambiguous or missing responses, they inspect the file's actual bytes and decide for themselves what kind of content they are looking at, a behaviour called MIME sniffing. A file can be a valid image by every check your upload handler ran and simultaneously contain a complete, valid HTML document in bytes the image format ignores, which a browser's sniffing logic can find and render instead of the picture.
This post is about files that are more than one thing at once, and what stops a browser from choosing the wrong one. For whoever built the upload and serving path.
Why this is possible at all
Several image and document formats have enough structural slack that a well-formed file in one format can also contain a well-formed file in a completely different one, at the byte level, simultaneously. A GIF's header format and an HTML document's syntax do not conflict in the ways you might expect, and constructing a file valid as both, a polyglot, is a known and repeatable technique rather than an obscure curiosity.
Your upload validation checked that the file was a valid image, using whatever library or magic-byte check you configured, and it was correct: the file genuinely is a valid image. It is also, independently and simultaneously, valid as something else, and nothing in an image-format validator has any reason to notice that.
Where the danger actually lives
The content type header on the response, not the upload check. If your server serves the uploaded file back with a generic or missing content type, or with a content type a browser does not fully trust for that context, browser MIME sniffing can inspect the bytes and decide to render it as HTML rather than as the image format your validation confirmed it to be.
The origin the file is served from. A polyglot file rendered as HTML by a browser executes any script it contains with the privileges of whatever origin served it. If user-uploaded content is served from your main application's origin, that script runs with access to your application's cookies and, if the browser and your application are configured to allow it, your users' sessions. This is the entire consequence in one sentence: a file that passed image validation becomes stored cross-site scripting because of where and how it was served, not because the validation was wrong about what it checked.
The header that closes most of this
X-Content-Type-Options: nosniff, set on every response serving user-uploaded content, tells browsers that support it not to second-guess the declared content type at all. Combined with an accurate, explicitly set content type on the response, this removes the sniffing behaviour that the whole attack depends on.
This is a single response header, it has no functional downside for correctly typed content, and its absence on an endpoint serving user uploads is worth treating as a finding on its own, independent of anything else about your upload validation.
What else actually helps
Serve user-uploaded content from a separate origin, never your main application's domain. This is the control that matters most regardless of how good your content validation becomes, because it means that even a successful polyglot render executes in a context with no access to your application's session, cookies, or anything else of value. The isolation, not the validation, is what bounds the consequence.
Re-encode images rather than storing and serving the original bytes, where your product's requirements allow it. Decoding an uploaded image and writing out a fresh file in the same format discards whatever additional structure a polyglot relied on, because the new file contains only what the image decoder actually extracted, not the original byte sequence with its second, hidden interpretation intact.
Validate by parsing, not merely by inspecting a magic byte sequence at the start of the file. A check that reads the first few bytes and confirms they match an expected format signature is trivially satisfied by a polyglot, because the format signature is exactly the part constructed to be genuinely valid. A full parse that actually decodes the image data is far harder to satisfy with a file that is simultaneously something else.
Set a strict content security policy on any page that might render user content, which limits what an executed script can do even in the case where sniffing or misconfiguration allows something to render unexpectedly. This is a backstop, not a replacement for the controls above, and it is worth having regardless.
Check yours
Construct or obtain a known GIF/HTML polyglot test file, upload it through your product's normal upload path, and then fetch the stored file directly with a tool that shows you the actual response headers:
curl -sI https://your-app.example.com/uploads/the-file-you-uploaded
Look specifically for X-Content-Type-Options: nosniff and for what content type is actually declared. Then, separately, check what origin that URL is served from relative to your main application. If the answer to either question is missing header or same origin, that combination is your exposure, and it is worth confirming with the actual test file rather than reasoning about it in the abstract.
The concession
Serving all user content from a separate origin and re-encoding every upload is a real architectural commitment, particularly for products that need to preserve original file bytes exactly, for legal, archival, or fidelity reasons, and cannot re-encode. In those specific cases, the nosniff header plus a correctly set content type becomes the primary control rather than a backstop, and it needs to actually be present rather than assumed.
The implication
Validating that an uploaded file is a valid instance of the format you expect is necessary and does not establish that it is only that format, because the two properties are independent and a file can satisfy both a real format's structure and a second, unintended one at the same time.
Check the response headers on your own upload-serving endpoint today. The gap here is rarely in the upload check people spend the most time on; it is almost always in the response header nobody thought to set on the way back out.