Archive extraction looks like a file operation until an archive contains a member named ../../etc/file, an absolute path, or a symbolic link that changes where the next member is written. The resulting problem is commonly called Zip Slip, but the same class of failure appears in tar files and custom archive formats.

Checking whether a filename contains .. is not a complete defense. A member can be a symlink, a hardlink, or a path that resolves differently because the destination directory contains an existing link. Two archive members can interact: the first creates a link and the second writes through it. A live directory where another process can create or replace paths adds another race.

Validate the object and the destination

Applications should use extraction filters or safe extraction APIs provided by the language or archive library. Python's tarfile documentation explicitly warns that extraction can overwrite arbitrary files through absolute paths, parent components, or symlinks, and provides filters that refuse links outside the destination and limit extracted size and file counts. Use the filter that matches the application's needs instead of writing a regular-expression check around names.

Create a new destination directory for the extraction. Do not extract into /tmp, a shared upload directory, or a directory controlled by another user. Check the resolved destination after every link operation, constrain the archive's expanded size, and reject unsupported member types. For a service that processes untrusted archives, run extraction in a sandbox with only the input and output directories mounted.

The final path must be derived from a trusted root and the archive member's resolved location. A string that starts with the expected prefix after simple textual normalization can still point through a symlink. Use a path API that resolves the final object, or keep the extraction root inaccessible to other processes while it is active.

Test the failure modes

Build a small archive containing ../escape, an absolute path, a symlink to the parent directory, and a hardlink to a file outside the root. Confirm that each member is rejected before it changes the filesystem. Repeat the test when the destination already contains a symlink. Add a size-limit test and a member-count test.

Review the extraction library version and its default filter. A default can change between language releases, and an application may override it. Record the chosen policy in code and in the deployment image. If the application extracts only one known format from a trusted publisher, state that assumption and verify the publisher's signing path; do not use it to excuse an unsafe library default.

A safe extractor proves two things: the archive cannot write outside the chosen root, and it cannot create a link that a later member can use to escape. Testing only the filename is not enough for either claim.

References