Skip to content

test(fuzz): coverage-guided fuzzing for untrusted-input surfaces#37

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

test(fuzz): coverage-guided fuzzing for untrusted-input surfaces#37
seonghobae wants to merge 4 commits into
mainfrom
feat/add-fuzzing

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

Summary

Adds coverage-guided fuzzing (cargo-fuzz / libFuzzer) plus a stable proptest mirror for the gateway's attacker-controlled and untrusted-config surfaces. Targets were selected with CodeGraph (codegraph explore), which surfaced the request scorer, the persisted-state deserializer, the admin-token config parser, and the DNSBL zone generator as the reachable, no-covering-test entry points for arbitrary input.

Fuzz targets (fuzz/)

Target Surface Invariants
fuzz_score_request waf_ids_core::score_request no panic; reason non-empty; deterministic scoring
fuzz_appdata_json serde_json::from_str::<AppData> (state file) no panic; serde round-trip stable
fuzz_parse_admin_tokens waf_ids_ai_soc::parse_admin_tokens (ADMIN_TOKENS) no panic; no empty token key/actor
fuzz_dnsbl_zone export_dnsbl_zone / validate_dnsbl no panic; TXT payloads fully escaped (no zone break-out)

Each target ships a seed corpus of real attack payloads (SQLi, XSS, traversal, cmd-injection, SSRF, JNDI, percent-encoded) and malformed/edge-case inputs.

Keeping primary CI green

  • The fuzz/ crate is an isolated cargo workspace, so cargo test --workspace at the repo root never builds it (no nightly needed for normal CI).
  • crates/waf-ids-core/tests/fuzz_invariants.rs and tests/fuzz_invariants.rs re-assert the same invariants via proptest on stable, so they run in the existing test job. cargo fmt --check, cargo test --locked --workspace, and cargo clippy --locked --workspace --all-targets -- -D warnings all pass.

CI job

.github/workflows/fuzz.yml runs each target on a bounded budget — 60s/target on PRs, 300s/target nightly (cron) + manual dispatch — uploading crash artifacts on failure, so fuzzing cost stays predictable.

Licensing

All added dependencies are permissive (MIT OR Apache-2.0): cargo-fuzz, libfuzzer-sys, arbitrary, proptest. No GPL/AGPL. No runtime secrets. Fuzzing survey paper added under docs/papers/; usage in docs/fuzzing.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P

Add cargo-fuzz (libFuzzer) harnesses plus a stable proptest mirror for the
gateway's attacker-controlled and untrusted-config surfaces, identified with
CodeGraph as the reachable, untested entry points for arbitrary input:

- fuzz_score_request      -> waf_ids_core::score_request (request scorer:
  percent-decode + signature/threat/DNSBL/anomaly matching)
- fuzz_appdata_json       -> serde_json::from_str::<AppData> (persisted state file)
- fuzz_parse_admin_tokens -> waf_ids_ai_soc::parse_admin_tokens (ADMIN_TOKENS config)
- fuzz_dnsbl_zone         -> export_dnsbl_zone / validate_dnsbl (zone-file escaping)

Each target asserts no panic on arbitrary input plus surface-specific
invariants (non-empty reason, deterministic scoring, serde round-trip, no empty
token/actor, fully-escaped TXT payloads), and ships a seed corpus of real
attack payloads and edge cases.

The fuzz crate is an isolated workspace so `cargo test --workspace` at the root
is unaffected. crates/waf-ids-core/tests/fuzz_invariants.rs and
tests/fuzz_invariants.rs re-assert the same invariants via proptest on stable,
so they stay green in primary CI without a nightly toolchain.

CI: .github/workflows/fuzz.yml runs each target for a bounded budget (60s/target
on PRs, 300s nightly) to keep cost predictable. All added deps are permissive
(cargo-fuzz, libfuzzer-sys, arbitrary, proptest: MIT OR Apache-2.0). Docs in
docs/fuzzing.md; fuzzing survey paper in docs/papers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P
Comment thread .github/workflows/fuzz.yml Outdated
Comment thread .github/workflows/fuzz.yml Outdated
Comment thread .github/workflows/fuzz.yml Outdated
Comment thread .github/workflows/fuzz.yml Outdated
Seongho Bae and others added 2 commits July 8, 2026 23:00
Fixes the fuzz_dnsbl_zone crash and restores the coverage-evidence gate to
100% line/function coverage for PR #37.

Real bug (fuzz crash): export_dnsbl_zone rendered the untrusted `origin`
and each entry `code` into the zone file verbatim. A newline plus double
quotes in `origin` (or a non-IP `code`) broke out of the generated zone and
produced TXT records with unescaped quotes, violating the fuzz invariant.

- Sanitize the zone origin to DNS-safe characters (LDH, `_`, `.`); empty
  input falls back to the reserved `.invalid` TLD.
- Require each DNSBL response `code` to parse as an IP literal, re-rendering
  the canonical form and skipping entries that do not.
- Escape control characters (including raw newlines) in TXT payloads as BIND
  decimal escapes so a crafted reason/source cannot inject extra zone lines.
- Add regression tests reproducing the crash shape and the escaping paths.

Coverage-evidence (workspace 100% line/function gate): move the env-parsing
helpers into the tested library, add `run_from_env` (bound + served via an
ephemeral listener under an immediate shutdown in-process), reduce main.rs to
a thin shim covered end-to-end by tests/binary.rs (graceful SIGTERM shutdown),
and add unit tests for every configuration branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P
…case

Adds the exact libFuzzer crash input (newline + quotes in `origin`) to the
fuzz_dnsbl_zone corpus so CI replays it and proves the zone-injection fix
holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P
@seonghobae seonghobae enabled auto-merge (squash) July 9, 2026 01:21
@opencode-agent opencode-agent Bot disabled auto-merge July 11, 2026 11:57
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