Testing the error path nobody tests
Malformed input causes a large share of production incidents and is the least exercised code in most codebases. Deliberately broken files fix that cheaply.
Applications are built, demoed and tested with well-formed input. The code that handles malformed input is often the least exercised in the entire codebase, and it is the first code an attacker or an unlucky user reaches.
Deliberately corrupted files close that gap in about a minute.
The four questions worth answering
1. Does it fail closed?
Hand your parser a file whose header claims one thing and whose body is noise. The acceptable outcomes are a clean error and a rejected upload. The unacceptable ones are an unhandled exception, an unbounded allocation, or a hang.
Unbounded allocation is the one that bites hardest. A length field read from a corrupted header can easily say “this record is 3 GB”. If your code trusts it and allocates, one bad upload takes the process down.
2. Does the user see something useful?
There is a large gap between a message that says the file could not be read, and one that prints a stack trace with your server’s directory layout in it. The second leaks information, and it is the sort of thing that never gets noticed until a security review. Corrupted files make it trivially easy to check.
3. Does the retry path work?
Simulate a truncated download by corrupting an artifact and putting it where your updater expects it. Does the checksum get validated before the file is used? Does a failed verification trigger a retry, or does it write a broken artifact over a working one?
This is the failure that turns a bad deploy into an outage, and it is entirely testable in advance.
4. Does the restore actually restore?
A backup you have never restored is a hypothesis. Corrupt a dump, run the restore runbook against it, and find out whether the import runs inside a transaction and rolls back cleanly, whether the failure is reported loudly or swallowed by a cron job, and whether anyone would notice before the good copy rotated out of retention.
Use Light-level corruption for this one. A file that fails immediately is the easy case. A file that imports four thousand rows and then fails is the case that exposes missing transaction handling.
Building a corpus
For a regression suite you want fixtures that are stable, not random. Keep a small set checked into the repository:
| Fixture | What it tests |
|---|---|
| Truncated archive | Extraction fails without hanging |
| Header destroyed, payload intact | Format detection, not just extension checks |
| Valid header, payload noise | Decompression error handling |
| Zero-length file | The path everyone forgets |
| Correct extension, wrong format | Content sniffing rather than trusting the name |
That last one catches a surprising number of bugs. Plenty of code decides how to
parse a file purely from its extension, which means a .png full of ZIP data
goes straight into the image decoder. Why that happens, and what identifies a
file properly, is covered in magic bytes, and why a file extension is not a
file type.
You can generate a damaged file for each row above in about a minute, and for upload endpoints specifically the fuller checklist is in validating file uploads properly.
Where this fits
None of this replaces fuzzing. Fuzzing explores the input space far more thoroughly than a handful of fixtures ever will. But fuzzing needs a harness, time and somewhere to run, whereas a corrupted file takes seconds and can be dropped into a test suite you already have.
Start with the fixtures. Add fuzzing when the fixtures stop finding anything, and when you get there, from a corrupted file to a fuzzing harness picks up exactly where this leaves off.
The infrastructure equivalent, rehearsing a restore rather than a parse, is in your backup is a hypothesis until you restore it.