# v0.9.0 — Fuzz Testing

**Release date:** 2026-05-22
**Status:** pre-1.0 milestone. The public API is allowed to evolve in
breaking ways through the `0.x` series; 1.0 freezes it.

---

## What this release is

The regression-prevention safety net. A new `fuzz/` workspace
ships **8 `cargo-fuzz` targets** covering every algorithm in the
crate plus the streaming frame format. Targets exercise the
public API with arbitrary input bytes and assert only that the
library never panics, never hangs, and never produces an
unrecoverable failure.

This phase intentionally adds zero new public API surface. The
0.8.0 functional contract carries forward unchanged.

---

## Highlights

- **8 `cargo-fuzz` targets** under `fuzz/fuzz_targets/`:
  - `aead_decrypt` — `Crypt::decrypt` with attacker-controlled
    bytes (highest-value target)
  - `aead_encrypt` — encrypt + round-trip decrypt equality
  - `hash_blake3` — BLAKE3 + XOF + streaming with streaming-vs-
    one-shot equivalence
  - `hash_sha2` — SHA-256/512 + streamers
  - `mac` — all three MACs, compute + verify + streaming
  - `hkdf` — `hkdf_sha256/sha512` with determinism check
  - `argon2_parse` — PHC parser + parameter-validation
  - `stream_decrypt` — `StreamDecryptor` with arbitrary chunk
    splits + round-trip
- **4.7 million iterations across all targets, zero findings.**
  Pre-release smoke run on WSL2 Ubuntu (15 s per target). Per-
  target counts:
  - `aead_decrypt`: 1 240 612 iterations
  - `aead_encrypt`: 667 296
  - `hash_blake3`: 609 020
  - `hash_sha2`: 815 590
  - `mac`: 499 211
  - `hkdf`: 212 542
  - `argon2_parse`: 106 053
  - `stream_decrypt`: 561 915
- **`fuzz/README.md`** documenting setup (nightly + cargo-fuzz +
  WSL2 on Windows), per-target commands, a 1-CPU-hour-per-target
  run loop for the 1.0.0-rc gate, and the findings policy
  (crashes → corpus seed + regression test + CHANGELOG entry).
- **Roadmap restructured.** Phase 0.10.0 is now **Allocation
  profile + zero-alloc paths** (using
  [`mod-alloc`](https://github.com/jamesgober/mod-alloc), the
  portfolio's dhat-compatible profiler). Docs + Release Candidate
  moved to **Phase 0.11.0**. The wrapping-overhead gap vs
  upstream RustCrypto identified in 0.8.0 will be closed before
  1.0 ships, not after.

---

## What does this give you, concretely?

If you're using `crypt-io` in production:

- **Confidence that the parsing surfaces don't panic.** PHC
  strings from your database, ciphertext from the wire, header
  bytes from a corrupted file — all hit code paths that have now
  been hammered with millions of arbitrary inputs.
- **Confidence in the streaming frame format.** Chunk boundaries
  are user-controlled — feeding 1 byte at a time, 1 KiB at a
  time, or a huge buffer all hit different code paths in the
  decryptor's internal buffer. All covered.
- **Confidence in the encrypt → decrypt round-trip.** Any input
  the encryptor accepts, the decryptor recovers exactly. Zero
  asymmetric-failure cases.

If you're contributing to the crate:

- **Any future panic / OOM / hang surfaced by `cargo-fuzz` gets a
  regression test in `tests/`.** The corpus is committed to the
  repo so the regression sticks.
- **The smoke window (15 s × 8 targets) is fast enough for
  developer use.** Run `for t in $(cargo +nightly fuzz list); do
  cargo +nightly fuzz run "$t" -- -max_total_time=15; done`
  before pushing a security-sensitive change.

---

## Running the fuzz suite

Requirements:
- **Nightly Rust** (libfuzzer needs sanitizer support).
- **Linux or macOS** (libfuzzer doesn't ship for Windows — on
  Windows use WSL2).
- `cargo install cargo-fuzz`.

```bash
cd fuzz/

# Smoke (~15s per target)
cargo +nightly fuzz run aead_decrypt -- -max_total_time=15

# 1 CPU-hour run (the 1.0.0-rc soak gate)
cargo +nightly fuzz run aead_decrypt -- -max_total_time=3600

# All 8 targets, 5 minutes each (~40 min total)
for t in aead_decrypt aead_encrypt hash_blake3 hash_sha2 mac \
         hkdf argon2_parse stream_decrypt; do
    cargo +nightly fuzz run "$t" -- -max_total_time=300
done
```

Full reference in [`fuzz/README.md`](../../fuzz/README.md).

---

## What's NOT in 0.9.0

- **1-CPU-hour-per-target runs.** Smoke-only this release; the
  full 1-hour-per-target soak is the **Phase 0.11.0 RC gate**
  (~8 hours of unattended runtime). The smoke results give
  100 K-1.24 M iterations per target of immediate-panic
  confidence right now.
- **Argon2id at OWASP defaults as a fuzz target.** Each iteration
  is ~50-150 ms; the fuzzer would be useless. Parameter
  validation is exercised through `argon2_hash_with_params` with
  capped costs in `argon2_parse.rs` instead.
- **TEE-detection fuzzing.** It's a runtime hardware probe, not
  an input-driven surface.
- **Continuous fuzzing in CI.** Not free — needs a dedicated
  runner. Reasonable post-1.0 add-on; current setup supports
  one-shot local runs.

---

## Compatibility & build

- **No public API changes.** `Crypt`, `Algorithm`, `Error`,
  `Result`, all six modules carry forward unchanged from 0.8.0.
- **MSRV unchanged:** Rust 1.85 (edition 2024) for the main
  crate. The `fuzz/` workspace is a separate Cargo project that
  uses nightly — does not leak the nightly requirement to
  consumers.
- **No new runtime dependencies.** The fuzz workspace adds
  `libfuzzer-sys` and `arbitrary` as fuzz-only dev-deps.
- **No new files committed under `fuzz/corpus/`** yet — populated
  on first finding (no findings in this release).

---

## Installation

```toml
[dependencies]
crypt-io = "0.9"
```

---

## Verification

| Check | Result |
|---|---|
| `cargo fmt --all -- --check` (Windows + WSL Linux) | clean |
| `cargo clippy --all-targets --all-features -- -D warnings` (both) | clean |
| `cargo test --all-features` (both) | 126 unit + 1 smoke + 25 stream + 31 doctest — all passing |
| `cargo doc --no-deps --all-features` with `RUSTDOCFLAGS="-D warnings"` (both) | clean |
| All 8 `cargo-fuzz` targets build | passes (WSL2 Ubuntu, nightly 2026-05-21) |
| Smoke fuzz across 8 targets (15 s each) | **0 findings, ~4.7 M iterations** |
| MSRV (1.85) build | clean |

---

## What's next

Phase 0.10.0: Allocation profile + zero-allocation hot paths.
Wire [`mod-alloc`](https://github.com/jamesgober/mod-alloc) as
the heap profiler (lower MSRV than dhat, ~60 ns/op overhead, same
JSON output format). Ship `Crypt::encrypt_into(&mut buf, ...)` +
`StreamEncryptor::update_into(&mut buf, ...)` to close the
per-call `Vec` allocation gap that's the documented wrapping-
overhead vs upstream RustCrypto. Re-bench with the new APIs; push
stream encrypt cleanly over 1 GiB/s before the 1.0 cut.
