archmeld 1.3.0

Secure, memory-safe, type-safe CLI for multi-format archive extraction, inspection and decompression
Documentation

archmeld

Secure, memory-safe, type-safe CLI for multi-format archive extraction, inspection and decompression.

Built entirely in safe Rust (#![forbid(unsafe_code)]) with zero tolerance for memory safety issues.

Features

Unified archive extraction (inspired by secana/archive): ZIP, TAR (.gz/.bz2/.xz/.zst/.lz4), 7-Zip, and single-file compression formats — all through one interface with built-in zip-bomb protection and path-traversal prevention.

LZ4 compress/decompress (inspired by traceflight/lz4_decompress): Pure-Rust LZ4 frame and block format support via lz4_flex. Compress, decompress, and inspect LZ4 frame headers.

StuffIt archive inspection (inspired by sit-rs): Parse and display classic StuffIt (.sit) and StuffIt 5.x archive headers, entry metadata, compression methods, and encryption flags.

Compact Pro archive inspection (inspired by cyco/cpt-rs): Parse Compact Pro (.cpt) archive headers, file/directory entries, CRC-32 verification, LZH compression flags, and Mac OS type/creator codes.

XAR/PKG archive support (powered by xara): Extract, list, and verify XAR archives and macOS PKG installer packages. Pure Rust.

DDS texture inspection (powered by image_dds): Detect and inspect DirectDraw Surface (.dds) texture files — dimensions, pixel format, mip levels, array layers.

Gzip deep inspection (inspired by jt55401/gzinspector): Full RFC 1952 header parsing, flag decoding, OS identification, CRC-32 verification, multi-member detection, SHA-256 fingerprinting. Chunked member analysis via gzinspector.

Installation

⚠️ Security notice — crates.io vs. the GitLab release

The prebuilt binaries in the GitLab release are not affected. The crates.io package is.

archmeld pins xara and gzinspector to vendored, already-fixed copies via [patch.crates-io]. Cargo strips that section when a crate is published, so the manifest on crates.io still requires xara = "0.3" — which resolves to quick-xml 0.37.5 and its two 7.5 HIGH advisories (RUSTSEC-2026-0194, RUSTSEC-2026-0195) — and gzinspector = "0.2", which pulls the unmaintained number_prefix (RUSTSEC-2025-0119).

cargo install archmeld therefore builds the vulnerable quick-xml. Prefer the GitLab release artefacts until upstream xara moves to quick-xml >= 0.41, or apply the same patch in your own workspace.

From source

cargo install --path .

Pre-built binaries

Download from the Releases page:

Platform Binary
Linux x86_64 archmeld-1.2.3-x86_64-unknown-linux-gnu
Linux aarch64 archmeld-1.2.3-aarch64-unknown-linux-gnu
Windows x86_64 archmeld-1.2.3-x86_64-pc-windows-gnu.exe
macOS x86_64 archmeld-1.2.3-x86_64-apple-darwin
macOS aarch64 archmeld-1.2.3-aarch64-apple-darwin

Usage

archmeld <COMMAND>

Commands:
  extract       Extract files from an archive
  list          List contents of an archive
  info          Detect and display format information
  gz-inspect    Inspect gzip file headers and metadata
  lz4           LZ4 compression and decompression
  sit-inspect   Inspect StuffIt (.sit) archive
  cpt-inspect   Inspect Compact Pro (.cpt) archive
  verify        Verify archive integrity

Update options (always available):
  --check-update      Report whether a newer release
                      exists, then exit
  --self-update       Download and install the latest
                      release, then exit
  --no-self-update    Refuse --self-update
                      (env: ARCHMELD_NO_SELF_UPDATE)

Self-update

archmeld can replace its own binary in place. This capability is compiled into every build — there is no feature flag that adds it, only a runtime flag that refuses it:

# Is there a newer release?  Exits 10 if yes, 0 if not.
archmeld --check-update

# Install it.
archmeld --self-update

# Refuse, for package-managed or air-gapped installs.
archmeld --no-self-update --self-update
export ARCHMELD_NO_SELF_UPDATE=1

Release-asset checksums are verified before the running binary is replaced. Exit codes are a stable contract, so wrapper scripts can branch on them without parsing text:

Code Meaning
0 Up to date, updated, or host unreachable
10 --check-update found a newer release
3 --self-update refused by policy
1 Update failed (no asset, bad checksum, …)
2 Argument parse error

An unreachable update host is deliberately not a failure — a checker that failed closed would break every wrapper script the moment the release host had an outage. The CLI flag always wins over the environment variable.

Examples

# Detect format by magic bytes
archmeld info backup.tar.gz

# List archive contents
archmeld list backup.zip
archmeld list --json backup.tar.xz

# Extract with safety limits
archmeld extract backup.zip -o ./output/
archmeld extract backup.tar.zst -o ./output/ \
  --max-file-size 50 --max-total-size 500

# Extract solid 7z archives
archmeld extract solid.7z -o ./output/ \
  --allow-solid-archives

# Allow world-writable permissions (Windows
# archives)
archmeld extract windows.tar -o ./output/ \
  --allow-world-writable

# Inspect gzip headers (RFC 1952)
archmeld gz-inspect data.gz
archmeld gz-inspect --verify --json data.gz

# LZ4 operations
archmeld lz4 compress bigfile.bin -o bigfile.lz4
archmeld lz4 decompress bigfile.lz4
archmeld lz4 inspect bigfile.lz4

# Inspect legacy Mac archives
archmeld sit-inspect archive.sit
archmeld cpt-inspect --verify archive.cpt

# XAR / macOS PKG archives
archmeld extract installer.pkg -o ./pkg-contents/
archmeld list archive.xar
archmeld verify archive.xar

# DDS texture inspection
archmeld info texture.dds

# Gzip chunked member analysis
archmeld gz-inspect --chunks multi.gz

# Verify integrity
archmeld verify backup.tar.gz

Batch Extraction

The script example/extract-all.sh recursively extracts all supported archives from a source directory. It detects each file's format, skips non-extractable types (DDS, RAR, ARC, ZOO, StuffIt, Compact Pro), and extracts all remaining files into unique output directories.

# 1. Build the release binary
cargo build --release

# 2. Run the batch extraction script
./example/extract-all.sh

The script uses the following defaults:

Setting Value
Source directory example/
Output directory test/
Max total size 10 GiB
Max file size 1 GiB

Output example:

=== EXTRACTION RESULTS ===
Extractable tried: 2132
Successful:        2098
Failed:            34
Skipped:           678
Extraction rate:   98%

Edit SRC, DST, or the --max-* flags inside the script to adjust paths and size limits.

Supported Formats

Format Extract List Inspect Verify
ZIP
TAR
TAR.GZ
TAR.BZ2
TAR.XZ
TAR.ZSTD
TAR.LZ4
7-Zip
Gzip
Bzip2
XZ
LZ4
Zstandard
LZMA
LHA/LZH
XAR/PKG
DDS
RAR
ARC
ZOO
StuffIt
Compact Pro

Security

  • #![forbid(unsafe_code)] — no unsafe Rust anywhere in archmeld
  • Zip-bomb protection — configurable per-file and total extraction size limits (default: 100 MiB / 1 GiB)
  • Path traversal prevention — all archive paths are sanitized; ../ components are rejected
  • Clippy pedantic — full pedantic lint set enabled
  • cargo audit — continuous dependency vulnerability scanning
  • cargo deny — license compliance, dependency bans, supply-chain checks

Hardened builds

archmeld parses attacker-supplied bytes by design, so the binary itself is hardened:

  • mimalloc in secure mode as the global allocator — guard pages between blocks, encoded free-list pointers, and randomised placement, which turns several classes of heap corruption into a crash rather than an exploit
  • overflow-checks = true in release — sizes and offsets come from attacker-controlled headers, where a silent wraparound is exactly how an extraction limit gets bypassed
  • Full RELRO, non-executable stack and PIE on Linux targets (.cargo/config.toml). Not applied on macOS, where those linker options do not exist and PIE/NX are already the default

Verify a Linux build with:

checksec --file=target/release/archmeld
readelf -lWd target/release/archmeld \
  | grep -E 'GNU_RELRO|BIND_NOW|GNU_STACK'

Dependency patches

vendor/xara/ holds a patched copy of xara 0.3.2 that raises its quick-xml requirement from 0.37 to 0.41, closing RUSTSEC-2026-0195 and RUSTSEC-2026-0194 (both 7.5 HIGH) on the XAR / PKG parsing path. Upstream pins quick-xml = "0.37", so the resolver refuses the bump and a [patch.crates-io] redirect is the only route. Remove it once upstream ships a fix — both manifests carry that instruction. Full rationale: the decision record.

SAST Pipeline

Run the full static analysis pipeline:

make lint
# or individually:
cargo check          # type errors, memory-safety, data-race checks
cargo fmt --check    # canonical formatting
cargo clippy         # lints, performance, correctness, unsafe checks
cargo audit          # RustSec advisory DB scan
cargo deny check     # license, bans, supply-chain policy

Testing

The suite has five layers, each answering a different question:

Layer File / command Answers
Unit + integration cargo test Does it do the right thing on the cases we thought of?
Property tests/proptest_parsers.rs Does it hold for inputs nobody thought of?
Invariant tests/static_assertions_invariants.rs Do the type-level contracts still hold? (checked by the compiler)
Fuzz fuzz/fuzz_targets/* Does untrusted input ever crash or hang it?
Mutation cargo mutants Do the tests actually catch a behaviour change?

The property layer includes the core security guarantee — that no path sanitize_path accepts can escape the extraction root. The invariant layer is enforced at compile time, so a violation fails the build rather than production.

# Run all tests (unit + integration)
cargo test

# Test fixtures are in test-fixtures/:
ls test-fixtures/
# sample.zip, sample.tar, sample.tar.gz, sample.tar.bz2,
# sample.tar.xz, sample.tar.zst, sample.txt.gz,
# sample.txt.bz2, sample.txt.xz, sample.txt.lz4,
# sample.txt.zst, sample.txt.lzma, sample.lzh,
# sample.sit, sample.cpt

Fuzzing

Fuzz testing with cargo-fuzz (requires nightly):

# Install cargo-fuzz
cargo install cargo-fuzz

# List available fuzz targets
cargo +nightly fuzz list

# Run a single target
cargo +nightly fuzz run fuzz_gzip_parse

# Run all targets (30 s each, CI smoke test)
make fuzz

# Run with a time limit
cargo +nightly fuzz run fuzz_detect_format -- -max_total_time=60

Fuzz Targets

Target What it tests
fuzz_detect_format Magic-byte format detection
fuzz_gzip_parse Gzip header/trailer parsing
fuzz_lz4_parse LZ4 frame header and decompression
fuzz_sit_analyze StuffIt archive parsing
fuzz_cpt_analyze Compact Pro archive parsing
fuzz_lha_parse LHA/LZH archive parsing
fuzz_xar_parse XAR archive header/TOC parsing
fuzz_xar_toc_xml XAR table-of-contents XML parsing
fuzz_dds_parse DDS texture header parsing
fuzz_sanitize_path Path-traversal sanitisation
fuzz_size_parse --max-* size-suffix parsing
fuzz_archive_extract Full extraction pipeline

Seed corpora from test-fixtures/ are in fuzz/corpus/.

fuzz_xar_toc_xml exists separately from fuzz_xar_parse for a reason: the latter hands raw bytes to the XAR opener, so an input only reaches the XML parser if the fuzzer happens to synthesise a valid zlib stream — which it effectively never does, leaving that parser unfuzzed. The former treats the input as the TOC XML and zlib-wraps it, so every input reaches it.

Cross-compilation

# Build for all targets
make dist

# Or individually
make dist-linux-x86
make dist-linux-arm
make dist-windows
make dist-macos-x86
make dist-macos-arm

The CI workflow (.github/workflows/ci.yml) builds all 5 targets automatically.

License

MIT OR Apache-2.0

Acknowledgements

This tool integrates concepts and functionality from: