MIME Types for File Upload Validation

File upload validation requires checking MIME type on both client and server. File.type limitations, magic byte detection, and validation in Multer, Django, and Spring Boot.

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.

Magic byte signatures

  • JPEG FF D8 FF at byte offset 0
  • PNG 89 50 4E 47 0D 0A 1A 0A (8 bytes)
  • PDF ASCII %PDF — bytes 25 50 44 46
  • WebAssembly 00 61 73 6D — a null byte followed by ASCII "asm"

Opens the MIME Type Reference with this page's checklist shown at the top of the tool.

Open in the tool →

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

Before
// Insecure: file.type is extension-derived and attacker-controlled
if (file.type === 'image/jpeg') { upload(file); }
After
// 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.

Sources
  1. 1.

    Mozilla Developer Network, "File.type," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/API/File/type

  2. 2.

    W3C, "PNG Specification," w3.org, accessed June 2026. https://www.w3.org/TR/png/

  3. 3.

    "python-magic," PyPI, pypi.org, accessed June 2026. https://pypi.org/project/python-magic/

  4. 4.

    Apache Tika, "Detection," tika.apache.org, accessed June 2026. https://tika.apache.org/2.1.0/detection.html

  5. 5.

    "Multer," npm, npmjs.com, accessed June 2026. https://www.npmjs.com/package/multer

  6. 6.

    Django, "FILE_UPLOAD_VALIDATORS," docs.djangoproject.com, accessed June 2026. https://docs.djangoproject.com/en/stable/ref/settings/#file-upload-validators

  7. 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. 8.

    "sharp," GitHub, github.com, accessed June 2026. https://github.com/lovell/sharp

FAQ

No. file.type is derived from the file extension by the operating system, not from the file content. An attacker can rename any file to .jpg to make file.type return "image/jpeg". Client-side type checks are only useful for user experience. All security-relevant validation must happen server-side using magic byte detection.

Magic bytes are characteristic byte sequences at the start of a file that identify its format. JPEG files start with FF D8 FF. PNG files start with 89 50 4E 47. Reading these bytes from an uploaded file and comparing them against known signatures identifies the actual format independent of the filename or Content-Type header. Libraries like python-magic and file-type automate this process.

An allowed list is safer. Define exactly which MIME types you accept and reject everything else. A blocked list requires you to enumerate every dangerous type, which is impossible to keep complete as new formats emerge. An allowed list like Set(["image/jpeg", "image/png", "image/webp"]) rejects everything not explicitly permitted.

Yes, with effort. An attacker can prepend valid JPEG magic bytes to a PHP or HTML payload, creating a polyglot file that magic detection classifies as JPEG. Further validation such as attempting to parse the file as the detected format and rejecting files that fail parsing provides additional protection. For image uploads, processing the image through an image library (resize, re-encode) effectively strips any embedded non-image content.

Multer's fileFilter callback receives mimetype from the multipart form-data body, which is the client-supplied Content-Type for the file part. This is not a security check. For server-side security validation, read the uploaded file bytes and apply magic detection with the file-type package after Multer stores the file. Reject and delete files that do not match your allowed types. CapyToolkit's MIME reference lists the correct magic byte signatures for common formats so you can verify your detection logic.

Additional resources