Skip to content

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

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

Add Jazzer coverage-guided fuzzing for untrusted-input parsers#118
seonghobae wants to merge 5 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
Comment thread .github/workflows/codeql.yml Fixed
@opencode-agent opencode-agent Bot disabled auto-merge July 11, 2026 11:44
@seonghobae

Copy link
Copy Markdown
Collaborator Author

Current-head salvage is now pushed as 722afde after merging main into the existing branch.

Verification evidence:

  • CodeGraph re-index/exploration confirms the three Jazzer targets exercise artifact-token/date parsing, tenant claims, and document validation call paths.
  • Fuzz regression suite: 13 tests, 0 failures/errors/skips.
  • Full Maven suite: 416 tests, 0 failures/errors/skips.
  • Maven Javadoc completed and generated target/reports/apidocs/index.html.
  • actionlint and git diff --check pass.
  • Trivy filesystem scan at Medium/High/Critical: 0 vulnerabilities, 0 secrets, 0 misconfigurations.
  • OSV Scanner: no issues found. Maven Central returned transient HTTP 429 for several BOM enrichment requests, and the scanner logged each exact URL/reason before completing; the repository OSV policy also logs its documented CVE-2026-54515 false-positive filter for fixed Jackson 2.22.1.
  • jazzer-junit 0.30.0 is explicitly present so @FuzzTest works both as regression replay and active fuzzing.
  • Preserved original paper: Böhme, Pham, Roychoudhury, CCS 2016, DOI 10.1145/2976749.2978428; PDF SHA-256 fb3a7a74280659f86f83a934cae7bd35660e4c699dbd1dbd6825834bfe132151.
  • The repo-local CodeQL workflow was removed; CodeQL remains centrally governed by ContextualWisdomLab/.github, eliminating duplicate runs and addressing the outstanding token-permission finding.

All previously reported mutable Action references are pinned to immutable SHAs on the current head.

@seonghobae seonghobae moved this from Todo to In Progress in naruon Platform Roadmap Jul 12, 2026
@seonghobae seonghobae enabled auto-merge July 12, 2026 12:26
@seonghobae

Copy link
Copy Markdown
Collaborator Author

Current-head 7f9ef32 fixes the actual Jazzer finding from job 86649790273.

Finding and fix:

  • The uploaded crash reached DefaultDocumentValidationService.extensionOf with a filesystem-root filename. Path.getFileName() returned null and the subsequent toString() raised an undocumented NullPointerException.
  • A focused regression first reproduced the exact failure with filename slash and a non-empty payload. The validator now treats a null leaf name as a missing extension and returns the documented IllegalArgumentException reason: File extension is required.
  • A follow-up local fuzz run exposed a harness configuration error: the validator had no policy-override secret, so a legitimate server configuration IllegalStateException was incorrectly classified as an untrusted-input invariant failure. The fuzz fixture now uses a deterministic test-only secret, keeping the campaign scoped to attacker-controlled fields.
  • The follow-up crash input is preserved in the Jazzer regression corpus.

Verification:

  • Focused regression: red as NullPointerException before the fix, green after it.
  • Jazzer: 7,290,470 executions in 61 seconds, 0 findings.
  • Full Maven suite: 418 tests, 0 failures, 0 errors, 0 skipped.
  • Javadoc generation: pass.
  • CodeGraph synchronized current validation, fuzz, and conversion call paths.

Please evaluate only current SHA 7f9ef32.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants