# archmeld

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

[![Crates.io](https://img.shields.io/crates/v/archmeld)](https://crates.io/crates/archmeld)
[![License](https://img.shields.io/crates/l/archmeld)](LICENSE)
[![rust-doctor](https://img.shields.io/badge/rust--doctor-99%2F100-brightgreen)](benchmark.md)

## Features

- **24 archive and compression formats** supported
- **Zero `unsafe` code** (`unsafe_code = "forbid"`)
- **Security-first** extraction via `safe_unzip`
  (zip-slip, zip-bomb, symlink, encrypted entry
  protection)
- **4 output formats**: text, JSON, SARIF 2.1.0,
  Markdown
- **359 tests**, **15 fuzz targets**,
  **0 cargo-audit vulnerabilities**
- **2.3 MiB binary** (51% smaller than ouch)
- **CSAF 2.0 advisories** for all security findings

## Installation

```bash
cargo install archmeld
```

Or download pre-built binaries from the
[releases page](https://github.com/ndaal/archmeld/releases).

## Quick Start

```bash
# Detect format
archmeld info archive.tar.gz

# List contents
archmeld list archive.zip

# Extract
archmeld extract archive.tar.xz -o output/

# Validate (dry-run, no output written)
archmeld validate archive.7z

# Verify integrity (checksums)
archmeld verify archive.gz

# Compress with Zopfli (high-ratio gzip)
archmeld gz-compress file.txt

# LZ4 compress/decompress
archmeld lz4 compress file.txt
archmeld lz4 decompress file.txt.lz4

# Inspect headers
archmeld gz-inspect archive.gz --verify
archmeld lz4 inspect file.lz4

# Output as JSON, SARIF, or Markdown
archmeld info -O json archive.zip
archmeld list -O sarif archive.tar.gz
archmeld validate -O markdown archive.deb
```

## Supported Formats

| Format      | Extract | List | Info | Inspect |
| ----------- | ------- | ---- | ---- | ------- |
| ZIP         | YES     | YES  | YES  |         |
| TAR         | YES     | YES  | YES  |         |
| TAR.GZ      | YES     | YES  | YES  |         |
| TAR.BZ2     | YES     | YES  | YES  |         |
| TAR.XZ      | YES     | YES  | YES  |         |
| TAR.ZSTD    | YES     | YES  | YES  |         |
| TAR.LZ4     | YES     |      | YES  |         |
| 7-Zip       | YES     |      | YES  |         |
| GZIP        | YES     |      | YES  | YES     |
| BZIP2       | YES     |      | YES  |         |
| XZ          | YES     |      | YES  |         |
| LZ4         | YES     |      | YES  | YES     |
| Zstandard   | YES     |      | YES  |         |
| BZip3       | YES     |      | YES  |         |
| Brotli      | YES     |      | YES  |         |
| Snappy      | YES     |      | YES  |         |
| LZMA        | YES     |      | YES  |         |
| DEB         | YES     | YES  | YES  |         |
| AR          | YES     | YES  | YES  |         |
| CAB         | YES     | YES  | YES  |         |
| RAR         | YES     | YES  | YES  |         |
| StuffIt     |         |      | YES  | YES     |
| Compact Pro |         |      | YES  | YES     |
| Lzip        |         |      | YES  |         |
| ARC         |         |      | YES  |         |

RAR support requires the `rar` feature (enabled
by default, bundles C++ unrar library).

## Security

archmeld is built with security as a primary goal:

- **`unsafe_code = "forbid"`** in the entire crate
- **Zip-Slip protection** via `path_jail` and
  `sanitize_path`
- **Zip-Bomb detection** with configurable limits
  (file size, total size, entry count, compression
  ratio, path depth)
- **Symlink rejection** (configurable policy)
- **Encrypted entry detection** (returns error)
- **Filename sanitization** (control characters,
  Windows reserved names)
- **Two-pass validation** (`ValidateFirst` mode)
- **Permission stripping** (setuid/setgid removal)
- **Atomic file creation** (`O_EXCL` via
  `safe_unzip`)

```bash
# Configurable safety limits
archmeld extract archive.zip \
  --max-file-size 50 \
  --max-total-size 500 \
  --max-entries 10000 \
  --max-ratio 500
```

## Benchmark vs ouch

Tested on macOS (Apple Silicon) with hyperfine.

### Large File Extraction (curl 8.20.0 source)

| File           | archmeld | ouch     | Winner      |
| -------------- | -------- | -------- | ----------- |
| curl.tar.gz    | 2467 ms  | 5574 ms  | archmeld 2x |
| curl.tar.bz2   | 9302 ms  | 14773 ms | archmeld 2x |
| curl.tar.xz    | 4970 ms  | 5954 ms  | archmeld 1x |
| curl.zip       | 5186 ms  | 4020 ms  | ouch 1x     |

### Code Quality (rust-doctor)

| Category        | archmeld     | ouch     |
| --------------- | ------------ | -------- |
| **Overall**     | **99 / 100** | 92 / 100 |
| Security        | **100**      | 99       |
| Reliability     | **100**      | 90       |
| Maintainability | **98**       | 69       |
| Warnings        | **24**       | 345      |

### Summary

| Metric            | archmeld      | ouch    |
| ----------------- | ------------- | ------- |
| Formats           | **24**        | 14      |
| Binary size       | **2.3 MiB**   | 4.7 MiB |
| Tests             | **359**       | N/A     |
| Fuzz targets      | **15**        | N/A     |
| `unsafe_code`     | **Forbidden** | Used    |
| Security features | **14**        | 2       |
| cargo-audit       | **0 vulns**   | N/A     |
| CSAF advisories   | **4**         | 0       |

Full benchmark details in
[benchmark.md](benchmark.md).

## Library Usage

```rust
use archmeld::archive::Extractor;
use archmeld::format::{self, ArchiveFormat};

// Detect format from bytes
let data = std::fs::read("archive.tar.gz")?;
let fmt = format::detect_format(&data);

// Extract with safety limits
let extractor = Extractor::new()
    .with_max_file_size(100 * 1024 * 1024)
    .with_max_total_size(1024 * 1024 * 1024)
    .with_max_entries(100_000);

let files = extractor.extract(&data, fmt)?;
for file in &files {
    println!("{}: {} bytes", file.path, file.size);
}

// Validate without extracting
let result = extractor.validate(&data, fmt)?;
println!("Valid: {}, entries: {}", result.is_valid,
  result.entry_count);
```

## Building

```bash
# Debug build
cargo build

# Release build
cargo build --release

# Without RAR support (no C++ dependency)
cargo build --release --no-default-features

# Run tests
cargo test

# Run clippy
cargo clippy --all-targets -- -D warnings

# Run fuzz targets (requires nightly)
cargo fuzz run fuzz_extract -- -max_total_time=60
```

## CSAF Security Advisories

All dependency vulnerabilities are documented
as CSAF 2.0 advisories in `csaf/`:

- **ndaal-sa-2026-007**: lz4\_flex info leak
  (RUSTSEC-2026-0041) — FIXED
- **ndaal-sa-2026-008**: tar PAX header
  (RUSTSEC-2026-0068) — FIXED
- **ndaal-sa-2026-009**: tar symlink chmod
  (RUSTSEC-2026-0067) — FIXED
- **ndaal-sa-2026-010**: time DoS
  (RUSTSEC-2026-0009) — FIXED

## License

MIT OR Apache-2.0

## Authors

[ndaal GmbH](https://ndaal.eu) — Pierre Gronau

Security contact: <security@ndaal.eu>
