File Type Validation with Magic Bytes (Not File Extension)
Detect true file types using magic bytes in Node.js with the file-type library. Extension-based validation is trivially bypassed β learn the secure approach.
Relying solely on file extensions (like .pdf or .jpg) or the client-supplied Content-Type header is dangerous. An attacker can easily rename malware.exe to invoice.pdf and bypass standard filters. True file validation requires inspecting magic bytes.
What Are Magic Bytes?
Magic bytes (file signatures) are specific hexadecimal byte sequences located at the exact beginning of a file:
| File Format | Extension | Magic Bytes (Hex) | ASCII Representation |
|---|---|---|---|
25 50 44 46 | %PDF | ||
| PNG | .png | 89 50 4E 47 | .PNG |
| JPEG | .jpg | FF D8 FF | ΓΏΓΓΏ |
| ZIP / DOCX | .zip/.docx | 50 4B 03 04 | PK.. |
| GIF | .gif | 47 49 46 38 | GIF8 |
Download a real 1MB DOCX file to inspect its PK ZIP container magic bytes in your code.
Download 1MB DOCX Sample βDetecting Magic Bytes in Node.js with file-type
npm install file-type
import { fileTypeFromBuffer } from 'file-type';
export async function validateActualFileType(buffer: Buffer, expectedMime: string): Promise<boolean> {
const detected = await fileTypeFromBuffer(buffer);
if (!detected) {
console.error('Unknown or unsupported binary structure.');
return false;
}
console.log(`Detected true format: ${detected.ext} (${detected.mime})`);
return detected.mime === expectedMime;
}
Reading Magic Bytes in the Browser
You can read the first 16 bytes of any File or Blob without loading the whole file into RAM:
export async function checkPdfHeaderInBrowser(file: File): Promise<boolean> {
const headerBlob = file.slice(0, 4);
const arrayBuffer = await headerBlob.arrayBuffer();
const bytes = new Uint8Array(arrayBuffer);
// %PDF in ASCII is [0x25, 0x50, 0x44, 0x46]
return bytes[0] === 0x25 && bytes[1] === 0x50 && bytes[2] === 0x44 && bytes[3] === 0x46;
}
Security Gotchas
- Polyglot files: Files crafted to be simultaneously valid JPEGs and executable scripts. Always pair magic byte validation with format re-encoding or sandboxed processing.
- DOCX/XLSX detection: Office documents are ZIP archives. Their magic bytes will report as
application/zip. Use an OpenXML validator to check forword/document.xmlinside the archive.
Nguyen Dai Long
AuthorBackend Lead β’ Distributed Systems & Cloud Edge Architecture Specialist
4+ years designing high-throughput file ingestion pipelines, database architectures, and distributed edge storage on Cloudflare R2 & AWS S3. Founder of FileDummy and the NDL Ecosystem.
Was this article helpful?
Click Like to support the author and help other developers discover this guide.
Engineering Discussion & Feedback (0)
Share benchmark results, report edge cases, or ask technical questions.
No comments yet. Be the first developer to start the discussion!
Frequently Asked Questions
What are magic bytes?βΎ
Magic bytes are the first few bytes of a file that uniquely identify its format. PDF starts with 25 50 44 46 (%PDF), ZIP/DOCX with 50 4B 03 04 (PK..), JPEG with FF D8 FF, PNG with 89 50 4E 47. The file-type library uses these signatures to detect true format regardless of extension.
Why is extension-based validation dangerous?βΎ
An attacker can rename malware.exe to document.pdf and bypass extension checks. Operating systems determine file type from content, not filename. Always validate actual bytes using file-type (Node.js) or Apache Tika (Java).
Can I detect file types in the browser?βΎ
Yes. Read the first 12 bytes using FileReader.readAsArrayBuffer() on a Blob.slice(0, 12), then check against known signatures. The file-type library also works in browser environments.