Skip to content

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 resourceProposed decisionControlled test
Path outside the scratch rootRejectHarmless member with an escaping path
Symbolic or hard linkReject unless explicitly supportedLink cannot redirect a later write
Excess member countStop within the configured budgetSmall test-only count limit
Excess expanded bytesStop and remove partial outputReduced expansion budget
Duplicate normalized pathReject or apply a documented ruleNo 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

  1. Python documentation: zipfileAccessed