
Documents CVE-2026-65320, a tar-slip path traversal in fastcore's untar_dir(), with a harmless proof-of-concept demonstrating arbitrary file write and code execution.
untar_dir()Severity: Critical, CVSS 3.1 9.8
Vector (v3.1): CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Affected: fastcore <= 2.2.30
Fixed in: Not fixed at time of writing
CWE: CWE-22 (Improper Limitation of a Pathname to a Restricted Directory, 'Path Traversal')
Component: fastcore.xtras.untar_dir() → fastcore.xtras._unpack() → shutil.unpack_archive()
High-risk runtime: Python < 3.14 (Python 3.14 makes the safe data tar filter the default)
Reported by: Rahul Karne
CNA: VulnCheck
fastcore.xtras.untar_dir(fname, dest) is a documented helper that untars an archive "into dest".
Internally, it hands the archive to the standard library's shutil.unpack_archive() without an extraction filter, without validating archive member paths, and without any post-extraction containment check.
As a result, a TAR archive whose members contain ../ traversal sequences or absolute paths can write files outside the directory the caller asked fastcore to extract into.
A caller who reasonably expects untar_dir(fname, dest) to confine everything to dest instead gets arbitrary file write anywhere the process can write.
Arbitrary file write under the authority of the process calling untar_dir().
Depending on what is overwritten, this can result in:
The proof of concept demonstrates the full write → import → code-execution chain end to end, entirely inside a harmless demo directory.
fastcore is a foundational dependency across the fast.ai ecosystem, including:
fastainbdevghapiIt receives tens of millions of downloads per month.
untar_dir() is the extraction primitive behind the common "download an archive, then unpack it" workflow, which follows the same general pattern as fastai's untar_data.
Any code path that feeds untar_dir() an archive whose contents are not fully trusted is exposed.
The vulnerable code exists in fastcore/xtras.py:
def _unpack(fname, out):
import shutil
shutil.unpack_archive(str(fname), str(out)) # <-- no filter, no validation
ls = out.ls()
return ls[0] if len(ls) == 1 else out
def untar_dir(fname, dest, rename=False, overwrite=False, uid=-1, gid=-1):
"untar `file` into `dest` ..."
import tempfile, shutil
dest = Path(dest)
with tempfile.TemporaryDirectory() as d:
out = Path(d) / remove_suffix(Path(fname).stem, '.tar')
out.mkdir()
...
src = _unpack(fname, out) # extraction escapes `out` here
...
shutil.move(str(src), dest)
...
return dest
untar_dir() creates an internal temporary extraction directory:
<tempdir>/<random>/<archive-stem>/
It then calls _unpack(), which forwards the archive directly to:
shutil.unpack_archive()
For TAR archives, this ultimately calls tarfile.extractall().
On Python < 3.14, the default extraction behavior is fully_trusted, meaning traversal members can be honored.
For example, an archive member such as:
../../../some/other/dir/file
is written relative to the extraction directory and can therefore escape it.
Nothing in fastcore:
filter="data" where supported... path components.untar_dir() treats shutil.unpack_archive() as though it were a safe, sandboxed archive extractor.
It is not.
Python's standard library documentation warns that extracting untrusted archives may create files outside the requested destination and recommends safe extraction filtering for TAR archives.
fastcore neither passes an appropriate extraction filter nor implements an equivalent containment guard for the Python versions it supports.
It also does not warn callers of untar_dir() about this security-sensitive behavior.
TAR members can contain arbitrary path strings.
On affected Python versions, tarfile.extractall() does not automatically neutralize .. traversal sequences by default.
Writing a traversal member relative to fastcore's temporary extraction directory is therefore sufficient to escape that directory and write anywhere the process has permission to write.
A reliable primitive is relative traversal such as:
../../../target/file
This is the technique used by the proof of concept.
The dangerous input is the archive contents, not the destination path chosen by the developer.
Archives are frequently obtained from external or semi-trusted locations, including:
Any service or tool that downloads an archive from an untrusted or semi-trusted source and subsequently processes it using untar_dir() may therefore be reachable.
The PoC also includes a variant that serves the crafted archive from an attacker-controlled HTTP endpoint to demonstrate this delivery path.
Successful exploitation requires:
The application calls fastcore.xtras.untar_dir() on an archive whose contents are attacker-influenced, such as an uploaded, downloaded, or otherwise untrusted archive.
The runtime uses Python < 3.14, or another configuration where a safe TAR extraction filter is not enforced.
For escalation from arbitrary file write to code execution, the process later imports or loads a file from a location reachable by the traversal, such as:
Arbitrary file write and configuration/data modification require only conditions 1 and 2.
poc_fastcore_cna_demo.py invokes the real fastcore.xtras.untar_dir() API.
The demonstration is intentionally harmless.
Every file created by the PoC remains inside a single demo directory specified using:
--demo-root
The PoC does not modify real system files and does not access cloud services or credentials.
Prints the environment details and vulnerable source code for _unpack() and untar_dir().
Creates a demonstration layout containing:
service_app/plugins/
Creates:
attacker_supplied_upload.tar
containing archive members designed to point outside the intended extraction root, including traversal members and:
service_app/plugins/startup_hook.py
def auto_extract_uploaded_archive(received_archive):
return untar_dir(
received_archive,
intended_root,
overwrite=True
)
Verifies that files were written outside the intended extraction directory.
Simulates a service reload by importing the archive-written:
startup_hook.py
The harmless startup hook creates:
SERVICE_RELOAD_MARKER
This demonstrates the complete chain:
Attacker-controlled archive
↓
Path traversal during extraction
↓
Arbitrary file write
↓
Write into importable / auto-loaded location
↓
Application loads attacker-controlled file
↓
Code execution
Install the affected package:
pip install fastcore
The issue has been confirmed through:
fastcore 2.2.30
The demonstration can run against either:
fastcore--sourceAll PoC writes remain inside the directory specified through --demo-root.
Clean any previous PoC output:
python poc_fastcore_cna_demo.py --demo-root ./poc_fastcore --clean
Run the demonstration:
python poc_fastcore_cna_demo.py \
--demo-root ./poc_fastcore \
--source latest \
--no-pause
Example abridged output:
fastcore version: 2.2.30
Outside marker exists after extraction? True
Is outside marker inside intended extraction root? False
Relative traversal marker exists after extraction? True
Is relative traversal marker inside intended extraction root? False
Service plugin file exists after extraction? True
Service reload marker exists? True
POC WORKED: fastcore extracted attacker-controlled tar members outside the intended root.
On supported Python versions, use a safe extraction filter such as:
filter="data"
with shutil.unpack_archive() or the corresponding tarfile extraction operation.
Every archive member should be checked before extraction.
Reject:
..Link targets should also be resolved and validated before extraction.
Resolve every destination path before writing and verify that it remains beneath the intended extraction root.
Conceptually:
resolved_member_path
↓
must remain inside
↓
resolved_extraction_root
Extraction should fail if the resolved destination escapes the extraction root.
Regression tests should cover at least:
../ traversal
absolute paths
Windows drive-letter paths
UNC paths
symbolic links
hard links
Clearly document whether untar_dir() is intended to safely process untrusted archives.
If unsafe extraction is intentionally supported, the secure behavior should still be the default.
Unsafe behavior should require explicit opt-in rather than silently trusting archive contents.
Until a package-level fix is released:
untar_dir().The underlying technical defect is an arbitrary file-write vulnerability caused by archive path traversal.
However, the final severity depends on the deployment environment and how untar_dir() is exposed.
A representative vector may approach:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Result:
9.8 — Critical
If exploitation instead requires a developer to manually download and extract an archive, user interaction and attack complexity may be higher.
The resulting CVSS score would therefore be materially lower.
Two important factors should be considered when evaluating the issue.
This is an instance of the long-known unsafe TAR extraction vulnerability class associated with:
CVE-2007-4559
Modern Python releases have introduced safer archive extraction behavior.
Python 3.14 makes the safe data extraction filter the default.
Therefore, the primary affected environment is Python < 3.14.
However, those runtimes remain widely deployed.
The vulnerability becomes exploitable when a caller supplies untar_dir() with an archive whose contents are attacker-controlled or otherwise untrusted.
This report therefore treats the contents of the archive as the untrusted security boundary.
A robust archive extraction helper should fail safely regardless of how the archive was obtained.
Replace the placeholder dates below with the actual disclosure dates.
YYYY-MM-DD — Vulnerability reported to the maintainers.YYYY-MM-DD — Vendor response / acknowledgment / no response.2.2.30); extraction code remained unchanged.Discovered and reported by Rahul Karne.
fastcore untar_dir() documentation
https://fastcore.fast.ai/xtras.html
fastcore xtras.py source
https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xtras.py
fastcore on PyPI
https://pypi.org/project/fastcore/
Python shutil.unpack_archive() documentation
https://docs.python.org/3/library/shutil.html#shutil.unpack_archive
Python tarfile extraction filters
https://docs.python.org/3/library/tarfile.html#extraction-filters
PEP 706 — Filter for tarfile.extractall
https://peps.python.org/pep-0706/
CWE-22 — Improper Limitation of a Pathname to a Restricted Directory
https://cwe.mitre.org/data/definitions/22.html
CVE-2007-4559 — Unsafe TAR extraction vulnerability class
https://nvd.nist.gov/vuln/detail/CVE-2007-4559
This repository documents a coordinated-disclosure security finding and provides a harmless, self-contained proof of concept.
The PoC writes only inside the --demo-root directory specified by the user and performs no destructive actions.
Any network functionality included in the demonstration is limited to an optional local demonstration.
This material is provided for defensive security research and educational purposes.
Media inquiries: [email protected]. Full PoC (attacker server, traversal archive, victim application) and additional technical detail available on request.