MIME Types for File Upload Validation: Client-Side, Magic Bytes, and Frameworks
File upload validation requires checking MIME type on both client and server. Restricting file types to a safe allowed list prevents malicious uploads from reaching storage or being served to other users. The challenge is that MIME type information is available at two places in an upload flow, and neither is fully trustworthy alone.
On the client side, the browser's File API exposes file.type derived from the file extension, which an attacker trivially controls by renaming a file.1 On the server side, the Content-Type header in the multipart form-data body is also client-supplied and equally untrustworthy. Consequently, server-side validation must inspect the file's actual bytes, using magic number detection to identify the real format independent of the declared type. This magic byte approach is the only reliable way to verify file format on the server. Libraries like libmagic in Python and C, python-magic, and file-type in Node.js implement this detection.
Why client-side MIME type is not trustworthy
The File API's file.type property in the browser reads the MIME type from the operating system's file extension mapping, not from the file's actual content. Renaming a PHP script to image.jpg gives it file.type of "image/jpeg" according to the browser. The Content-Type header in the multipart form-data request body is set by the browser based on this same extension-derived value, meaning the server receives Content-Type: image/jpeg for the renamed PHP file. Both sources of MIME information are therefore client-controlled and cannot be trusted for security decisions.1
Client-side validation as a UX convenience only
Furthermore, client-side validation with accept attributes on file inputs and file.type checks in JavaScript is useful for user experience (preventing accidental wrong-format uploads) but provides no security value. All security-relevant type checking must happen server-side with content-based detection. The accept attribute on file inputs and JavaScript file.type checks are easily bypassed by renaming a file extension or crafting a request with spoofed headers, which means these client-side measures only protect against accidental mistakes by legitimate users, not against deliberate attacks.
Magic number checking and file signature verification
Every file format has characteristic byte sequences at known offsets in the file body, and these magic bytes (or file signatures) identify the actual format independent of the filename extension or declared MIME type that the client sent. JPEG files begin with FF D8 FF at byte offset 0, PNG files begin with the eight-byte sequence 89 50 4E 47 0D 0A 1A 0A, and PDF files start with the ASCII characters %PDF (bytes 25 50 44 46).2 WebP files have RIFF at offset 0 and WEBP at offset 8, while WebAssembly binaries begin with the null byte followed by the ASCII characters "asm" (00 61 73 6D). Checking these signatures on the server before storing or serving an uploaded file is the only reliable defence against type confusion attacks.
Libraries for server-side magic detection
python-magic is a Python binding to libmagic that detects format from bytes: magic.from_buffer(file_bytes, mime=True) returns the detected MIME type string.3 In Node.js, the file-type package reads the first bytes of a stream or buffer and returns the detected format without requiring the full file to be loaded into memory first. For Java, Apache Tika detects format from content and is the standard library for server-side type detection across enterprise applications.4 Using an allowed list that accepts only specific detected MIME types, rather than maintaining a blocklist of dangerous types, is the safer approach because it rejects any format you have not explicitly approved.
Framework-specific validation in Multer, Django, and Spring Boot
Framework-level upload validation provides a structured place to apply magic byte detection. In Node.js with Multer and Express, the fileFilter callback receives the file's mimetype from the Content-Type header and the req object; however, this mimetype is client-supplied.5 Pair the Multer fileFilter with post-upload magic detection using the file-type package on the stored bytes. In Python with Django, FILE_UPLOAD_VALIDATORS accepts a list of validator functions called after the upload is buffered.6 Apply magic byte detection inside a validator function using python-magic. In Spring Boot, @RequestParam MultipartFile exposes getContentType() which is client-supplied, so pair this with Apache Tika's Detector applied to the file's InputStream for server-side format verification.7 Consequently, framework-level hooks give you a clean integration point for magic detection without writing custom request parsing code.
Image reprocessing as a defence against polyglot file attacks
Magic byte detection identifies the file format from its leading bytes but cannot eliminate embedded payloads in the file body. A polyglot file starts with valid JPEG magic bytes and contains an HTML or script payload at a deeper byte offset, passing magic byte detection while embedding executable content. Re-encoding the uploaded image through an image library is a more robust defence: the encoder reads the file as image data and writes back only pixel values, discarding any non-image bytes in the process.
Sharp in Node.js reprocesses uploaded images with a single method chain: sharp(uploadedBuffer).jpeg({ quality: 85 }).toBuffer() reads the buffer as a JPEG and outputs clean, re-encoded bytes.8 An input buffer containing a payload rather than valid JPEG data causes Sharp to throw a decode error, which you catch and convert into a rejection response. The re-encoded output contains no trace of the original file's non-image content.
Protecting against pixel-flood inputs
Set a maximum input file size limit in your multipart parser before reprocessing begins, to prevent attackers from sending enormous image files that consume CPU. Additionally, Sharp's resize() method called before re-encoding limits the decoded pixel dimensions. A resize cap of 4000x4000 pixels prevents pixel-flood attacks where a specially crafted image claims to have extremely large dimensions that exhaust decoder memory. These two limits together constrain both bandwidth and CPU cost for the reprocessing step.
The limits should be set as part of the upload policy rather than buried in the reprocessing code, because a missing size cap before the image library runs still allows a malicious client to exhaust memory during decode. Placing the check at the parser boundary catches the oversized input before any expensive transformation begins. confirm upload Content-Type before storing so the stored object matches what the validator expects.
When to use this
Use this guide when implementing a file upload endpoint that needs to reject unwanted file types, auditing an existing upload endpoint for type-confusion vulnerabilities, or adding server-side MIME validation to a framework that relies on client-supplied Content-Type.
Examples
Node.js: magic byte detection with file-type after Multer upload
Multer stores the file first; then detect the actual format from the stored bytes using file-type before accepting the upload.
Python: magic byte detection with python-magic
Detect MIME type from file bytes server-side, independent of the client-supplied Content-Type header.
Never trust file.type alone in browser JavaScript
// Insecure: file.type is extension-derived and attacker-controlled
if (file.type === 'image/jpeg') { upload(file); } // Acceptable for UX only (add server-side magic detection)
if (file.type === 'image/jpeg') { upload(file); }
// Server validates actual bytes independently Client-side type checks improve UX but provide zero security. Always validate on the server with magic byte detection.
- 1.
Mozilla Developer Network, "File.type," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/API/File/type
- 2.
W3C, "PNG Specification," w3.org, accessed June 2026. https://www.w3.org/TR/png/
- 3.
"python-magic," PyPI, pypi.org, accessed June 2026. https://pypi.org/project/python-magic/
- 4.
Apache Tika, "Detection," tika.apache.org, accessed June 2026. https://tika.apache.org/2.1.0/detection.html
- 5.
"Multer," npm, npmjs.com, accessed June 2026. https://www.npmjs.com/package/multer
- 6.
Django, "FILE_UPLOAD_VALIDATORS," docs.djangoproject.com, accessed June 2026. https://docs.djangoproject.com/en/stable/ref/settings/#file-upload-validators
- 7.
Pivotal Software, "MultipartFile," Spring Framework docs.spring.io, accessed June 2026. https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/multipart/MultipartFile.html
- 8.
"sharp," GitHub, github.com, accessed June 2026. https://github.com/lovell/sharp