Python 3.14.6 fixes tar extraction filter bypasses
The June maintenance release corrects path checks in tarfile's data filter. The fixes show why validating an archive requires the filesystem state and the exact name extraction will use.
A path check can approve one string while extraction writes a different interpretation of it. Python 3.14.6, released on June 10, corrects several cases of that problem in tarfile.data_filter(). The release changelog says the fixes close path traversal bypasses in the data extraction filter.
The filter now validates link targets using the normalized value that will be written to disk. It also strips trailing separators from a member name when resolving a symlink's directory and rejects link members that would replace the destination directory itself. These are changes to the relationship between validation and execution. The intended restriction already existed, but some paths through extraction did not enforce it correctly.
I like Python's ability to put a substantial library operation behind a small interface. Archive extraction is also a reminder of what that convenience delegates. The caller asks to unpack files into a directory. The library has to account for the archive's ability to change what later names mean within that directory.
Tar archives describe filesystem operations
PEP 706, which introduced extraction filters, starts with a conflict between use cases. A workstation backup may need to preserve permissions, special files, and symlinks. A portable data bundle generally does not need that authority. Supporting both through the same permissive operation made an innocent-looking extraction call difficult to evaluate.
The proposal therefore defines filters around intended use. fully_trusted preserves the archive's metadata. The tar filter applies restrictions suited to Unix-style archives. The more restrictive data filter discards or rejects additional behavior that is inappropriate for a general data bundle.
In particular, the data policy refuses absolute link targets and links that lead outside the extraction destination. It rejects device files, including pipes, and ignores ownership information. Those restrictions express a concrete contract about what the archive may create. The filter does not merely ask whether a filename looks suspicious in isolation.
The default transition was deliberate. PEP 706 specified a deprecation period before Python 3.14 would use the data filter by default. The June fixes therefore repair an important default behavior on the supported branch. Applications that explicitly request fully_trusted have chosen a different policy and cannot infer protection from a correction to the data filter.
Validation happens during extraction
The proposal explains why inspecting all archive members in advance is difficult. Earlier members can create symlinks that affect the interpretation of later paths. Correct preflight analysis would need to model those filesystem changes. Reading every member in advance also imposes costs and requires an archive that supports seeking.
Instead, the extraction loop calls the filter immediately before processing each member. The filter receives that member's metadata and the destination path. It can inspect the current state of the filesystem, return modified metadata, skip a member, or raise an exception. That places the decision near the operation whose effects it constrains.
The June normalization fix follows the same design requirement at a smaller scale. Checking a link target is meaningful only if the check evaluates the target extraction will actually use. If normalization changes its interpretation after approval, the check and the write no longer refer to the same operation. The changelog identifies that mismatch as one of the repaired bypasses.
Trailing separators and the destination directory itself are related boundary cases. Resolving the directory of a symlink must use the intended member name, and the extraction root must remain the root against which later operations are constrained. A library implementation has to account for these details even when the application never explicitly creates a symlink.
An explicit policy remains useful
The named filter keeps the intended use visible in application code. This call selects the data policy described by PEP 706.
import tarfile
with tarfile.open("bundle.tar") as archive:
archive.extractall(path="unpacked", filter="data")
The argument states the policy, while the installed interpreter supplies its implementation. Consequently, the explicit argument does not substitute for the patch. A corrected implementation is necessary for the named policy to enforce the repaired cases.
Extraction can also stop after writing some members. PEP 706 makes cleanup the caller's responsibility when an exception aborts the operation. The archive operation is therefore not an all-or-nothing transaction. A partially populated directory must not automatically become an application's accepted input just because some files appeared there.
The proposal also leaves resource limits outside the filter's guarantee. A data archive can consume excessive disk space or contain too many files without relying on a path traversal trick. The filter restricts filesystem behavior, while the calling application still needs a definition of acceptable data volume for its own use case.
Python's standard-library coverage remains a major reason I use the language despite its packaging complications. This release gives that dependency a specific operational meaning. Programs using the data filter receive corrected link validation through the interpreter update. Their extraction policy stays the same, and the implementation now checks the normalized target that it will use.