CVE-2026-87910 Overview
CVE-2026-87910 is a path traversal vulnerability [CWE-22] in the Python standard library tarfile module. When tarfile extracts a link on a system that does not support links, it falls back to extracting a member from the archive. During this fallback path, the filter function is invoked twice: once for the extracted member and once with name set to the location of the link. The return value from one of the calls was ignored, allowing a crafted archive to bypass user-supplied filter policies.
Critical Impact
A malicious tar archive can write files to attacker-controlled paths outside the intended extraction root when link extraction falls back to member extraction, defeating the filter= protection introduced to prevent tar traversal attacks.
Affected Products
- CPython tarfile module (upstream Python)
- Applications using tarfile.extractall() or tarfile.extract() with a filter on platforms lacking link support
- Downstream distributions bundling vulnerable CPython builds
Discovery Timeline
- 2026-09-11 - CVE CVE-2026-87910 published to NVD
- 2026-09-16 - Last updated in NVD database
Technical Details for CVE-2026-87910
Vulnerability Analysis
The defect resides in Lib/tarfile.py within the makelink_with_filter code path. On systems without symbolic or hard link support, tarfile extracts the target member of the link as a regular file. To enforce the extraction filter (data, tar, or a user-defined callable), the filter function is called twice with different TarInfo views. The first call substitutes the link's destination name into the member; the second call runs against the unfiltered member itself.
The original code assigned only the second call's return value to filtered. When the first call returned None, indicating the filter wanted the member skipped, that decision was silently discarded. Extraction then proceeded using the member returned by the second call, writing content to a path the filter had already rejected.
Root Cause
The root cause is an ignored return value. The filter contract states that a None result means "skip this member," but the code path for link fallbacks only honored one of the two filter invocations. This is a logic flaw in trust boundary enforcement, not a memory safety issue.
Attack Vector
An attacker crafts a tar archive containing a link entry whose target member would be rejected by the caller's filter, such as a path outside the extraction root. When the archive is extracted on a system that does not support links, the fallback path invokes the filter for the link name, receives None, and discards that decision. The rejected member is written to disk. Exploitation requires the victim to extract the archive; user interaction is required and privileges are those of the extracting process.
"makelink_with_filter: if filter_function is not None, "
+ "extraction_root must also not be None")
try:
- filter_function(
+ filtered = filter_function(
unfiltered.replace(name=tarinfo.name, deep=False),
extraction_root)
+ if filtered is None:
+ return
filtered = filter_function(unfiltered, extraction_root)
except _FILTER_ERRORS as cause:
raise LinkFallbackError(tarinfo, unfiltered.name) from cause
Source: CPython commit fb2f0bb
Detection Methods for CVE-2026-87910
Indicators of Compromise
- Files written outside the intended extraction directory following a tarfile extraction operation.
- Tar archives containing LNKTYPE or SYMTYPE entries whose linked members resolve to absolute paths or .. traversals.
- Process telemetry showing python writing to sensitive directories after reading a .tar, .tar.gz, or .tgz file.
Detection Strategies
- Inspect archives before extraction using tarfile.TarFile.getmembers() and reject entries where linkname escapes the destination root.
- Enable Python audit hooks (sys.addaudithook) to log tarfile.chown, open, and write events during extraction.
- Scan build pipelines, package installers, and backup restore tooling for use of tarfile without an explicit filter='data' argument.
Monitoring Recommendations
- Alert on Python processes creating files outside expected working directories during archive handling workflows.
- Log and review any LinkFallbackError exceptions raised by internal tooling, since these indicate the vulnerable code path executed.
- Track CPython version deployment across build agents, container images, and endpoint fleets to identify unpatched interpreters.
How to Mitigate CVE-2026-87910
Immediate Actions Required
- Upgrade CPython to a version that includes commits fb2f0bb and d9565e5 from the CPython repository.
- Audit application code for calls to tarfile.extract() and tarfile.extractall() and confirm a restrictive filter is passed.
- Rebuild and redistribute container images, virtual environments, and packaged applications that embed the vulnerable interpreter.
Patch Information
The fix in CPython pull request 157266 assigns the result of the first filter_function call to filtered and returns early when the value is None. This ensures a rejection from either filter invocation aborts the member extraction. Reference the Python Security Announcement and the OpenWall oss-security thread for release details.
Workarounds
- Always pass filter='data' to extractall() when processing untrusted archives; validate that member paths remain within the destination root before extraction.
- Perform extraction inside a sandboxed directory with restrictive filesystem permissions or within an ephemeral container.
- Pre-scan archives and reject any containing link entries when running on platforms without link support.
# Verify installed Python includes the fix
python3 -c "import tarfile, sys; print(sys.version)"
# Safer extraction pattern for untrusted archives
python3 - <<'EOF'
import tarfile
with tarfile.open('untrusted.tar.gz') as tf:
tf.extractall(path='/tmp/sandbox', filter='data')
EOF
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.