Renames the written-usage rule into practice across the contract surface: docs/ (architecture, research, plans, sdd), tasks/ (all 43 task files, taskgraph validate passes), AGENTS.md, README.md, CHANGELOG.md, and the .opencode/agents/ prompts. Every replacement is a sense-preserving rewrite (colons, semicolons, commas, parentheses, sentence splits, varied), never a mechanical swap; no spaced hyphens were substituted. Enum fields, frontmatter, tables, wire data, and cited identifiers survive verbatim; en dashes inside numeric ranges keep their meaning. Verification: taskgraph validate (43 tasks), repo grep clean (single exempt hit: the verification command's own search pattern in AGENTS.md), cargo test, clippy --all-targets -D warnings, fmt --check, doc all green.
10 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| w5-fuzz-harness | Wave 5: the fuzz sidecar (the crate's ingress surfaces) | completed |
|
moderate | medium | phase | implementation |
Description
The family standing rule, "anything worth fuzzing gets fuzzed", applied to
the crate's ingress surfaces (docs/plans/implementation.md §Wave 5,
the fuzzing bullet): the fuzz/ sidecar per the alkgit convention
(fuzz/README.md there, covering the layout, the detached runner, the corpus
gate). Four targets over the plan's ingress inventory:
exec_parse, the git-parseable exec grammar + the dispatch parse path (src/git_map.rs's parse +src/dispatch.rs's parse-only routing): no-panic on any command string; the typed decline classes (git-parseable → the git row, non-git → the session pipe path, service-only → decline-with-reason) stable per input; the per-length caps enforced before any allocation (the decline-first-alloc-never invariant under hostile lengths).fingerprint, theSHA256:+ unpadded base64 format path (src/fingerprint.rs): no-panic on arbitrary key blobs; the format invariant holds for every accepted input; determinism; the committedssh-keygen -lfparity vectors still agree.key_decode, thedecode_secret_keyingress (src/key_source.rs's decrypt-at-use seam): no-panic on arbitrary plaintext bytes and passphrase shapes; every failure a typedKeySourceErrorclass (decode failure, never a catch-all, never a panic); no secret material retained past the one-time decode (theZeroizingposture's shape asserted where assertable).terminal_params, thepty_requestargs →TerminalParamsWireconversion (src/tty_map.rs, the SSH modes → reserved-modes JSON): no-panic on arbitrary term strings, sizes, pixel dims, and mode blobs; oversize/overflow fail-closed (typed, not clamped-silent); the reserved-modes JSON shape stable.
Shape (the alkgit convention, adapted):
fuzz_targets/thinfuzz_target!wrappers;shared/the invariant logic as a plain stable-clean library socargo test --manifest-path fuzz/shared/Cargo.tomlreplays the committed corpora through the same invariants, and the corpus gate joins the verification list; the sidecar consumes the crate's public API only (plus#[doc(hidden)]fuzz-visible exposures where the ingress is private, with the exposure shape decided + recorded, the alkgit pattern; never a dependency of the root crate).corpus/<target>/committed hand-made seeds + the deterministic generator (gen_fuzz_seeds.py, no-op-diff check); grown corpora/artifacts/coverage gitignored, never merged back.run-detached.sh, the mandatory detached runner (agent sessions never foreground a campaign);rust-toolchain.tomlnightly pin insidefuzz/; the rootCargo.tomlgainsexclude = ["fuzz"](the root is a single-member workspace, with the sidecar its own).- Campaigns run detached per target (the runner's default budget); any finding fixed in-task or booked for the gate, never absorbed silently.
Acceptance Criteria
- The
fuzz/sidecar lands with all four targets; the sidecar is excluded from the root workspace and never a root dep - The exposure shape decided + recorded (public API vs
#[doc(hidden)]per target) - Committed seeds + the deterministic generator (no-op diff);
the corpus gate (
cargo test --manifest-path fuzz/shared/Cargo.toml) green and joining the verification list - Detached campaigns run per target; findings fixed or booked (recorded)
fuzz/README.mddocuments the layout + commands (the alkgit shape); the root crate's verification set stays green (the sidecar changes no root behavior)
References
- docs/plans/implementation.md §Wave 5 (the fuzzing bullet, giving the four-surface inventory)
- /workspace/@alkdev/alkgit/fuzz/ (the convention's reference implementation: README, run-detached.sh, gen_fuzz_seeds.py, shared/), docs/plans/implementation.md (alkgit) §Wave 5 (the fuzz-harness task's shape)
- src/git_map.rs, src/dispatch.rs, src/fingerprint.rs, src/key_source.rs, src/tty_map.rs (the ingress surfaces)
- docs/architecture/mapping.md §Session-channel dispatch (the decline-first-alloc-never invariant the exec_parse target pins)
Notes
The sidecar as landed: fuzz/ (its own workspace:
members = []+ nightly pin infuzz/rust-toolchain.toml; the root Cargo.tomlgainsexclude = ["fuzz"]at *both* levels: the[workspace]exclude and the[package]exclude list (the alkgit published-crate policy: the sidecar never ships in a publish; verified with the dry-run)).fuzz/Cargo.lockis committed (the alkgit convention);artifacts/, coverage/, target/`, and grown corpora
are gitignored.
The exposure-shape decision (per target and recorded): the sidecar consumes alkssh's public API wherever the ingress is already public, and the production surface itself (never a fork) where it is private:
fingerprint, with no exposure needed: the ingress is fully public ([alkssh::fingerprint] + the russh 0.65 blob parseparse_public_key_base64, the same re-export surface the crate rides).exec_parse, a#[doc(hidden)]re-export of the dispatch parse path:parse_exec,ExecRoute,ExecDecline,GitServiceRoute(madepubin the privatedispatchmodule; the lib re-export gate is the only reachability, as the grammar stays a dispatch seam, not a public-API promise).key_decode, a#[doc(hidden)]exposuredecode_key_arm, the productiondecrypt_and_decode's decode arm extracted and routed through itself (the fuzz target drives the exact production arm, and the arm takes&Zeroizing<String>so the drive exercises the adapter seam's wrap posture).terminal_params, a#[doc(hidden)]re-export (undersession) oftty_map::terminal_params, the exact functionhandler.rs'spty_requestrow constructs the stored params with.
The harness shape: the deterministic framed feed for
terminal_params (term_len u32-LE + term bytes + four u32-LE dims,
zero-defaulted, trailing mode blob ignored, since the modes are never
parsed, OQ-44); the production ingress shape for exec_parse
(byte string → from_utf8(..).unwrap_or("") → parse, the handler's
exact shape; the pre-parse cap and the git row's repo-id gate are the
root contract rows' territory, pinned by tests/dispatch_core.rs);
a from-spec base64 round-trip oracle for fingerprint; the
exec_parse verdict is differentially paired against a from-spec
grammar oracle (fuzz/shared/src/exec_parse.rs), fingerprint
parity replays the committed ssh-keygen -lf vectors inside the
corpus gate.
Campaigns run (detached via fuzz/run-detached.sh, fork mode, the
default 600 s budget each; all four final campaigns exited 0, with zero
crashes/OOMs/timeouts):
exec_parse, 2 campaigns: the first crashed on a harness-side oracle bug (the oracle stripped two leading/where the grammar strips exactly one (git-upload-pack //.git); fixed, re-ran clean (40M+ execs).fingerprint: 1 campaign, clean (12M+ execs).key_decode, 2 campaigns: the first found a real crate finding (below); fixed, re-ran clean (3.5M+ execs).terminal_params: 1 campaign, clean (8.8M+ execs).
The real finding (fixed in-task, recorded): russh 0.65's
ssh-key PPK parse path (PpkParseError::Header) interpolates the
raw header line, the plaintext's own bytes, into its Display,
and the decode arm previously passed that message through
KeySourceError::Decode's payload: a malformed or
PPK-header-shaped vault record would echo decrypted-record material
into a stored/logged error string (the ADR-005 decision-7 posture's
textual leak). Fix: decode_key_arm (and so every Decode payload
the crate ever produces) now carries crate-owned fixed text naming
the refusal, so the machinery's message never rides the typed payload;
error.rs's Decode doc records the semantics. The upstream ask
is booked (not absorbed): ssh-key's PPK parse errors should not
echo the untrusted header; filed to russh/ssh-key with the owners at
the wave-5 gate. Related harness-side finding (also fixed in-task):
the no-echo invariant's first shape (a plaintext-contains-message
check) was itself unsound, as an attacker-chosen plaintext can be a
substring of the machinery's fixed text (the campaign's own first
crash: plaintext "Could not read key"); the assertable form is the
bounded-message pair (the text is a bounded fixed message, ≤ 256
bytes) + the ≥ 256-byte plaintext-run check that cannot false-fire.
Verification list: the corpus gate
(cargo test --manifest-path fuzz/shared/Cargo.toml) is green (11
tests, as every committed seed replays the invariants on the stable
toolchain, plus the fixture parity rows) and has joined AGENTS.md's
verification commands. scripts/matrix.sh green after all changes
(the sidecar changes no root behavior: 6 builds × the full suite +
clippy ×2 -D warnings + fmt + doc, exit 0).
Summary
Completed 2026-10-30. The fuzz/ sidecar landed per the alkgit
convention with all four targets over the plan's ingress inventory
(exec_parse, fingerprint, key_decode, terminal_params), each a
thin fuzz_target! wrapper over the stable-clean
fuzz/shared/ invariant library whose corpus replay
(cargo test --manifest-path fuzz/shared/Cargo.toml) joined the
verification list (AGENTS.md); committed hand-made seeds + the
deterministic gen_fuzz_seeds.py (no-op-diff verified); the detached
runner + nightly pin inside fuzz/; the root workspace excludes the
sidecar (workspace + package excludes). The exposure-shape decision is
recorded per target (one no-exposure, fingerprint's fully-public
ingress; three #[doc(hidden)] production-surface re-exports).
Detached campaigns ran per target at the runner's default budget,
and all are clean after findings: one harness-oracle bug (the exec oracle's
double-/ strip) and one real crate fix, russh's ssh-key PPP/PPK
parse echo (raw header bytes) riding the typed Decode payload;
decode_key_arm now carries a crate-owned fixed message and the
upstream echo ask is booked, not absorbed. Verification: the sidecar
gate green, scripts/matrix.sh green end to end, cargo publish --dry-run clean (the sidecar ships nothing).