feat(test): add Go fuzz targets and CI workflow — OpenSSF Phase 4#532
Merged
Conversation
Closes the OpenSSF Scorecard Fuzzing check by adding native Go fuzz targets on the two highest-value untrusted-input surfaces in the project, plus tooling to exercise them locally and in CI. Fuzz targets: * internal/repository.FuzzDiskRepositoryGetState Fuzzes JSON state-file loading. State files can be persisted to disk or S3 and re-loaded later, so they are the primary untrusted-input surface in the project. A malformed file must never panic, OOM, or hang the reconciliation loop -- only return a typed error. * internal/model.FuzzGroupsResultUnmarshalBinary Fuzzes gob deserialization. The decoder loops `Items` times calling dec.Decode, so an attacker who controls the encoded blob can request an enormous number of items and force aggressive allocation. The fuzzer surfaces panics, hangs, or unbounded allocation here. Tooling: * New `make fuzz` target. Go's -fuzz only accepts one target per invocation, so the recipe iterates over every Fuzz* function found by `go test -list`. FUZZ_TIME (default 60s) is per-target. * New .github/workflows/fuzz.yml: - pull_request -> 60s/target (won't block merges) - schedule weekly Wed 06:00Z -> 10m/target (actually finds bugs) - workflow_dispatch lets a maintainer override the duration Smoke-tested both targets locally for 5s each: ~92-112k execs/sec, no crashes, 16-88 new interesting inputs discovered. `make test` still passes -- fuzz files compile as normal Go tests, the seed corpus runs as part of the unit-test pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 4 of the OpenSSF Scorecard hardening effort. Adds native Go fuzz targets on the two highest-value untrusted-input surfaces in the project, plus tooling to exercise them locally and in CI. Closes the Fuzzing check (0/10 → 10/10 once Scorecard re-evaluates).
Fuzz targets
internal/repository.FuzzDiskRepositoryGetStateFuzzes JSON state-file loading via
DiskRepository.GetState. State files can be persisted to disk or S3 and re-loaded later, so they are the primary untrusted-input surface in the project. A malformed file must never panic, OOM, or hang the reconciliation loop — only return a typed error.Seed corpus includes well-formed state, empty input, truncated JSON, raw binary, and a
"items": 2147483647pathological case.internal/model.FuzzGroupsResultUnmarshalBinaryFuzzes gob deserialization via
GroupsResult.UnmarshalBinary. The decoder loopsItemstimes callingdec.Decode, so an attacker who controls the encoded blob can request an enormous number of items and force aggressive allocation before failing. The fuzzer surfaces panics, hangs, or unbounded allocation here.Seed corpus includes a valid round-trip encoding plus several adversarial byte patterns.
Tooling
make fuzzGo's
-fuzzflag only accepts one target per invocation, so the new Makefile recipe iterates over everyFuzz*function discovered bygo test -list:.github/workflows/fuzz.ymlpull_requestto mainschedule(weekly, Wed 06:00 UTC)workflow_dispatchAll actions SHA-pinned per project convention (
actions/checkout@de0fac2e…,actions/setup-go@4a360112…).Verification (local smoke test)
Both targets ran clean for 5 seconds each before commit:
make testalso passes — fuzz files compile as normal Go tests and their seed corpus runs as part of the unit-test pass.What happens when the fuzzer finds a crash
Go writes the offending input to
testdata/fuzz/<FuzzName>/<hash>. Commit those files: they become permanent regression tests that re-run as part ofgo test ./...going forward. No special tooling needed.Test plan
Buildworkflow passesCodeQLpassesFuzzworkflow runs on this PR with the 60s/target budgetFollow-ups
main, CII Silver application🤖 Generated with Claude Code