

0 / 2 embers
0 / 3000 xp
click for more info
Complete a lesson to start your streak
click for more info
Still calibrating
click for more info
Not enough gems
Cost: 6 gems
1: Injection
incomplete
2: Fixing SQL Injection
incomplete
3: Injection Beyond SQL
incomplete
4: Safe Validation and Sanitization
incomplete
5: When to Sanitize
incomplete
6: Unsafe Archive Extraction
incomplete
7: Safe Archive Extraction
incomplete
8: LLM Prompt Injection
incomplete
9: Limiting Tool Calls
incomplete
10: Narrow Tool Interfaces
incomplete
11: File Upload Security
incomplete
Back
ctrl+,
Next
ctrl+.
This lesson's interactive features are locked, please to keep using them
A ZIP archive is not a single document. It's a container whose entries each include a filename, like july/invoice.pdf. Extracting the archive means trusting those names enough to turn them into new file paths on your server.
... and that's dangerous when the archive came from a user.
Imagine an invoice-processing service that extracts .zip archives of monthly invoices into a data/monthly-invoices directory on the server. A naïve extractor might join that directory with every entry name:
for (const [entryName, contents] of Object.entries(entries)) {
const destination = join(extractionDirectory, entryName);
writeFileSync(destination, contents);
}
This looks well-scoped to the extractionDirectory, but path.join only builds and normalizes a path. An attacker can provide an entry like ../../app.log, and after normalization, the destination points to an app.log outside of the data/monthly-invoices directory!
This is called a Zip Slip, and the archive format is just the delivery mechanism, this kind of vulnerability can happen whenever you're not careful about untrusted path inputs.
Checking the uploaded archive's filename or MIME type does not help! The dangerous path lives inside the archive, and every entry can have a different attacker-controlled name.