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

FormatOffsetBytes (hex)ASCIIConfirmed against
PDF025 50 44 46 2D%PDF-Generated file, ISO 32000-1
ZIP (and DOCX, XLSX, PPTX, ODT)050 4B 03 04PK..Generated file, PKWARE APPNOTE
RAR 5052 61 72 21 1A 07 01 00Rar!....Generated file, RARLAB technote
7z037 7A BC AF 27 1C7z....Generated file, 7-Zip format description
gzip01F 8BGenerated file, RFC 1952
Windows program (PE)0, then at the offset stored at 0x3C4D 5A, then 50 45 00 00MZ, PE\0\0Microsoft PE format only
PNG089 50 4E 47 0D 0A 1A 0A.PNG....W3C PNG specification only
JPEG0FF D8 FFJFIF 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 starts 50 4B 03 04. A spanned or split archive may instead start with the marker 0x08074b50, followed by the first local header (section 8.5.3), so a strict check for PK\x03\x04 can 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 bytes 0x52 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 0x3c the MS-DOS stub has the file offset to the PE signature, and that the signature is PE\0\0. The MZ (0x5A4D) value at the start comes from the IMAGE_DOS_SIGNATURE definition in Wine’s winnt.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 writes 0xff, 0xd8 as SOI), and the JFIF specification requires an APP0 marker right after it. Markers start with FF, so the usual first three bytes are FF 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:

  1. Accept. A real PDF, ZIP or DOCX of the size you expect passes.
  2. Wrong extension. Rename a PDF to .png and upload it. It should be rejected because its content does not match. Our libmagic run reported application/pdf for the renamed file.
  3. Wrong content, right extension. A zero-filled file and a random file, both named .pdf, were typed application/octet-stream by libmagic. A validator that reads the first bytes must reject both. See create a dummy file of any size for how to make them.
  4. 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 with PK and libmagic said application/zip, but unzip -t reported End-of-central-directory signature not found. So the signature says “looks like a PDF”, and only parsing shows “is a usable PDF”.
  5. 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.

These pages build the file in your browser at the exact byte size you choose. Nothing is uploaded.

Related guides

All guides

Send feedback

Request a feature or tell us what is not working.

What is it about?

We store your message to read it and, if you add an email, to reply. Privacy policy