Set archive extraction boundaries before unpacking
Review member paths, links, expansion limits, and cleanup for uploaded archives. Use a controlled scratch directory and small fixtures to verify containment.
Archive extraction turns uploaded metadata into filesystem operations. Review that transition explicitly. A compressed size limit does not bound the number of files, expanded bytes, or paths an extractor may create. Use a fresh scratch location with no customer data or application configuration.
Python's zipfile documentation warns about untrusted archives and distinguishes the behavior of extraction APIs. Check the exact library and method your application uses. Do not infer safety from the archive extension alone.
Define a member-admission policy
Inspect the archive's member list before publication and enforce limits during extraction. Declared metadata can guide a precheck, but actual writes and resource use still need bounds. Decide whether the product needs links, nested archives, or executable file permissions at all.
| Member or resource | Proposed decision | Controlled test |
|---|---|---|
| Path outside the scratch root | Reject | Harmless member with an escaping path |
| Symbolic or hard link | Reject unless explicitly supported | Link cannot redirect a later write |
| Excess member count | Stop within the configured budget | Small test-only count limit |
| Excess expanded bytes | Stop and remove partial output | Reduced expansion budget |
| Duplicate normalized path | Reject or apply a documented rule | No silent overwrite |
For a Python zipfile.Path implementation, do not rely on Path to sanitize member names; the Python documentation assigns that check to the caller. Make a tiny archive with docs/ok.txt, ../escape.txt, and two entries that normalize to the same output name. Precheck paths, types, duplicate names, count, and declared sizes. Enforce actual expanded-byte limits while streaming. In the escaping-path fixture, reject before writing and require the scratch root and its sibling to remain unchanged. On a later byte-limit failure, remove partial output. Compare resolved containment with the root, not a string prefix.
Keep extraction separate from acceptance
Do not serve a file merely because it was extracted successfully. Apply the application's normal type, ownership, and quarantine rules to accepted outputs. Keep the source archive identity linked to its derived files so a later rejection can remove the entire set.
Run test fixtures in a bounded worker. The test should demonstrate that containment fails before an outside write, not prove the issue by overwriting a real file. Include cancellation midway through extraction and a malformed archive that fails after creating partial output.
Inspect cleanup and error reporting
On rejection, remove the scratch output and release capacity reservations. Record a bounded reason and archive identifier without exposing internal paths or private filenames in public errors. Preserve only the evidence needed by the agreed review policy.
Retest after parser upgrades and platform changes, since path and link behavior can differ. Keep a valid small archive as the positive case. The final artifact is a containment-and-resource test record, with each member policy tied to the actual extractor version and the observed files left after success or failure.
Find the gaps before your next launch
Use the release checklist to identify changed security boundaries and missing evidence. Discuss the application paths you want reviewed before launch.
Sources
- Python documentation: zipfileAccessed