Skip to main content

exarch

PyPI Python CI License

Memory-safe archive extraction and creation library for Python.

Important: exarch is designed as a secure replacement for vulnerable archive libraries like Python's tarfile, which has known CVEs with CVSS scores up to 9.4.

This package provides Python bindings for exarch-core, a Rust library with built-in protection against common archive vulnerabilities.

Installation

pip install exarch

Tip: Use uv pip install exarch for faster installation.

Alternative Package Managers

# Poetry
poetry add exarch

# Pipenv
pipenv install exarch

Requirements

  • Python >= 3.10

Quick Start

Extraction

import exarch

result = exarch.extract_archive("archive.tar.gz", "/output/path")
print(f"Extracted {result.files_extracted} files")

Creation

import exarch

result = exarch.create_archive("backup.tar.gz", ["src/", "Cargo.toml"])
print(f"Created archive with {result.files_added} files")

Usage

Basic Extraction

import exarch

result = exarch.extract_archive("archive.tar.gz", "/output/path")

print(f"Files extracted: {result.files_extracted}")
print(f"Bytes written: {result.bytes_written}")
print(f"Duration: {result.duration_ms}ms")

With pathlib.Path

from pathlib import Path
import exarch

archive = Path("archive.tar.gz")
output = Path("/output/path")

result = exarch.extract_archive(archive, output)

Custom Security Configuration

import exarch

config = exarch.SecurityConfig()
config = config.with_max_file_size(100 * 1024 * 1024)  # 100 MB

result = exarch.extract_archive("archive.tar.gz", "/output", config)

Verifying Payload Integrity

verify_archive checks security only by default and reports integrity_status as "SKIPPED". Opt in to decode the payload and verify checksums:

import exarch

options = exarch.VerificationOptions().with_integrity_check()  # 500 MiB budget
report = exarch.verify_archive("archive.zip", options=options)

print(report.integrity_status)  # "OK", "FAILED", "INCOMPLETE" or "SKIPPED"

Note: "INCOMPLETE" means part of the payload was not verified (budget, resource limit, solid 7z archive while allow_solid_archives is off, TAR without an end-of-archive marker); it comes with a Medium issue, so status is at least "WARNING". With the check on, a TAR entry whose content fails to decode raises InvalidArchiveError.

Archives in Memory

list_archive_bytes, verify_archive_bytes and extract_archive_to_memory take the archive as bytes. The format is detected from the leading bytes, or stated with format ("Tar", "TarGz", "TarBz2", "TarXz", "TarZst", "Zip", "SevenZ"). Nothing is written to disk.

import exarch

data = open("archive.tar.gz", "rb").read()

manifest = exarch.list_archive_bytes(data)
report = exarch.verify_archive_bytes(data)

result = exarch.extract_archive_to_memory(data, max_bytes=64 * 1024 * 1024)
for entry in result.entries:
    print(entry.path, entry.kind)  # "file", "directory" or "symlink"
    if entry.kind == "file":
        content = entry.data  # bytes

Note: max_bytes caps payload plus path bytes plus 256 per entry and defaults to the config's max_total_size; exceeding it raises QuotaExceededError. path_model="portable" validates names against the strictest rules of every platform instead of this host's.

list_archive_bytes_with_progress, verify_archive_bytes_with_progress and extract_archive_to_memory_with_progress take a progress callback (path, total, current, bytes_written) after config; current counts from 1 and total is 0 for TAR. A raising callback does not abort the run; if the run succeeded its exception is raised (with progress_callback_error = True) and the result is discarded.

create_archive_to_bytes(sources, format) builds an archive in memory and returns a CreatedArchive with data and report:

created = exarch.create_archive_to_bytes(["src/"], "TarGz")
open("src.tar.gz", "wb").write(created.data)

Note: the whole archive is held in memory, up to about 2-3x its size at the peak, and there is no size cap. Sources must be regular files and directories; FIFOs and device files are not supported. Use create_archive for large archives.

Error Handling

import exarch

try:
    result = exarch.extract_archive("archive.tar.gz", "/output")
    print(f"Extracted {result.files_extracted} files")
except exarch.PathTraversalError as e:
    print(f"Blocked path traversal: {e}")
except exarch.ZipBombError as e:
    print(f"Zip bomb detected: {e}")
except exarch.SecurityViolationError as e:
    # e.relaxable_by names the SecurityConfig setting that would admit the entry, or None
    print(f"Security violation ({e.relaxable_by}): {e}")
except exarch.ArchiveError as e:
    print(f"Extraction failed: {e}")

API Reference

extract_archive(archive_path, output_dir, config=None)

Extract an archive to the specified directory with security validation.

Parameters:

Name Type Description
archive_path str | Path Path to the archive file
output_dir str | Path Directory where files will be extracted
config SecurityConfig Optional security configuration

Returns: ExtractionReport

Attribute Type Description
files_extracted int Number of files extracted
directories_created int Number of directories created
symlinks_created int Number of symlinks created
bytes_written int Total bytes written
duration_ms int Extraction duration in milliseconds
files_skipped int Number of files skipped (e.g. duplicates)
warnings list[str] Warning messages generated during extraction

Raises:

Exception Description
PathTraversalError Path traversal attempt detected
SymlinkEscapeError Symlink points outside extraction directory
HardlinkEscapeError Hardlink target outside extraction directory
ZipBombError Potential zip bomb detected
QuotaExceededError Resource quota exceeded
SecurityViolationError Security policy violation
UnsupportedFormatError Archive format not supported
UnknownFormatError Archive format cannot be determined from path or magic bytes (subclass of UnsupportedFormatError)
InvalidArchiveError Archive is corrupted
IOError I/O operation failed

Note: Since v0.4.0, create_archive raises FileNotFoundError for missing sources, FileExistsError when the output already exists without overwrite, and ValueError for invalid compression levels — matching standard Python conventions.

extract_archive_with_progress(archive_path, output_dir, config, progress)

Extract an archive with a progress callback. The GIL is held when a callback is provided and released otherwise.

Parameters:

Name Type Description
archive_path str | Path Path to the archive file
output_dir str | Path Directory where files will be extracted
config SecurityConfig | None Optional security configuration
progress Callable[[str, int, int, int], None] | None Optional progress callback: (path, total_files, current_file, bytes_written)
import exarch


def on_progress(path: str, total: int, current: int, bytes_written: int) -> None:
    print(f"[{current}/{total}] {path} ({bytes_written} bytes)")


result = exarch.extract_archive_with_progress(
    "archive.tar.gz", "/output", config=None, progress=on_progress
)

If progress raises: extraction is not aborted early — the progress-callback contract has no cancellation signal, so extraction always runs to completion first, and progress is not called again for the remaining entries once it has raised. If extraction otherwise succeeded, progress's own exception propagates unchanged, with files_extracted/bytes_written attributes describing what was written and a progress_callback_error = True marker attribute (check this marker before treating the presence of files_extracted as a partial-extraction signal, since a genuine partial-extraction failure carries the same two attribute names). If extraction also failed, the extraction error takes priority — a raising progress can never mask a security error such as SymlinkEscapeError — and progress's exception is attached as __cause__ instead of being dropped. create_archive_with_progress behaves the same way, using files_added in place of files_extracted.

SecurityConfig

Builder-style security configuration.

config = exarch.SecurityConfig()
config = config.with_max_file_size(100 * 1024 * 1024)  # 100 MB per file
config = config.with_max_total_size(1024 * 1024 * 1024)  # 1 GB total
config = config.with_max_file_count(10_000)  # Max 10k files
config = config.with_max_compression_ratio(50.0)  # Zip bomb threshold
config = config.add_allowed_extension(".txt")  # Extension allowlist
config = config.add_allowed_extension(".md")
config = config.add_banned_component("__MACOSX")  # Skip components
config = config.with_allow_solid_archives(True)  # Allow solid 7z archives
config = config.with_zip_consistency(
    "ignore"
)  # ZIP structure policy: "enforce" (default) or "ignore"
config = config.with_parallel_decode_memory(
    256 * 1024 * 1024
)  # 7z LZMA2 lookahead memory (default 128 MiB); None decodes on one thread
# Each extract/verify call may start up to 16 worker threads and hold its own
# lookahead, so concurrent calls multiply both.

Security Features

The library provides built-in protection against:

Protection Description
Path traversal Blocks ../ and absolute paths
Symlink attacks Prevents symlinks escaping extraction directory
Hardlink attacks Validates hardlink targets
Zip bombs Detects high compression ratios
TAR metadata bombs Bounds GNU long-name/long-link and PAX header record reads
Permission sanitization Strips setuid/setgid bits
Size limits Enforces file and total size limits

Caution: Unlike Python's standard tarfile module, exarch applies security validation by default.

Supported Formats

Format Extensions Extract Create List Verify
TAR .tar ✅ ✅ ✅ ✅
TAR+GZIP .tar.gz, .tgz ✅ ✅ ✅ ✅
TAR+BZIP2 .tar.bz2, .tbz2 ✅ ✅ ✅ ✅
TAR+XZ .tar.xz, .txz ✅ ✅ ✅ ✅
TAR+ZSTD .tar.zst, .tzst ✅ ✅ ✅ ✅
ZIP .zip ✅ ✅ ✅ ✅
ZIP-family .jar, .war, .ear, .nar, .nbm, .apk, .aab, .ipa, .appx, .msix, .whl, .vsix, .xpi, .epub ✅ — ✅ ✅
7z .7z ✅ — ✅ ✅

Note: ZIP-family formats share the ZIP container but add extra structure (signing, checksum manifests, ordering rules) that exarch doesn't produce, so creation is rejected for those extensions. 7z creation is not yet supported. Solid and encrypted 7z archives are rejected for security reasons. A 7z archive holding a Windows reparse point is refused as a whole; a Unix S_IFLNK symlink is read and validated through the symlink policy, so it is created only with symlinks allowed, and a 7z FIFO, socket or device node is refused.

Comparison with tarfile

# UNSAFE - tarfile has known vulnerabilities (CVE-2007-4559)
import tarfile

with tarfile.open("archive.tar.gz") as tar:
    tar.extractall("/output")  # May extract outside target directory!

# SAFE - exarch validates all paths
import exarch

exarch.extract_archive("archive.tar.gz", "/output")  # Protected by default

Development

This package is built using PyO3 and maturin.

# Clone repository
git clone https://github.com/bug-ops/exarch
cd exarch/crates/exarch-python

# Build with maturin
pip install maturin
maturin develop

# Run tests
pytest tests/

License

Licensed under either of:

at your option.

Metadata

Release files for exarch 0.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for exarch 0.8.0
File Actions
exarch-0.8.0-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details for exarch-0.8.0-cp39-abi3-win_amd64.whl
exarch-0.8.0-cp39-abi3-musllinux_1_2_x86_64.whl CPython 3.9 abi3 Linux musl 1.2+ x86-64 Details for exarch-0.8.0-cp39-abi3-musllinux_1_2_x86_64.whl
exarch-0.8.0-cp39-abi3-musllinux_1_2_aarch64.whl CPython 3.9 abi3 Linux musl 1.2+ ARM64 Details for exarch-0.8.0-cp39-abi3-musllinux_1_2_aarch64.whl
exarch-0.8.0-cp39-abi3-manylinux_2_34_x86_64.whl CPython 3.9 abi3 Linux glibc 2.34+ x86-64 Details for exarch-0.8.0-cp39-abi3-manylinux_2_34_x86_64.whl
exarch-0.8.0-cp39-abi3-manylinux_2_34_aarch64.whl CPython 3.9 abi3 Linux glibc 2.34+ ARM64 Details for exarch-0.8.0-cp39-abi3-manylinux_2_34_aarch64.whl
exarch-0.8.0-cp39-abi3-macosx_11_0_arm64.whl CPython 3.9 abi3 macOS 11.0+ ARM64 Details for exarch-0.8.0-cp39-abi3-macosx_11_0_arm64.whl
exarch-0.8.0-cp39-abi3-macosx_10_12_x86_64.whl CPython 3.9 abi3 macOS 10.12+ x86-64 Details for exarch-0.8.0-cp39-abi3-macosx_10_12_x86_64.whl

Total release size: 15.9 MB

Release files / exarch-0.8.0-cp39-abi3-win_amd64.whl

Download URL exarch-0.8.0-cp39-abi3-win_amd64.whl
Size 2.1 MB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
81af31b98bf0ccfa8b1a9dc48e8bccb6c088dbd584e8db062cbd1c087881f519
BLAKE2b-256 checksum
How to use checksums
fb32731554f58671f2b5b3e5ec80f0048e47f5bb53eb9dee52b78ff1dc3328c0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Release files / exarch-0.8.0-cp39-abi3-musllinux_1_2_x86_64.whl

Download URL exarch-0.8.0-cp39-abi3-musllinux_1_2_x86_64.whl
Size 2.6 MB
Tags CPython 3.9 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
c03ac37f7ff81b4ee15c22e0ab470b83c245cef6de8111d50644fb021c674ad2
BLAKE2b-256 checksum
How to use checksums
b40ac5651b0461d8a1f55087f1cf4f48ed150b81cff5e8dcb3e6fc3fb5d3b3e5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Release files / exarch-0.8.0-cp39-abi3-musllinux_1_2_aarch64.whl

Download URL exarch-0.8.0-cp39-abi3-musllinux_1_2_aarch64.whl
Size 2.5 MB
Tags CPython 3.9 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
1883973cf81386c13804e18296269d1da1b9db8b9f458b40d6392c2bd6021f2e
BLAKE2b-256 checksum
How to use checksums
b055870fc204f18b7bd6bc77740608a9f459c88b9003feb126cb883b2c8f8493
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Release files / exarch-0.8.0-cp39-abi3-manylinux_2_34_x86_64.whl

Download URL exarch-0.8.0-cp39-abi3-manylinux_2_34_x86_64.whl
Size 2.4 MB
Tags CPython 3.9 Linux glibc 2.34+ x86-64 abi3
SHA-256 checksum
How to use checksums
8b5828405b652e7f840d39e31e7e1a4206830b3c3e5e9ef44b247651dc3ed990
BLAKE2b-256 checksum
How to use checksums
27eec506b078a60a54833d2176789b2fd90aa3fdc8d2b134f1d6b86b6490a72c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Release files / exarch-0.8.0-cp39-abi3-manylinux_2_34_aarch64.whl

Download URL exarch-0.8.0-cp39-abi3-manylinux_2_34_aarch64.whl
Size 2.3 MB
Tags CPython 3.9 Linux glibc 2.34+ ARM64 abi3
SHA-256 checksum
How to use checksums
83318a291fe929175799e3587fd64e4150d3fab6c932d74a1de9a72b810de0d2
BLAKE2b-256 checksum
How to use checksums
db897e88c441e6715f8c7585e73aca805c5b6578b1485b4b48f037b5c98fb245
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Release files / exarch-0.8.0-cp39-abi3-macosx_11_0_arm64.whl

Download URL exarch-0.8.0-cp39-abi3-macosx_11_0_arm64.whl
Size 2.0 MB
Tags CPython 3.9 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
43b59ef6dacc713e787e3db966b7d1cc39cd59249060eaa38d47d8a7ad5e8f41
BLAKE2b-256 checksum
How to use checksums
88c79760b7babf1eda982809ad28de8badbb67f99858436cba76fa4338083092
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Release files / exarch-0.8.0-cp39-abi3-macosx_10_12_x86_64.whl

Download URL exarch-0.8.0-cp39-abi3-macosx_10_12_x86_64.whl
Size 2.1 MB
Tags CPython 3.9 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
f8f3b3d6082cf7822083a256b9c552a66169f8fdb29eb4f86238d3c50d812ef9
BLAKE2b-256 checksum
How to use checksums
ff170e68bedab5f4c6ba3ccf6ba4cb83a35c462c962a6b5cbedb348e1dbb28d3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Security for exarch 0.8.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 7 files in this release have been uploaded with provenance metadata.

Select from 7 files

Attestation for exarch-0.8.0-cp39-abi3-win_amd64.whl

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page