Skip to content

security: ClusterFuzzLite fuzzing of the composed gate (Scorecard Fuzzing #10)#21

Merged
askalf merged 3 commits into
masterfrom
add-clusterfuzzlite-fuzzing
Jul 12, 2026
Merged

security: ClusterFuzzLite fuzzing of the composed gate (Scorecard Fuzzing #10)#21
askalf merged 3 commits into
masterfrom
add-clusterfuzzlite-fuzzing

Conversation

@askalf

@askalf askalf commented Jul 12, 2026

Copy link
Copy Markdown
Owner

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:

Target Invariant under test
fuzz/composed_gate.fuzz.js guardedCall (a) never throws into the agent on arbitrary action input, and (b) fails closed — proceeds (ok:true) only when redstamp itself would allow. A proceed on a non-allow verdict = a fail-open composition bug.
fuzz/egress_host.fuzz.js egressHost() — the parser deciding which host a secret may be redeemed toward — never throws and always returns string | null.

composed_gate builds 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 enters package.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 :00 grid) + workflow_dispatch; SHA-pinned actions; a crash fails the job and uploads the reproducer
  • .gitattributes — pins *.sh to LF so a Windows re-commit can't CRLF-break build.sh in the Linux container
  • npm run fuzz — runs every target locally (FUZZ_SECONDS to tune)

Verification

Both targets run clean locally under Jazzer.js:

  • egress_host — ~45k runs, no crash
  • composed_gate — ~1.2k runs (FS-stateful), no crash; the fail-closed assertion held on all inputs

No change to runtime/public API. npm test unaffected (fuzz targets are not part of the test run).

askalf added 2 commits July 12, 2026 10:26
… (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.
@askalf

askalf commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author

Two notes for review

1. ssh→https for the git deps (2nd commit). The root lockfile resolves @askalf/* to git+ssh://git@github.com URLs. GitHub-hosted runners clone those fine (that's why ci.yml is green), but the ClusterFuzzLite build runs inside the OSS-Fuzz base-builder container, which has no SSH key — so build.sh rewrites ssh→anonymous https via git config insteadOf before npm ci (public repos, shared lockfile untouched). Verified locally with GIT_SSH_COMMAND=false: the ls-remote succeeds through the rewrite and fails without it.

2. The OSS-Fuzz build runs first after merge. cflite.yml triggers on schedule + workflow_dispatch only, and workflow_dispatch keys off the default branch — so the fuzz build (the OSS-Fuzz Docker image + npm ci in-container) can't execute on this PR. I've de-risked every piece I can locally: both fuzz targets run clean under Jazzer.js, both npm ci calls + the Jazzer merge succeed, and the ssh→https rewrite is proven. After merge, kick the tires with:

gh workflow run cflite.yml --repo askalf/agent-security-stack

If the in-container build ever needs a nudge, the two usual levers are the base-image digest (.clusterfuzzlite/Dockerfile) and the git-dep clone (handled above).

@askalf askalf enabled auto-merge (squash) July 12, 2026 14:50
@askalf askalf merged commit 9eaec66 into master Jul 12, 2026
2 checks passed
@askalf askalf deleted the add-clusterfuzzlite-fuzzing branch July 12, 2026 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant