Skip to content

Add Jazzer coverage-guided fuzzing for untrusted-input parsers#118

Open
seonghobae wants to merge 2 commits into
mainfrom
feat/add-fuzzing
Open

Add Jazzer coverage-guided fuzzing for untrusted-input parsers#118
seonghobae wants to merge 2 commits into
mainfrom
feat/add-fuzzing

Conversation

@seonghobae

Copy link
Copy Markdown
Collaborator

Summary

Adds Jazzer (Apache-2.0) coverage-guided fuzzing, integrated with JUnit 5, for the viewer's highest-value untrusted-input surfaces. Targets were chosen using CodeGraph to find the boundaries where attacker-controlled bytes are parsed.

Each harness asserts that arbitrary input only ever yields the documented failure mode, never an unhandled runtime exception.

Harness Surface Invariant
ArtifactTokenParserFuzzTest ArtifactLinkService.verifyReadToken — splits + HMAC-verifies + Base64URL-decodes + parses 10 token fields (UUID, epoch Instants). Forges valid signatures to reach the deep parse path. Only ArtifactTokenException (4xx) escapes
DocumentValidationFuzzTest DefaultDocumentValidationService.validateOrThrow — filename/extension + policy-override headers Only IllegalArgumentException escapes
TenantClaimsFuzzTest TenantContext.fromHeaders + TenantAccessService.require — tenant/subject/permission + signed-claims timestamp/signature fromHeaders never throws; require only throws ResponseStatusException

Bug found and fixed

Fuzzing immediately surfaced an unhandled java.time.DateTimeException in the artifact-token parser. Instant.ofEpochSecond(Long.parseLong(...)) throws DateTimeException (which is not an IllegalArgumentException) on an out-of-range but syntactically valid epoch second, escaping the catch (IllegalArgumentException) block in ArtifactLinkService.parseAndVerify and turning a would-be 401 into a 500. Fixed by also catching DateTimeException; the triggering input is committed as a regression seed (regression-datetime-epoch-overflow).

How it runs

  • mvn test (default): @FuzzTest methods run in regression mode, replaying the committed seed corpus as fast unit tests — no libFuzzer driver, cross-platform. 348 tests green (was 336).
  • JAZZER_FUZZ=1 mvn test -Dtest=<Harness>: libFuzzer drives each target for its bounded @FuzzTest(maxDuration = "60s") budget.

CI

.github/workflows/fuzz.yml fuzzes each target for 60s on PRs/pushes and 10min/target nightly (bounded to keep cost low). New crashers fail the job and upload the reproducer as an artifact.

Notes

  • Permissive deps only (Jazzer = Apache-2.0). No new runtime deps, no secrets from env, no DB/object changes.
  • Included a foundational coverage-guided fuzzing paper in docs/papers/ (Böhme et al., Coverage-based Greybox Fuzzing as Markov Chain, CCS 2016 — the AFLFast paper) and docs/fuzzing.md.
  • CodeGraph was used to locate the target surfaces (codegraph init + codegraph explore).

🤖 Generated with Claude Code

https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P

Adds Jazzer (Apache-2.0) JUnit 5 fuzz harnesses for the highest-value
untrusted-input surfaces, selected with CodeGraph:

- ArtifactTokenParserFuzzTest: fuzzes ArtifactLinkService.verifyReadToken
  (attacker-controlled HMAC token; forges valid signatures to reach the
  Base64URL-decode/UUID/epoch-parse path). Invariant: only ArtifactTokenException.
- DocumentValidationFuzzTest: fuzzes DefaultDocumentValidationService
  (filename/extension + policy-override headers). Invariant: only IllegalArgumentException.
- TenantClaimsFuzzTest: fuzzes TenantContext.fromHeaders + TenantAccessService.require
  (tenant/subject/permission + signed-claims timestamp/signature).
  Invariant: fromHeaders never throws; require only throws ResponseStatusException.

Fuzzing immediately surfaced an unhandled java.time.DateTimeException in the
token parser: Instant.ofEpochSecond(Long.parseLong(...)) escaped the
catch (IllegalArgumentException) block on an out-of-range epoch second,
turning a 401 into a 500. Fixed by also catching DateTimeException in
ArtifactLinkService.parseAndVerify; the triggering input is committed as a
regression seed.

Seed corpora, a bounded CI fuzz job (.github/workflows/fuzz.yml: 60s/target on
PRs, 10min/target nightly), docs/fuzzing.md, and a foundational coverage-guided
fuzzing paper (docs/papers) are included. Regression replay of the seed corpus
runs as part of the normal `mvn test` build (348 tests green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P
Comment thread .github/workflows/fuzz.yml Fixed
Comment thread .github/workflows/fuzz.yml Fixed
Comment thread .github/workflows/fuzz.yml Fixed
@seonghobae seonghobae enabled auto-merge (squash) July 9, 2026 01:22

permissions:
contents: read
security-events: write
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

2 participants