security: ClusterFuzzLite fuzzing of the composed gate (Scorecard Fuzzing #10)#21
Conversation
… (Scorecard Fuzzing) The individual tools are each fuzzed in their own repo; this adds the missing piece — fuzzing the INTEGRATION. Two Jazzer.js targets exercise this repo's own code with arbitrary bytes: - composed_gate: feeds arbitrary actions through guardedCall and asserts the two headline invariants — it NEVER throws into the agent, and it FAILS CLOSED (proceeds only when redstamp itself would allow the action; a proceed on a non-allow verdict is a fail-open bug). Vault/lock/pin are set up once at module load; a reused high-use lease keeps each exec O(1). - egress_host: fuzzes egressHost(), the parser that decides which host a secret may be redeemed toward; asserts it never throws and returns string|null. Wiring mirrors the fleet pattern (redstamp): OSS-Fuzz JS base builder pinned by digest, weekly + on-demand cflite.yml (SHA-pinned actions), and a fuzz-only Jazzer manifest hash-pinned by its committed lockfile. Because this repo (unlike the zero-dep tools) has runtime deps the targets import, build.sh installs the project deps first, then merges Jazzer on top — Jazzer never enters package.json. A .gitattributes pins *.sh to LF so a Windows re-commit can't break build.sh in the Linux OSS-Fuzz container. `npm run fuzz` runs every target locally. Resolves the OpenSSF Scorecard Fuzzing alert. Both targets run clean locally (egress_host ~45k runs, composed_gate ~1.2k runs, no crashes).
The root lockfile resolves the git deps to git+ssh://git@github.com URLs. GitHub-hosted CI runners can clone those, but the ClusterFuzzLite build runs inside the OSS-Fuzz base-builder container, which has no SSH key — npm ci would fail to fetch redstamp/truecopy/strongroom. Rewrite ssh -> anonymous https in build.sh's git config (these repos are public); the shared lockfile is untouched. Verified: with GIT_SSH_COMMAND=false (the container's no-key condition), a git ls-remote of an ssh:// URL succeeds through the rewrite and fails without it.
Two notes for review1. ssh→https for the git deps (2nd commit). The root lockfile resolves 2. The OSS-Fuzz build runs first after merge. If the in-container build ever needs a nudge, the two usual levers are the base-image digest ( |
What
Adds ClusterFuzzLite continuous fuzzing, resolving the OpenSSF Scorecard Fuzzing alert (#10, medium). The three tools are each fuzzed in their own repos; this fuzzes the piece that was missing — the integration.
Two Jazzer.js targets exercise this repo's own code with arbitrary bytes:
fuzz/composed_gate.fuzz.jsguardedCall(a) never throws into the agent on arbitrary action input, and (b) fails closed — proceeds (ok:true) only when redstamp itself wouldallow. A proceed on a non-allow verdict = a fail-open composition bug.fuzz/egress_host.fuzz.jsegressHost()— the parser deciding which host a secret may be redeemed toward — never throws and always returnsstring | null.composed_gatebuilds the vault/lock/pinned-tool once at module load and reuses a high-use lease, so each exec is O(1) and the fuzzer covers the orchestration, not setup.Wiring (mirrors the fleet / redstamp pattern)
.clusterfuzzlite/Dockerfile— OSS-Fuzz JS base builder, pinned by digest.clusterfuzzlite/build.sh— installs the project's runtime deps first (the targets import the composed gate →@askalf/*+ MCP SDK), then merges in hash-pinned Jazzer.js; Jazzer never enterspackage.json.clusterfuzzlite/{package,package-lock}.json— fuzz-only Jazzer manifest, integrity-verified by the committed lockfile.github/workflows/cflite.yml— weekly (Mon 04:23 UTC, off the:00grid) +workflow_dispatch; SHA-pinned actions; a crash fails the job and uploads the reproducer.gitattributes— pins*.shto LF so a Windows re-commit can't CRLF-breakbuild.shin the Linux containernpm run fuzz— runs every target locally (FUZZ_SECONDSto tune)Verification
Both targets run clean locally under Jazzer.js:
egress_host— ~45k runs, no crashcomposed_gate— ~1.2k runs (FS-stateful), no crash; the fail-closed assertion held on all inputsNo change to runtime/public API.
npm testunaffected (fuzz targets are not part of the test run).