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
xaraandgzinspectorto vendored, already-fixed copies via[patch.crates-io]. Cargo strips that section when a crate is published, so the manifest on crates.io still requiresxara = "0.3"— which resolves toquick-xml0.37.5 and its two 7.5 HIGH advisories (RUSTSEC-2026-0194, RUSTSEC-2026-0195) — andgzinspector = "0.2", which pulls the unmaintainednumber_prefix(RUSTSEC-2025-0119).
cargo install archmeldtherefore builds the vulnerable quick-xml. Prefer the GitLab release artefacts until upstreamxaramoves toquick-xml >= 0.41, or apply the same patch in your own workspace.
From source
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.
# Install it.
# Refuse, for package-managed or air-gapped installs.
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
# List archive contents
# Extract with safety limits
# Extract solid 7z archives
# Allow world-writable permissions (Windows
# archives)
# Inspect gzip headers (RFC 1952)
# LZ4 operations
# Inspect legacy Mac archives
# XAR / macOS PKG archives
# DDS texture inspection
# Gzip chunked member analysis
# Verify integrity
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
# 2. Run the batch extraction script
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 = truein 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:
|
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:
# or individually:
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)
# Test fixtures are in 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
# List available fuzz targets
# Run a single target
# Run all targets (30 s each, CI smoke test)
# Run with a time limit
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
# Or individually
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:
- secana/archive — unified pure-Rust archive extraction
- traceflight/lz4_decompress — pure-Rust LZ4 decompression
- sit-rs — StuffIt archive format
- cyco/cpt-rs — Compact Pro archive reading
- jt55401/gzinspector — gzip file analysis
- delharc — pure-Rust LHA/LZH archive extraction
- xara — pure-Rust XAR/PKG archive extraction
- image_dds — pure-Rust DDS texture inspection
- gzinspector — chunked gzip member analysis
- exarch-core — secure archive extraction backend