File Signature Magic Bytes: Validate Uploads Safely
By Niks · Published · Updated
Tested on
- Files generated by this site's own generator code (Node.js v22.13.0), dumped with xxd on Windows 11 (10.0.22631)
- file 5.48 (libmagic), unzip 6.00, pdf-lib 1.17.1
- Run on 29 September 2026. The PNG, JPEG and Windows PE (MZ) signatures come from the specifications and were not checked against generated files: our generator makes PNG and JPEG in a browser canvas, which we could not run here, and this site does not generate Windows programs.
File signature magic bytes are the fixed sequence of bytes a format starts with, also called the file signature: %PDF- for PDF, PK followed by 03 04 for ZIP, MZ for Windows programs. To validate an upload, read the first bytes of the file yourself and compare them with the signature of the type you expect. Do not trust the extension or the Content-Type header, because the client chooses both. A signature check is only the first step: it says what a file claims to be, not that it is safe or well-formed.
This guide lists the signatures, shows them in real files, and explains how to test your validator with dummy files.
File signature magic bytes for common formats
| Format | Offset | Bytes (hex) | ASCII | Confirmed against |
|---|---|---|---|---|
| 0 | 25 50 44 46 2D | %PDF- | Generated file, ISO 32000-1 | |
| ZIP (and DOCX, XLSX, PPTX, ODT) | 0 | 50 4B 03 04 | PK.. | Generated file, PKWARE APPNOTE |
| RAR 5 | 0 | 52 61 72 21 1A 07 01 00 | Rar!.... | Generated file, RARLAB technote |
| 7z | 0 | 37 7A BC AF 27 1C | 7z.... | Generated file, 7-Zip format description |
| gzip | 0 | 1F 8B | Generated file, RFC 1952 | |
| Windows program (PE) | 0, then at the offset stored at 0x3C | 4D 5A, then 50 45 00 00 | MZ, PE\0\0 | Microsoft PE format only |
| PNG | 0 | 89 50 4E 47 0D 0A 1A 0A | .PNG.... | W3C PNG specification only |
| JPEG | 0 | FF D8 FF | JFIF and RFC 2435 only |
“Generated file” means we produced a file with this site’s generator code, dumped its first bytes, and compared them. These are real dumps:
sample.pdf 25 50 44 46 2d 31 2e 37 0a 25 81 81 81 81 0a 0a %PDF-1.7.%......
sample.zip 50 4b 03 04 14 00 00 08 00 00 83 02 3e 5d 51 7f PK..........>]Q.
sample.rar 52 61 72 21 1a 07 01 00 c5 1a 33 32 03 01 00 00 Rar!......32....
sample.7z 37 7a bc af 27 1c 00 04 1f d4 53 33 53 27 00 00 7z..'.....S3S'..
sample.gz 1f 8b 08 08 5e 08 bc 6a 00 03 61 72 63 68 69 76 ....^..j..archiv
What each source says:
- PDF. ISO 32000-1, section 7.5.2, says the first line of a PDF file is a header of the five characters
%PDF-followed by a version number. Our generated file starts%PDF-1.7. We could not fetch the standard’s PDF itself, so the wording is from that section as quoted by public sources, and the file was also loaded successfully with pdf-lib. - ZIP. PKWARE’s APPNOTE, section 4.3.7, gives the local file header signature as
0x04034b50. Stored little-endian, the file starts50 4B 03 04. A spanned or split archive may instead start with the marker0x08074b50, followed by the first local header (section 8.5.3), so a strict check forPK\x03\x04can reject a legitimate split archive. - RAR. The RARLAB technote gives the RAR 5.0 signature as 8 bytes,
0x52 0x61 0x72 0x21 0x1A 0x07 0x01 0x00, and the older RAR 4.x signature as the 7 bytes0x52 0x61 0x72 0x21 0x1A 0x07 0x00. A validator that accepts RAR should say which version it means. - 7z. The 7-Zip format description defines
kSignature[6] = {'7', 'z', 0xBC, 0xAF, 0x27, 0x1C}. - gzip. RFC 1952 fixes ID1 as 31 (0x1f) and ID2 as 139 (0x8b).
- PE. Microsoft’s PE format documentation says that at location
0x3cthe MS-DOS stub has the file offset to the PE signature, and that the signature isPE\0\0. TheMZ(0x5A4D) value at the start comes from theIMAGE_DOS_SIGNATUREdefinition in Wine’swinnt.h, a reimplementation of the Windows header. We did not generate or dump a Windows program: this site does not offer one, so this row comes from the documentation only. - PNG. The W3C PNG specification, section 5.2, says the first eight bytes always contain
89 50 4E 47 0D 0A 1A 0A. We did not generate a PNG here: the site’s PNG generator draws on a browser canvas, and we ran the generator in Node.js, where that path is not available, so we cannot show a dump of one. - JPEG. A JPEG stream starts with the start-of-image marker
FF D8(the RFC 2435 reference code writes0xff, 0xd8asSOI), and the JFIF specification requires an APP0 marker right after it. Markers start withFF, so the usual first three bytes areFF D8 FF. Again, no generated JPEG was checked.
Office files share the ZIP signature
DOCX, XLSX, PPTX and ODT files are ZIP packages, so all of them start with PK 03 04. A signature check cannot tell a Word document from a ZIP file. We listed a generated DOCX:
$ unzip -l sample.docx
Length Date Time Name
--------- ---------- ----- ----
508 2026-09-29 18:50 [Content_Types].xml
0 2026-09-29 18:50 _rels/
302 2026-09-29 18:50 _rels/.rels
0 2026-09-29 18:50 word/
355 2026-09-29 18:50 word/document.xml
8459 2026-09-29 18:50 padding.dat
To identify the format, open the archive and look for [Content_Types].xml and the part names such as word/document.xml. libmagic does this: it typed our generated DOCX as application/vnd.openxmlformats-officedocument.wordprocessingml.document and the XLSX as application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, while the plain ZIP was application/zip. Renaming the DOCX to .zip did not change the result, because file ignores extensions.
Test your validator with real files
Use dummy files to check both the accept and the reject paths:
- Accept. A real PDF, ZIP or DOCX of the size you expect passes.
- Wrong extension. Rename a PDF to
.pngand upload it. It should be rejected because its content does not match. Our libmagic run reportedapplication/pdffor the renamed file. - Wrong content, right extension. A zero-filled file and a random file, both named
.pdf, were typedapplication/octet-streamby libmagic. A validator that reads the first bytes must reject both. See create a dummy file of any size for how to make them. - Truncated file. Cut a real file short and check what your pipeline does. Both survive a signature check by design. We cut our PDF to 1,500 bytes: libmagic still reported
application/pdf, but pdf-lib’s strict parser failed (Trying to parse invalid object). A truncated ZIP still starts withPKand libmagic saidapplication/zip, butunzip -treportedEnd-of-central-directory signature not found. So the signature says “looks like a PDF”, and only parsing shows “is a usable PDF”. - Size at the limit. Combine this with N-1, N, N+1 sizes, as in how to test file upload limits.
Beyond signatures: what a validator must also do
OWASP’s File Upload Cheat Sheet says to validate the file type rather than trust the Content-Type header, because it can be spoofed, and says to set a file size limit. It describes signature validation as a supplement to content-type validation, and warns that it should not be used on its own, as bypassing it is pretty common and easy. The same sheet lists more controls:
- Generate the stored filename yourself (a UUID) instead of using the client’s, and restrict length and characters. It flags double extensions, null bytes and sequential periods as bypass and traversal techniques.
- Set size limits, including for extracted content: it names ZIP bombs and XML bombs among the attacks, and says that if the system extracts or processes files, the size limit should be considered after decompression, using secure methods to calculate the size. In practice, cap the total extracted size, the number of entries and the nesting depth, and reject entries whose names contain
..or start with/. - Consider whether to accept archives at all. The sheet says ZIP files are not recommended, since they can contain all types of files and the attack vectors are numerous.
- For images, rewrite them (decode and re-encode). The sheet says image rewriting destroys malicious content injected in an image.
- Store uploads outside the web root, or on a different host.
The advice about entry counts and depth is our recommendation; the sheet does not give numbers. A file that passes all of this is still only known to be plausible. Run antivirus or content scanning where the risk justifies it.
Try it
Generate real files to test against: a Dummy PDF file for the %PDF- check, a Dummy Excel file to see the ZIP-based Office signature, a Dummy ZIP file for PK, a Dummy RAR file for the RAR 5 signature, a Dummy Word file for a DOCX, and a Dummy PNG file or Dummy JPG file for the image signatures. They are built in your browser and nothing is uploaded.
Sources
Accessed 30 September 2026.
- ISO 32000-1 (PDF 1.7), section 7.5.2 File Header: https://pdfa.org/resource/iso-32000-1/
- PKWARE, APPNOTE.TXT (.ZIP File Format Specification), version 6.3.10: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
- RARLAB, RAR technical note: https://www.rarlab.com/technote.htm
- 7-Zip, 7z format description: https://github.com/ip7z/7zip/blob/main/DOC/7zFormat.txt
- RFC 1952, GZIP file format specification: https://www.rfc-editor.org/rfc/rfc1952
- Microsoft, PE format: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
- Wine,
winnt.h(IMAGE_DOS_SIGNATURE): https://github.com/wine-mirror/wine/blob/master/include/winnt.h - W3C, Portable Network Graphics (PNG) Specification, section 5.2: https://www.w3.org/TR/png-3/
- W3C, JPEG File Interchange Format 1.02: https://www.w3.org/Graphics/JPEG/jfif.txt
- RFC 2435, RTP Payload Format for JPEG-compressed Video: https://www.rfc-editor.org/rfc/rfc2435
- OWASP, File Upload Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html
- File dumps and validator runs: this site’s generator code, xxd, file 5.48, unzip 6.00 and pdf-lib 1.17.1 on Windows 11, 29 September 2026.