tarsafe
Secure drop-in replacement for Python's tarfile module that blocks directory-traversal and symlink attacks during extraction.
Repository Health
Technical Analysis
Tarsafe is a security-focused subclass of Python’s standard-library tarfile.TarFile that closes the classic tar-extraction directory-traversal hole (“tar slip”) without requiring any changes to calling code. It overrides extract() and extractall() to walk every archive member before writing anything, rejecting archives that contain absolute paths, .. traversal segments, or symlinks/hardlinks whose resolved target falls outside the extraction root, as well as archives containing character or block device members.
Because TarSafe is API-compatible with tarfile.TarFile — including exposing every name tarfile exports as a module-level alias — teams can migrate simply by swapping import tarfile for import tarsafe as tarfile, or by importing TarSafe directly, with no changes to the rest of the extraction logic. It remains actively maintained for the pre-3.12 Python versions that predate the standard library’s own PEP 706 extraction filters.
What You Get
- Drop-in
TarSafeclass overridingextract()/extractall()with pre-extraction path validation - Detection of absolute-path,
..-relative, and symlink/hardlink traversal attempts, anchored to the real extraction target rather than the process’s cwd - Rejection of character and block device archive members
- Full re-export of the standard
tarfilemodule’s public API for true drop-in compatibility
Common Use Cases
- Safely unpacking user-uploaded tar/tar.gz archives in web applications
- Extracting third-party or plugin archives distributed as tarballs
- Replacing unsafe
tarfile.extractall()calls in CI/build tooling that processes external artifacts - Hardening backup/restore utilities that extract archives from untrusted sources
Under The Hood
Architecture
Tarsafe is a single-class wrapper around the standard library: TarSafe subclasses tarfile.TarFile (in tarsafe/tarsafe.py) and overrides only extract() and extractall(), funneling both through one _safetar_check() method before delegating to the parent implementation. That method iterates every tarinfo member and runs four independent predicate checks — traversal, unsafe symlink, unsafe hardlink, and device-file — each raising a dedicated TarSafeException on violation. Path containment itself is factored into a single static _is_contained() helper built on os.path.commonpath(), so all three path-based checks share one correctness-critical primitive rather than duplicating comparison logic. A cheap _looks_suspicious() pre-filter (checking for a leading slash, .., or a Windows drive letter) gates the more expensive os.path.abspath()/commonpath() resolution, which keeps the check affordable on deeply nested archives. Given the narrow surface area, changing the core containment primitive would immediately affect every safety check in the module — there is no layering to absorb a change, which is appropriate for code this size and this security-sensitive.
Tech Stack
Pure Python with zero runtime dependencies — pyproject.toml declares only python = ">=3.6" under [tool.poetry.dependencies], with pytest ^6.1.1 as the sole dev dependency. Packaging supports a dual build path (poetry.masonry.api per pyproject.toml, plus a standalone setup.py for setuptools-based installs), and a GitHub Actions “Unit Tests” workflow runs the suite across Python 3.7 through 3.10 on every push and PR to master. No web framework, database, or external service integration is present or needed for a library this focused.
Code Quality
The test suite in test/test_tarsafe.py is disproportionately thorough for the codebase’s size: fixture-driven tests extract known-bad archives (test/data/bad/) and assert a TarSafeException is raised, plus parametrized regression tests for specific bypasses — a sibling-directory string-prefix escape, a multi-dot (.....) symlink-traversal claim, Windows-style absolute-path detection, and an explicit check that validation targets the real extraction path rather than the process’s cwd. Each regression test’s docstring documents the exact vulnerability it guards against, which is an unusually strong practice for a small library. Error handling is explicit and typed (a dedicated TarSafeException), naming is clear and consistent, and CI runs the suite across multiple Python versions — though there are no static type annotations or a linter/formatter configured.
API Design
The library’s entire value proposition is minimal-friction adoption: TarSafe.open() mirrors tarfile.TarFile.open()’s signature exactly, extract()/extractall() keep the same parameters as their parent, and the module re-exports every name in tarfile.__all__ alongside its own additions so existing from tarfile import *-style code needs no rewiring. A team can adopt tarsafe by changing a single import line, with no new concepts to learn beyond catching TarSafeException.