- fuzz/shared/src/spec_import.rs: invariants over the import-parse surface — the three OpenAPISpec constructors (str/json/yaml, ADR-051 JSON-first), the YAML→JSON normalization boundary (reject-loudly partition: non-finite floats, pointer keys), the compile/harden/validate lane (hardening partition, validate determinism, closed-by-default unknown-key probe), the FromMCP build_spec tool-shape lane, and the $ref-bounded resolver lane (cycles/hop chains/deep nesting fail cleanly, no hang, no stack blowout). - fuzz/fuzz_targets/spec_import.rs: thin fuzz_target! wrapper. - 42 committed corpus seeds via gen_fuzz_seeds.py (valid JSON/YAML docs, structural failures, YAML normalization edges, $ref cycles/ chains/unresolvable/external, schema-validation edges, MCP tool-shape edges); corpus-replay gate count floor 12. - fuzzing exposure hubs next to the wave-1 one: openapi_spec::fuzzing, input_validation::fuzzing (opaque FuzzCompiledValidator newtype — the pub(crate) validator never leaks), from_mcp::fuzzing (mcp feature). - fuzz/shared gains openapi/mcp features (default on); json.dict extended with the OpenAPI token set (75 unique tokens). - clippy follow-up in wave-1's ws_chunk_framing replay test (unnecessary u32 casts against the newer lint). - docs/plans/fuzzing.md §3/§6 target 3 → implemented; fuzz/README target table + campaign-advice lines updated. Verification: corpus replay 11/11 green (default and --no-default-features), cargo fuzz build clean, main crate 457 default / 590 all-features tests green, clippy+fmt clean both crates. Campaign: detached 10-min smoke run — see docs/plans/fuzzing.md §5.
alkhttp fuzzing
cargo-fuzz targets for the HTTP surface's hand-rolled parse paths. The
design and operating rules live in docs/plans/fuzzing.md (adopted
from alkcall's docs/research/fuzzing.md); this README is the
operational cheat-sheet.
Layout
fuzz_targets/— nightly-onlyfuzz_target!binaries (thin wrappers).shared/— stable-toolchain library holding the invariant logic; the corpus replay tests run here on plaincargo test.corpus/<target>/— committed seeds (regenerate withpython3 fuzz/gen_fuzz_seeds.py).artifacts/— gitignored crash/oom/timeout artifacts + campaign logs.
Targets
| Target | Drives |
|---|---|
sse_parser |
SseParser::feed + the FWD-17 envelope mapping (src/adapters/forward.rs) — the SSE frame parser every subscription forwards through |
ws_chunk_framing |
WriteChunkWalk (the write pump's chunk walk) (src/websocket/byte_adapter.rs) |
spec_import |
OpenAPISpec::from_str/from_yaml/from_json, the YAML normalization boundary, CompiledInputSchema compile/harden/validate, FromMCP::build_spec (src/adapters/openapi_spec.rs, input_validation.rs, from_mcp/mod.rs) |
Running a campaign — always detached
Agent sessions must never run fuzzing in the foreground (an OOM in a target can take down the session host; see docs/plans/fuzzing.md §2). Use the detached runner:
fuzz/run-detached.sh sse_parser
# poll:
tail -n 50 fuzz/artifacts/sse_parser-*.log
ls fuzz/artifacts/sse_parser/
pgrep -f "cargo fuzz run sse_parser"
FUZZ_RUNTIME_SECS=1800 fuzz/run-detached.sh sse_parser for a longer
campaign. The runner pins -fork=1 -rss_limit_mb=2048 -malloc_limit_mb=2048 -timeout=25 and detaches via setsid + nohup.
Corpus replay (the standing fuzz gate)
cargo test --manifest-path fuzz/shared/Cargo.toml
Replays every committed seed through the same invariant functions the fuzzer runs, on stable, no nightly needed. Part of the release verification checklist (AGENTS.md).
Toolchain notes
rust-toolchain.toml pins nightly for this subtree only (llvm-tools
required by cargo-fuzz); the crate itself stays on stable at MSRV 1.88.
cargo fuzz build must be run with the fuzz dir as CWD or trust the
toolchain file (rustup resolves per directory).
Run campaigns before releases and after touching src/adapters/forward.rs
(the SSE parser), src/websocket/byte_adapter.rs (the WS chunk walk),
or the import adapters (src/adapters/openapi_spec.rs,
input_validation.rs, from_mcp/).