Files
glm-5.3-flash d37cbc0c8a test(fuzz): the four-target fuzz sidecar (w5-fuzz-harness)
The family standing rule (anything worth fuzzing gets fuzzed) on the
crate's ingress surfaces, per the alkgit convention:

- fuzz_targets/{exec_parse,fingerprint,key_decode,terminal_params} —
  thin fuzz_target! wrappers over four ingress surfaces:
  - exec_parse: the production parse path (from_utf8-fallback ingress
    shape), differentially paired against a from-spec grammar oracle;
    typed routing classes + determinism.
  - fingerprint: blob parse + the single fingerprint path; the
    SHA256:/unpadded-base64 contract shape via a from-spec
    base64 round-trip oracle; the committed ssh-keygen -lf vectors
    replayed inside the gate.
  - key_decode: the production decode arm (the #[doc(hidden)]
    decode_key_arm exposure, routed through itself); typed-class +
    bounded-fixed-message invariants; passphrase-shape robustness
    drive on the underlying decode.
  - terminal_params: the pty_request conversion via the #[doc(hidden)]
    terminal_params exposure; exact clamp, verbatim term, the
    reserved-modes {} JSON shape, serialized-shape determinism.
- shared/ — the invariant logic as a stable-clean library; the
  corpus gate (cargo test --manifest-path fuzz/shared/Cargo.toml)
  replays every committed seed through the batteries and joins
  AGENTS.md's verification list.
- corpus/<target>/ committed seeds + the deterministic
  gen_fuzz_seeds.py (no-op-diff verified).
- run-detached.sh + nightly pin; the sidecar excluded from the root
  workspace (workspace + package excludes; publish dry-run clean).

Exposure shape decided + recorded per target (README + lib.rs doc
comments): fingerprint needs none (fully public ingress); the rest
are #[doc(hidden)] re-exports of the production surfaces, never
forks. Detached campaigns ran per target (600 s default budget, fork
mode): all four clean; the two findings of the first round are
recorded in the task file (the harness oracle's double-/-strip bug;
the PPK echo fixed in the preceding commit).

Verification: corpus gate green (11 tests); scripts/matrix.sh green;
taskgraph validate clean.
2026-10-30 11:44:21 +00:00
..

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; thin fuzz_target! wrappers.
  • shared/ — the invariant logic, as a plain library so normal cargo 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 in src/dispatch.rs, the key-decode arm decode_key_arm in src/key_source.rs, and the terminal_params conversion in src/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 with python3 fuzz/gen_fuzz_seeds.py from the repo root — must be a no-op diff on the committed set). Only seed-* 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: wraps cargo fuzz run in setsid + 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).