- alkvault 0.1 -> 0.2 (breaking req): decrypt returns Zeroizing<String> (alkvault review 001, resolution paths 1+2); the adapter seam's own wrap in decrypt_and_decode is dropped (the pre-wrap window ADR-005 decision 3 recorded is closed upstream — the vault fences the plaintext from its return onward, every path including the UTF-8 error one; rotate never holds a bare plaintext either). decode_key_arm's signature is unchanged. - alktunnels 0.4.1 -> 0.5.0 (breaking req, additive API): the review-003 W2 adoption's dep half (the establisher shape's registration ride lands in the next commit). - alktty 0.8.1 -> 0.8.2 (patch, PTY exit-signal fidelity): pin text refreshed; no alkssh contract change (the negative-code convention is consumed verbatim). - seam docs re-shaped to the closed-gap posture (key_source module + decrypt arm + decode arm; doubles.rs; fuzz key_decode battery). Verification: cargo test (default + --all-features), clippy x2 -all-features -D warnings, fmt, fuzz corpus replay (11). Verification of the flake check: e2e real_git_push row 6+ isolated passes on both trees (one full-matrix load flake, pre-existing abort race, not this change).
alkssh fuzzing
libFuzzer targets for alkssh's ingress surfaces — the family standing rule (anything worth fuzzing gets fuzzed), landed per the plan's wave-5 charter and the alkgit convention: the JSON schema gate itself is alkcall's surface, family-fuzzed there; SSH framing, key exchange, and the channel multiplexer are russh's wire (family ADR-003) — the alkssh-owned post-framing ingress is what fuzzes here.
Layout
fuzz_targets/— one binary per target; thinfuzz_target!wrappers.shared/— the invariant logic, as a plain library so normalcargo test(stable toolchain) can replay the committed corpora through the same invariants (#[cfg(test)]modules; quinn's CI pattern). The fuzz binaries are nightly-only; the shared crate is stable-clean. It consumes alkssh's public API only (plus the three#[doc(hidden)]fuzz-visible exposures: the exec-grammar parse path insrc/dispatch.rs, the key-decode armdecode_key_arminsrc/key_source.rs, and theterminal_paramsconversion insrc/tty_map.rs— the recorded exposure decisions: the ingresses are production-private, so the exposures are the production surfaces themselves, never forks; the fingerprint ingress is fully public API) and must never become a dependency of the root crate.corpus/<target>/— committed hand-made seeds (regenerate withpython3 fuzz/gen_fuzz_seeds.pyfrom the repo root — must be a no-op diff on the committed set). Onlyseed-*files are tracked; grown corpora (SHA-named units), artifacts, and coverage output are gitignored and are not merged back into the committed seeds.run-detached.sh— mandatory runner for agent sessions: wrapscargo fuzz runinsetsid+nohup+ log redirection so an OOM in a target can never take down the agent host.gen_fuzz_seeds.py— deterministic seed generator (quiche pattern).
Targets
| Target | Drives | Invariants |
|---|---|---|
exec_parse |
the production exec-grammar parse (parse_exec, src/dispatch.rs — the handler's ingress shape: byte string → from_utf8 fallback "" → parse; the pre-parse max_exec_command_len cap and the git row's invalid-repo-id gate are the root contract rows' territory, the parse is everything behind them) |
no-panic on any byte string (any length behind the cap); the typed routing classes (git-parseable → the git row, non-git → the session pipe path, service-only → decline-with-reason) agree with a from-spec grammar oracle (the differential pair) and are stable per input; for Git routes the repo-id gate classification is deterministic |
fingerprint |
the crate's single fingerprint path ([alkssh::fingerprint] over russh::keys::parse_public_key_base64 — ADR-004 decision 1) |
no-panic on arbitrary key blobs (a non-blob is the parse's typed refusal); every accepted key's fingerprint is exactly the SHA256: + unpadded-standard-base64 contract shape (43-char body, canonical alphabet, decodes to a 32-byte digest, re-encodes canonically); determinism; the committed ssh-keygen -lf parity vectors still agree (the corpus-gate test row) |
key_decode |
the decrypt-at-use chain's decode arm (decode_key_arm, src/key_source.rs — the production decrypt_and_decode conversion exposed #[doc(hidden)]) + the underlying decode_secret_key passphrase arm (robustness drive — the production chain always passes None) |
no-panic on arbitrary plaintext shapes (the vault's String shape, lossy from arbitrary bytes) and passphrase shapes; every failure on the production arm is exactly the typed KeySourceError::Decode class — never a catch-all, never a panic; the failure text never carries the plaintext; the Zeroizing posture rides the drive (the wrap is what the adapter seam applies); determinism |
terminal_params |
the pty_request args → TerminalParamsWire conversion (terminal_params, src/tty_map.rs — OQ-44's SSH modes → reserved-modes {} JSON) |
no-panic on arbitrary term strings, u32 dimensions, and mode blobs (the modes are never parsed — reserved); the dimension clamp is exactly the recorded cap (min(value, 65535), never a magnitude truncation); term preserved verbatim; the reserved-modes JSON is always exactly {}; the full serialized wire shape is deterministic per input |
Commands
cargo fuzz build and cargo fuzz run must be executed with fuzz/
(or deeper) as the working directory so rustup selects the pinned
nightly toolchain — the detached runner handles that itself.
# build (nightly, pinned by rust-toolchain.toml inside fuzz/; run from fuzz/)
cargo fuzz build
# agent sessions: detached campaign (never foreground; CWD-independent)
FUZZ_RUNTIME_SECS=600 fuzz/run-detached.sh <target> [extra libfuzzer args...]
# corpus replay through the invariants (stable toolchain, no nightly)
cargo test --manifest-path fuzz/shared/Cargo.toml
# coverage
cargo fuzz coverage <target>
The fuzz workspace is excluded from the main workspace (exclude in
the root Cargo.toml); it pins its own nightly toolchain via
rust-toolchain.toml and does not affect the root workspace's stable
toolchain or MSRV.
Campaign cadence: the corpus gate (above) runs in every verification
pass; the fuzzing sessions themselves run at the wave gate and before
publish (fuzz/run-detached.sh per target, bounded runtime).