archmeld 1.3.0

Secure, memory-safe, type-safe CLI for multi-format archive extraction, inspection and decompression
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
# 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:

| 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

```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:

| 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

```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:

| Setting | Value |
| --- | --- |
| 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

| 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:

```bash
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](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:

| 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.

```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

| 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

```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