How to Test File Upload Validation for MIME Types, Magic Bytes, and Rename-Only Bypass Tricks
By Luca Müller · October 11, 2026
Learn how to test file upload validation by separating browser checks from server checks, then verify MIME types, magic bytes, extensions, and rename-only bypass cases.
A file upload is only as trustworthy as the weakest check in the chain. If a system relies on the file extension alone, a renamed payload can glide past the UI and reach the backend. If it trusts the browser-supplied MIME type alone, a crafted upload can claim to be an image while containing something else. The practical goal when you test file upload validation is not just to see whether the form rejects bad files, but to verify that the browser, the client code, and the server each enforce the right rule for the right reason.
For a quick reference, the distinction that matters is this:
- File extension is the name suffix, such as
.pngor.pdf. - MIME type is a declared content type, such as
image/png. - Magic bytes are the leading bytes in the file that identify its real format, such as
89 50 4E 47for PNG.
Those three signals can agree, or they can disagree. A good upload test suite covers both situations.
The validation chain you should assume
A secure upload flow usually has at least four checkpoints:
- Browser input constraints, such as the
acceptattribute on<input type="file">. - Client-side validation, such as a JavaScript check that rejects obviously wrong files before upload.
- Server-side validation, which must be the final authority.
- Storage and processing checks, such as virus scanning, content-type revalidation, image parsing, or document conversion.
The critical testing rule is simple: browser filtering improves user experience, but only server-side validation can be trusted as a security control.
If you want a primary source for the browser side, MDN documents the accept attribute and the File API. For security-focused guidance, the OWASP File Upload Cheat Sheet is the most useful starting point.
What to test separately
The main mistake in upload testing is treating the UI as proof that the backend is safe. Instead, split your test cases by layer.
1) Browser file input testing
Browser-side checks answer a narrow question: does the form steer users toward the right file types?
Test these behaviors:
- The file picker filters by extension when
acceptis set. - The UI shows a client-side error when the wrong type is selected.
- The form does not submit when the client blocks the file.
- Drag-and-drop, paste, and programmatic upload paths behave consistently.
Do not overstate the meaning of a passing browser test. A file input can look restricted while a direct HTTP request still sends anything.
2) Server-side MIME type validation
Server-side MIME validation should answer: does the backend accept only the content types it truly supports?
Test cases should include:
- A file with a valid extension but wrong declared type.
- A file with the wrong extension but a valid declared type.
- A file whose client-reported
File.typeis empty or generic. - A request with a forged
Content-Typeheader.
You are testing whether the server trusts the client too much.
3) Magic byte checks
Magic byte validation answers a different question: does the backend inspect the file signature instead of trusting labels?
Test cases should include:
- A file renamed from
.exeto.png. - A file with a
.pngextension but a PDF signature. - A file with a correct header and a corrupted body.
- A polyglot or malformed file if your application handles high-risk uploads.
If the application stores documents, image previews, or media processing jobs, signature verification is usually more important than the extension rule shown in the browser.
A compact decision table for test design
| Check | What it proves | Good test input | Failure you are looking for |
|---|---|---|---|
accept attribute |
The picker nudges the user | Wrong extension selected in the UI | UI blocks nothing or blocks the wrong files |
| Client-side MIME validation | Frontend rejects obvious mistakes | Renamed file with mismatched type | Frontend trusts extension or filename only |
| Server-side MIME validation | Backend validates declared type | Forged Content-Type header |
Backend accepts client declaration blindly |
| Magic byte validation | Backend inspects file signature | File renamed to allowed extension | Backend trusts the name instead of bytes |
| Post-upload processing | Stored file is safe to use | File that passes upload but fails parser | Upload succeeds, later processing crashes or misclassifies |
Test cases for rename-only bypass tricks
Rename-only bypass is the easiest false positive to miss, because the file name looks valid while the content is not.
Minimum set of rename-only cases
Use a known-disallowed payload and rename it to an allowed name, for example:
payload.exerenamed topayload.pngdocument.htmlrenamed todocument.pdfscript.jsrenamed tophoto.jpg
Then verify all of the following:
- The browser file picker may still show the file as selectable if the extension matches the
acceptlist. - The client-side validator should reject it if it checks more than the filename.
- The server should reject it if it validates the actual file content.
- The stored metadata should not record a misleading content type.
A rename-only test is especially useful when the product supports previews, OCR, antivirus scanning, or downstream conversion. Those features often expose the difference between “looks like an image” and “is actually an image.”
Test cases for MIME type mismatch
A MIME mismatch can happen in a few ways, and each one tells you something different about the implementation.
Browser-reported type versus upload request type
In browser automation, the File.type value may be set by the test fixture or inferred by the browser. That value is not proof of server safety. A more important question is whether the backend reads the request body and compares it with the declared content type.
Useful cases:
.pngfile withimage/pngdeclared type, expected to pass..pngfile withapplication/pdfdeclared type, expected to fail or be normalized..pdffile withimage/pngdeclared type, expected to fail.
Content-Type header mismatch
For APIs that accept raw multipart or direct uploads, test a forged request where the body and Content-Type disagree. This catches servers that route solely on the header.
If the application relies on presigned uploads or object storage callbacks, verify where validation actually happens. Some systems validate only after the upload lands in storage, which means the test needs to assert both upload acceptance and post-upload rejection.
A Playwright example that exercises the UI and the backend separately
Use browser automation to validate the user-facing behavior, then use a direct request to test whether the server still enforces the rule when the UI is bypassed.
import { test, expect } from '@playwright/test';
import fs from 'fs';
test('rejects renamed executable disguised as png', async ({ page, request }) => {
await page.goto('https://example.com/upload');
await page.setInputFiles('input[type="file"]', {
name: 'invoice.png',
mimeType: 'image/png',
buffer: fs.readFileSync('fixtures/payload.exe')
});
await page.click('button[type="submit"]');
await expect(page.getByText(/invalid file type|unsupported/i)).toBeVisible();
const form = new FormData();
form.set('file', new Blob([fs.readFileSync('fixtures/payload.exe')], { type: 'image/png' }), 'invoice.png');
const res = await request.post('https://example.com/api/upload', { multipart: form as any });
expect(res.status()).not.toBe(200);
});
This example is intentionally simple. The test value comes from the separation of concerns, not from clever code.
How to build a useful test matrix
You do not need dozens of cases to catch the common bypasses. You need a matrix that covers the failure modes with enough variety to show whether the implementation checks name, declared type, or actual bytes.
A practical starter matrix:
- Allowed extension, valid MIME, valid magic bytes
- Allowed extension, invalid MIME, valid magic bytes
- Allowed extension, valid MIME, invalid magic bytes
- Disallowed extension, valid MIME, valid magic bytes
- Renamed file with allowed extension, disallowed magic bytes
- Empty or missing MIME type
- Uppercase extension and mixed-case filename
- Double extension, such as
invoice.pdf.exe
If the app accepts archives, documents, or images from untrusted users, add file-specific cases. For example, a PDF validator should inspect the header and also reject malformed object streams if your downstream parser is sensitive to them.
Failure modes worth asserting explicitly
Good upload tests do more than check pass or fail. They verify the system response when validation fails.
Assert that a rejected upload:
- Returns the correct status or UI error.
- Does not create a database record.
- Does not write the file to persistent storage.
- Does not enqueue follow-on processing jobs.
- Does not show a thumbnail, preview, or download link.
This matters because a system can reject the upload from the user’s point of view while still storing the object behind the scenes.
Also check the bypass paths
Some upload surfaces are harder to see than the main form:
- Profile picture widgets
- Rich text editor attachments
- Import/export jobs
- Mobile app document pickers
- API endpoints used by the frontend but callable directly
If any of these accept files, treat them as separate attack surfaces. Reuse the same validation cases, but do not assume they share the same server code.
What not to trust
A few signals are helpful for UX, but weak for security:
- The file name alone
- The browser picker filter alone
- The frontend
File.typealone - A green upload toast alone
- The object storage content type alone
The backend must inspect something stronger than the client-provided label. Depending on the file class, that might be magic bytes, parser validation, a content sniffing library, or a virus scanner plus format parser.
If the defense can be bypassed by renaming a file, you do not have validation, you have decoration.
Maintenance tips for automation suites
Upload tests tend to rot for predictable reasons, so build them to survive content changes.
- Keep a small library of fixture files with obvious names.
- Store fixtures by intent, such as
renamed-exe.pngorbad-signature.pdf. - Avoid generating file content inline in many tests, because the intent becomes hard to audit.
- Version-control the expected error messages carefully, since upload copy changes often.
- Re-run the matrix when backend libraries change, especially parsers, content sniffers, or antivirus integrations.
If your team uses shared fixtures across web and API tests, make the fixture naming explicit. The filename should tell the reader what bypass it represents.
A short checklist for coverage
Before you call upload validation complete, confirm that your tests answer these questions:
- Does the UI guide the user toward the right file types?
- Does the frontend reject obvious mismatches without blocking valid files?
- Does the server reject forged MIME types?
- Does the server inspect magic bytes or equivalent file signatures?
- Do renamed files fail when their bytes do not match the allowed format?
- Do rejected uploads leave no stored artifact behind?
- Do preview, conversion, and indexing steps fail safely?
If the answer to any of those is unclear, the test suite is still exposing the symptom, not the control.
FAQ
Is MIME type validation enough for file uploads?
No. MIME type is often client-supplied or easily forged, so it should not be the only validation check.
Are magic bytes always better than file extensions?
They are stronger than extensions for format detection, but they are not sufficient by themselves for all security decisions. You still need server-side policy, parsing, and safe processing.
Can browser file input testing prove the backend is secure?
No. Browser restrictions improve usability, but a direct request can bypass the UI.
What is the simplest rename-only bypass test?
Take a disallowed file, rename it to an allowed extension, and verify that the server rejects it based on content, not the name.
Should upload tests check the stored file or only the response?
Check both. A failing response without a stored artifact is the safer outcome.
What should I automate first?
Start with one allowed file, one renamed disallowed file, and one MIME mismatch. That trio catches the most common validation gaps without making the suite hard to maintain.