# 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](https://github.com/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](https://github.com/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](https://crates.io/crates/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](https://codeberg.org/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](https://crates.io/crates/xara)):
Extract, list, and verify XAR archives and macOS
PKG installer packages. Pure Rust.
**DDS texture inspection**
(powered by
[image_dds](https://crates.io/crates/image_dds)):
Detect and inspect DirectDraw Surface (`.dds`)
texture files — dimensions, pixel format, mip
levels, array layers.
**Gzip deep inspection**
(inspired by
[jt55401/gzinspector](https://github.com/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](https://crates.io/crates/gzinspector).
## Installation
> **⚠️ Security notice — crates.io vs. the GitLab release**
>
> The prebuilt binaries in the [GitLab release][amrel] 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][amr194], [RUSTSEC-2026-0195][amr195]) — and
> `gzinspector = "0.2"`, which pulls the unmaintained `number_prefix`
> ([RUSTSEC-2025-0119][amr119]).
>
> `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.
[amrel]: https://gitlab.com/vPierre/ndaal_public_archmeld_files/-/releases
[amr194]: https://rustsec.org/advisories/RUSTSEC-2026-0194
[amr195]: https://rustsec.org/advisories/RUSTSEC-2026-0195
[amr119]: https://rustsec.org/advisories/RUSTSEC-2025-0119
### From source
```bash
cargo install --path .
```
### Pre-built binaries
Download from the [Releases](https://github.com/ndaal/archmeld/releases) page:
| 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
```text
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:
```bash
# 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:
| `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
```bash
# 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.
```bash
# 1. Build the release binary
cargo build --release
# 2. Run the batch extraction script
./example/extract-all.sh
```
The script uses the following defaults:
| Source directory | `example/` |
| Output directory | `test/` |
| Max total size | 10 GiB |
| Max file size | 1 GiB |
Output example:
```text
=== 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
| 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:
```bash
checksec --file=target/release/archmeld
readelf -lWd target/release/archmeld \
### 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](documentation/rust/dependency-decisions/2026-08-08-quick-xml-0.41-via-xara-patch.md).
## SAST Pipeline
Run the full static analysis pipeline:
```bash
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:
| 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.
```bash
# 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](https://github.com/rust-fuzz/cargo-fuzz)
(requires nightly):
```bash
# 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
| `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
```bash
# 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:
- [secana/archive](https://github.com/secana/archive)
— unified pure-Rust archive extraction
- [traceflight/lz4_decompress](https://github.com/traceflight/lz4_decompress)
— pure-Rust LZ4 decompression
- [sit-rs](https://crates.io/crates/sit-rs)
— StuffIt archive format
- [cyco/cpt-rs](https://codeberg.org/cyco/cpt-rs)
— Compact Pro archive reading
- [jt55401/gzinspector](https://github.com/jt55401/gzinspector)
— gzip file analysis
- [delharc](https://crates.io/crates/delharc)
— pure-Rust LHA/LZH archive extraction
- [xara](https://crates.io/crates/xara)
— pure-Rust XAR/PKG archive extraction
- [image_dds](https://crates.io/crates/image_dds)
— pure-Rust DDS texture inspection
- [gzinspector](https://crates.io/crates/gzinspector)
— chunked gzip member analysis
- [exarch-core](https://crates.io/crates/exarch-core)
— secure archive extraction backend