blobforge

Adversarial fixtures

break your zip parser

11 ZIP archives engineered to fail. Path traversal, absolute and backslash entry names, a NUL inside a filename, duplicate entries, a CRC that does not match, and a header that lies about its size — each with the correct behaviour documented next to it.

Built in your browser. Nothing is uploaded, and no signup is required.

0Passed
0Failed
11Untested

Entry path escaping the target directory

paths-path-traversal.zip

What this tests

An entry named `../../../tmp/blobforge-escaped.txt`, alongside a normal entry.

What a correct parser should do

Reject the entry, or resolve it against the destination and refuse anything that lands outside. This is Zip Slip: extractors that join the entry name onto the output path without normalising it will write wherever the path points. The fix is to resolve the final path and verify it is still inside the destination directory — checking for the literal string '..' is not sufficient.

Bytes · 360 B
00000000504B030414000000000000002100F3A4PK..........!...
00000010109D21000000210000000A0000007265..!...!.......re
0000002061646D652E7478747468697320656E74adme.txtthis ent
00000030727920697320696E6572742074657374ry is inert test
0000004020636F6E74656E740A504B0304140000 content.PK.....
000000500000000000210025897F824100000041.....!.%...A...A
00000060000000220000002E2E2F2E2E2F2E2E2F..."...../../../
00000070746D702F626C6F62666F7267652D6573tmp/blobforge-es
0000008063617065642E74787469662074686973caped.txtif this
000000902066696C6520657869737473206F7574 file exists out
000000A073696465207468652074617267657420side the target
000000B06469722C2065787472616374696F6E20dir, extraction
000000C0697320756E736166650A504B01021400is unsafe.PK....
000000D014000000000000002100F3A4109D2100........!.....!.
000000E00000210000000A000000000000000000..!.............
000000F00000000000000000726561646D652E74........readme.t
000001007874504B010214001400000000000000xtPK............
00000110210025897F8241000000410000002200!.%...A...A...".
0000012000000000000000000000000049000000............I...
000001302E2E2F2E2E2F2E2E2F746D702F626C6F../../../tmp/blo
0000014062666F7267652D657363617065642E74bforge-escaped.t
000001507874504B050600000000020002008800xtPK............
000001600000CA0000000000........
Hover a byte to see why it matters.
  • NUL byte
  • Line feed
  • Control character
  • Double quote
  • Delimiter
  • Non-ASCII byte

How to use these fixtures

  1. Download a single file, or take all 11 as a ZIP — the archive includes a README explaining every file.
  2. Run each one through your own ZIP import path.
  3. Come back and record whether your parser handled it. Results are kept in your browser, so leaving the page is expected.
  4. Copy the damage report as Markdown and paste it into a pull request or an issue.

Why ZIP parsers fail

A ZIP archive is a list of entry names plus a promise about each entry's size and checksum. Every one of those is attacker-controlled, and extraction code routinely trusts all of them — joining the entry name straight onto an output path, pre-allocating from the declared size, and never checking the CRC it just read.

These archives are built byte by byte rather than with a ZIP library, because a correct library refuses to produce them. That is also why the hex view matters more here than on the text formats: you can see the local file header, the entry name with its traversal sequence intact, and the central directory that declares it.

Questions

What is Zip Slip?
An archive entry whose name contains ../ sequences, so that joining it onto your extraction directory produces a path outside that directory. An extractor that writes to the joined path without normalising it will write wherever the entry points. The fix is to resolve the final path and verify it is still inside the destination — checking for the literal string '..' is not sufficient, because encodings and backslashes bypass that check.
Why build these by hand instead of with a ZIP library?
Because the most valuable fixtures are archives that are wrong, and a correct ZIP library will not produce them. JSZip normalises ../ out of entry names, which removes the entire point of a traversal fixture, and no conforming writer will emit an entry whose declared size disagrees with its data. These are written byte by byte so every header field stays under control.
Is the compression bomb fixture actually dangerous?
No. A real decompression bomb is highly repetitive content actually deflated, so a few kilobytes on disk expand to gigabytes in memory. This fixture stores its megabyte uncompressed, so the archive is honest about its size — it demonstrates the content shape without being a weapon. The lesson it teaches is to budget total extracted bytes rather than trusting archive size.
Are these files dangerous?
No. They are inert test files for checking your own import pipeline. Nothing here executes on its own — the fixtures that model injection or traversal exist so you can verify your own code rejects them, and the resource-exhaustion ones are deliberately scaled down so they demonstrate the behaviour without taking down the machine you are testing from.
Why does the page show raw bytes?
Because most of these cases are invisible in a text preview. A byte order mark renders as nothing, a bare carriage return looks identical to a CRLF, and a NUL looks like the end of the string. The hex view highlights exactly which bytes matter and explains each one when you hover it.
Does my pass/fail progress get saved?
Yes, in your own browser only, and separately for each format. The workflow expects you to leave the page, run a fixture through your parser, and come back — so results persist locally. Nothing is uploaded, and clearing site data clears them.
Is this free?
Yes. Every fixture is generated in your browser, with no signup and no upload.