Skip to content

Repository files navigation

exarch

CIcodecovcrates.iodocs.rsPyPInpmLicenseMSRV

Memory-safe archive extraction and creation library with Python and Node.js bindings.

Important

exarch is designed as a secure replacement for vulnerable archive libraries like Python's tarfile and Node.js's tar-fs, which have known CVEs with CVSS scores up to 9.4.

Features

  • Extract, create, list, and verify archives — Full support for TAR and ZIP (all operations), plus 7z extraction, listing, and verification
  • Format auto-detection — Falls back to magic-byte inspection when the file extension is absent, unrecognised, or contradicts the file content
  • Security-first design — Default-deny security model with protection against path traversal, symlink attacks, zip bombs, and more
  • Type-driven safety — Rust's type system ensures validated paths can only be constructed through security checks
  • Extension allowlists — Optional allowed_extensions filter restricts extraction to a specific set of file extensions across TAR, ZIP, and 7z handlers
  • Fluent configuration — 15 with_* builder methods on SecurityConfig and 2 on ExtractionOptions for ergonomic setup
  • Multi-language support — Native bindings for Python (PyO3) and Node.js (napi-rs)
  • Zero unsafe code — Core library contains no unsafe Rust code
  • High performance — Optimized I/O with reusable buffers and streaming operations

Installation

Rust

[dependencies]
exarch-core = "0.6"

Important

Requires Rust 1.96.0 or later (Edition 2024).

Python

pip install exarch

Note

Requires Python 3.10 or later.

Node.js

npm install exarch-rs

Note

Requires Node.js 20 or later.

Quick Start

Extraction

Rust

use exarch_core::{extract_archive,SecurityConfig};fnmain() -> Result<(), exarch_core::ArchiveError>{let config = SecurityConfig::default();let report = extract_archive("archive.tar.gz","/output/path",&config)?;println!("Extracted {} files ({} bytes, {} skipped)",
report.files_extracted,
report.bytes_written,
report.files_skipped);Ok(())}

Python

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

Node.js

const{ extractArchive }=require('exarch-rs');// Async (recommended)constresult=awaitextractArchive('archive.tar.gz','/output/path');console.log(`Extracted ${result.filesExtracted} files`);

Creation

Rust

use exarch_core::{create_archive,CreationConfig};fnmain() -> Result<(), exarch_core::ArchiveError>{let config = CreationConfig::default();let report = create_archive("output.tar.gz",&["src/","Cargo.toml"],&config)?;println!("Created archive with {} files ({} bytes)",
report.files_added,
report.bytes_written);Ok(())}

Python

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

Node.js

const{ createArchive }=require('exarch-rs');// Async (recommended)constresult=awaitcreateArchive('output.tar.gz',['src/','package.json']);console.log(`Created archive with ${result.filesAdded} files`);

Security

exarch provides defense-in-depth protection against common archive vulnerabilities:

ProtectionDescriptionDefault
Path traversalBlocks ../ and absolute pathsEnabled
Symlink attacksPrevents symlinks escaping extraction directoryBlocked
Hardlink attacksValidates hardlink targets within extraction directoryBlocked
Zip bombsDetects high compression ratiosEnabled (100x limit)
TAR metadata bombsBounds GNU long-name/long-link and PAX header record readsEnabled (4 MiB, 16 MiB for permissive())
Permission sanitizationStrips setuid/setgid bitsEnabled
Size limitsConfigurable file and total size limits50 MB / 10 GB

Caution

Enabling symlinks or hardlinks should only be done when you fully trust the archive source.

CLI: Strict Verification

The verify command accepts --strict to treat warnings as errors:

# Exit 0 — only hard failures are errors
exarch verify archive.tar.gz
# Exit 2 if any warnings are found (e.g. setuid bits), exit 1 on failure
exarch verify --strict archive.tar.gz

Security Configuration

use exarch_core::SecurityConfig;let config = SecurityConfig::default().with_max_file_size(100*1024*1024)// 100 MB.with_max_total_size(1024*1024*1024)// 1 GB.with_max_compression_ratio(50.0)// 50x compression limit.with_allowed_extensions(vec![".tar".into(),".gz".into()]);// optional allowlist

Important

SecurityConfig, AllowedFeatures, and ExtractionOptions are #[non_exhaustive] since v0.4.0. Use Default::default() plus the fluent with_* builder methods instead of struct literal syntax.

Supported Formats

FormatExtensionsExtractCreateListVerifyCompression
TAR.tarNone
TAR+GZIP.tar.gz, .tgzgzip
TAR+BZIP2.tar.bz2, .tbz2bzip2
TAR+XZ.tar.xz, .txzxz/lzma
TAR+ZSTD.tar.zst, .tzstzstandard
ZIP.zipdeflate, deflate64, bzip2, zstd
ZIP-family.jar, .war, .ear, .nar, .nbm, .apk, .aab, .ipa, .appx, .msix, .whl, .vsix, .xpi, .epubdeflate, deflate64, bzip2, zstd
7z.7zlzma, lzma2

Note

ZIP-family formats share the ZIP container but add extra structure - signing (.apk/.aab/.ipa/.appx/.msix), checksum manifests (.whl), ordering rules (.epub), or descriptor files (.war/.ear/.vsix/.nbm) - which exarch doesn't produce. Creation is rejected to avoid silently emitting a bare ZIP with a misleading extension; callers who want that can set CreationConfig::format = Some(exarch_core::formats::detect::ArchiveType::Zip) explicitly.

Note

7z creation is not yet supported. Solid and encrypted 7z archives are rejected for security reasons. Unix symlinks inside 7z archives are reported as regular files (sevenz-rust2 API limitation).

Project Structure

exarch/
├── crates/
│ ├── exarch-core/ # Core Rust library
│ ├── exarch-cli/ # Command-line utility
│ ├── exarch-python/ # Python bindings (PyO3)
│ └── exarch-node/ # Node.js bindings (napi-rs)
├── benches/ # Criterion benchmarks
├── examples/ # Usage examples
└── tests/ # Integration tests

Performance

exarch uses optimized I/O with directory caching and atomic permission setting to outperform native archive libraries:

ComparisonAverage SpeedupMax Speedup
vs Python tarfile/zipfile1.10x faster1.43x
vs Node.js tar/adm-zip1.75x faster4.69x

Throughput (100MB archives)

FormatThroughputvs Target
TAR extraction2,136 MB/s4x target (500 MB/s)
ZIP extraction1,444 MB/s5x target (300 MB/s)
Path validation~85 ns12x better than 1 µs target

Tip

Run ./benches/run_all.sh to benchmark on your hardware. See benches/README.md for details.

Development

Requirements

  • Rust 1.96.0 or later (Edition 2024)
  • Python 3.10+ (for Python bindings)
  • Node.js 20+ (for Node.js bindings)

Build

cargo build --workspace

Test

cargo nextest run --workspace

Pre-commit Checks

cargo +nightly fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --workspace
cargo deny check

Tip

Run all checks before committing to ensure CI passes.

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting PRs.

License

Licensed under either of:

at your option.

About

Secure archive library: TAR/ZIP/7z extraction & creation with CVE protection. Type-safe Rust core, Python/Node.js bindings, zero unsafe code.

Topics

Resources

Contributing

Security policy

Stars

5 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages