corpus-management-reference
Pure-reference catalog of fuzz-corpus management practices. Defines what a corpus is (seed corpus + evolved corpus saved by the fuzzer), corpus directory layout per libFuzzer / AFL++ / Go native / cargo-fuzz / OSS-Fuzz, the canonical crash-artefact naming (crash-{sha1} / leak-{sha1} / timeout-{sha1}), seed corpus construction strategies (sample-from-prod, sample-from-test-fixtures, from-spec-keywords), corpus minimisation, dictionary files, and the OSS-Fuzz integration corpus sync. Use as the corpus-discipline reference when building a fuzz target or maintaining a long-running fuzz campaign.
Install with skills.sh (any agent)
npx skills add testland/qa --skill corpus-management-referencecorpus-management-reference
Overview
Pure-reference catalog of corpus-management practices across libFuzzer, AFL++, native Go fuzz, cargo-fuzz, Atheris, Jazzer, and OSS-Fuzz. Consumed by the per-language fuzzer skills and the fuzz-target authoring agent.
When to use
Corpus components
A fuzzing corpus has three roles:
| Role | What it is | Where it lives |
|---|---|---|
| Seed corpus | Hand-curated initial inputs covering known interesting paths | Versioned in repo (fuzz/seeds/) |
| Evolved corpus | Inputs the fuzzer added because they hit new coverage | Output directory (fuzz/corpus/) - typically not committed |
| Crash artefacts | Inputs that triggered a sanitiser / crash / timeout | Output directory + bug-report attachments |
Directory layout per fuzzer
libFuzzer
Per llvm.org/docs/LibFuzzer.html (opens in new window):
fuzz/
fuzz_target.cc # the harness
seeds/ # initial inputs (read on startup)
corpus/ # evolved corpus (read + written)
fuzz_target # compiled binaryInvocation:
./fuzz_target -max_total_time=3600 corpus/ seeds/The first directory listed is the output corpus (writable); subsequent directories are read-only seeds.
Crash artefacts saved to current directory as crash-<sha1>, leak-<sha1>, timeout-<sha1> per LLVM docs.
AFL++
fuzz/
inputs/ # seed corpus
output/ # AFL++ output (queue/, crashes/, hangs/)
fuzz_target # AFL-instrumented binary (afl-clang-fast)Invocation:
afl-fuzz -i inputs/ -o output/ -- ./fuzz_target @@Crash format: output/default/crashes/id:<num>,sig:<signal>,src:<id>,op:<mutator>,....
AFL++ corpora are not directly compatible with libFuzzer - the queue files have a different on-disk format. Convert via afl-fuzz -i + afl-cmin round-trip.
Go native (go test -fuzz)
Per go.dev/doc/security/fuzz (opens in new window):
package/
fuzz_test.go # contains FuzzXxx functions
testdata/
fuzz/
FuzzXxx/
seedfile1 # seed inputs (hand-curated)
seedfile2Failures auto-write to testdata/fuzz/FuzzXxx/ with a generated filename - they're meant to be committed as regression cases.
cargo-fuzz
fuzz/
Cargo.toml
fuzz_targets/
fuzz_target_1.rs # the harness (one per fuzz target)
corpus/
fuzz_target_1/ # per-target corpus
artifacts/
fuzz_target_1/
crash-<sha1>Invocation:
cargo fuzz run fuzz_target_1Atheris (Python)
fuzz/
fuzz_target.py # uses atheris.Setup + atheris.Fuzz
corpus/Invocation (Atheris uses libFuzzer's CLI under the hood):
python fuzz_target.py corpus/Jazzer (JVM)
fuzz/
FuzzTarget.java # with @FuzzTest annotation
corpus/Invocation:
jazzer --cp=target/test-classes \
--target_class=com.example.FuzzTarget \
corpus/OSS-Fuzz
OSS-Fuzz aggregates corpora across Google's infrastructure:
oss-fuzz/
projects/<project>/
Dockerfile # build the fuzzer
build.sh # produces $OUT/fuzz_target_1, $OUT/fuzz_target_1_seed_corpus.zipPer google.github.io/oss-fuzz (opens in new window).
The corpus syncs to gs://<project>-corpus.clusterfuzz-external.appspot.com/.
Seed corpus construction strategies
| Strategy | When | Example |
|---|---|---|
| From spec keywords | New target, no inputs exist | Extract JSON keywords from a JSON parser spec, write each as a tiny file |
| From test fixtures | Existing unit-test inputs cover paths | Copy fixtures from tests/fixtures/*.json to seeds/ |
| From production data | Mature target, prod logs available | Sample 1000 prod requests, strip PII per pii-categories-reference, seed |
| From corpus minimisation | Reduce a large corpus to its coverage-equivalent core | Run afl-cmin or libFuzzer -merge=1 |
| From OSS-Fuzz cousin | Same format, different target | Reuse <format>_seed_corpus.zip from a related OSS-Fuzz project |
A seed corpus of 5-50 hand-curated diverse inputs is typically enough to bootstrap. Bigger isn't always better - the fuzzer finds new paths via mutation.
Dictionary files
A dictionary lists tokens the fuzzer prefers when mutating. For a JSON parser:
# fuzz.dict
"{"
"}"
"["
"]"
"true"
"false"
"null"
"\":\""Invocation: libFuzzer -dict=fuzz.dict or afl-fuzz -x fuzz.dict.
Dictionaries dramatically improve fuzzer effectiveness on structured formats (JSON, XML, protobuf, SQL).
Corpus minimisation
A corpus that grows over time becomes redundant - many inputs hit the same coverage. Minimise periodically:
# libFuzzer merge mode
mkdir minimised/
./fuzz_target -merge=1 minimised/ corpus/
# AFL++ minimisation
afl-cmin -i corpus/ -o minimised/ -- ./fuzz_target @@
# Per-input minimisation (find smallest input triggering same coverage)
afl-tmin -i crash-input -o min-crash-input -- ./fuzz_target @@Minimisation reduces fuzz cycle time + reproducibility surface.
Crash artefact handling
When the fuzzer finds a crash:
CI integration
Long-running fuzz campaigns run continuously; CI runs short "smoke fuzz" campaigns (~5 min):
- name: Smoke fuzz
run: ./fuzz_target -max_total_time=300 corpus/ seeds/
Persist evolved corpus to a CI cache so coverage accumulates across runs:
- uses: actions/cache@v4
with:
path: corpus/
key: fuzz-corpus-${{ github.sha }}
restore-keys: fuzz-corpus-
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| No seed corpus | Fuzzer wanders randomly; takes hours to find shallow bugs | Always provide 5-50 hand-curated seeds |
| Committing evolved corpus to repo | Repo bloats; PR diffs hide signal | Persist via CI cache or object storage |
| Mixing seed and evolved corpus in one directory | Loses provenance | Separate directories; seeds read-only |
| No dictionary for structured formats | Fuzzer spends cycles re-discovering keywords | Always provide a dict for JSON / XML / protobuf / SQL |
| Never minimising | Cycle time grows; coverage redundant | Minimise weekly or per major change |
| Crash artefact deleted after fix | Lose regression coverage | Add minimised crash to seed corpus |
| Single fuzzer assumed | Different fuzzers find different bugs | Run libFuzzer + AFL++ on same target periodically |
| Sharing prod-sourced corpus without PII review | GDPR / HIPAA leak | Pass corpus through PII detection before sharing |
Limitations
References
Related skills
afl-plus-plus
Author and run AFL++ - out-of-process coverage-guided fuzzer (a community fork of Google's original AFL with improved mutations and instrumentation). Covers afl-cc / afl-clang-fast instrumented build, afl-fuzz invocation, parallel master/slave (-M / -S), dictionary support (-x), QEMU mode (-Q) for binaries without source, output structure (queue / crashes / hangs), crash minimisation (afl-tmin), corpus minimisation (afl-cmin), crash filename triage, and CI integration. Use for fuzzing standalone binaries (file processors, command-line tools) where libFuzzer's in-process model doesn't fit; for cross-fuzzer corpus strategy see corpus-management-reference.
atheris-python-fuzzing
Author and run Atheris - Google's Python coverage-guided fuzzer built on libFuzzer. Covers pip installation, atheris.Setup + atheris.Fuzz invocation, TestOneInput(data: bytes) target signature, FuzzedDataProvider for structured input, instrument_imports() / instrument_func decorators for coverage instrumentation, and libFuzzer-passthrough flags (-atheris_runs, -max_total_time, -dict). Use for fuzzing Python libraries - also supports CPython native-extension fuzzing.
cargo-fuzz-rust
Author and run cargo-fuzz - Rust fuzzing via libFuzzer with cargo integration. Covers `cargo install cargo-fuzz`, `cargo fuzz init` + `cargo fuzz add {target}` for harness scaffolding, the `fuzz_target!` macro for entry-point declaration, the `Arbitrary` trait for structured input mutation, and `cargo fuzz run` invocation. Requires Rust nightly. Use for fuzz testing Rust libraries - cargo-fuzz wraps libFuzzer with native Rust ergonomics.
crash-triage-reference
Pure-reference catalog for manually triaging individual fuzzer crash artifacts - reading ASan, UBSan, and MSan output; classifying findings as LIKELY-EXPLOITABLE, MEDIUM, or BENIGN; deduplicating by stack-hash; and minimizing reproducers with -minimize_crash. Use when you need to understand what a specific crash means, build exploitability intuition, or manually work a small set of findings. For automated bulk triage across a full artifact directory, run automated findings triage instead.
fuzz-tool-selector
Routes a fuzz-target authoring task to the right fuzzer for the detected language and build type. Decision tree: C/C++ → libfuzzer-cpp + afl-plus-plus; Rust → cargo-fuzz-rust (or libfuzzer-cpp via FFI); Go → go-native-fuzzing; Python → atheris-python-fuzzing; JVM → jazzer-jvm-fuzzing; closed-source binary → afl-plus-plus in QEMU mode; mature open-source project → ossfuzz-integration. Use when a project needs coverage-guided fuzzing and no fuzzer has been chosen for its language or toolchain yet.
go-native-fuzzing
Author and run Go's native fuzzing (Go 1.18+) - coverage-guided fuzzing built into the standard testing package via FuzzXxx functions. Covers f.Add seed-corpus declaration, f.Fuzz callback signature with typed parameters, testdata/fuzz/{FuzzXxx}/ directory layout for seeds + regression cases, the -fuzz flag for `go test`, and CI integration via short smoke runs. Use for fuzz testing Go libraries - Go's native approach integrates seamlessly with standard `go test` rather than requiring a separate toolchain like AFL++.
jazzer-jvm-fuzzing
Author and run Jazzer - Code Intelligence's JVM coverage-guided fuzzer built on libFuzzer. Covers Maven / Gradle / standalone JAR installation, the @FuzzTest annotation (JUnit 5 integration), typed parameter mutation (String, primitives, byte[]), built-in JVM sanitisers (SSRF / path traversal / OS command injection / deserialization gadget / ReDoS), and the JAZZER_FUZZ=1 env var to switch between regression and fuzzing modes. Use for fuzz testing Java / Kotlin libraries - particularly effective against parsing, deserialization, and HTTP-handling code.
libfuzzer-cpp
Author and run LLVM libFuzzer for C/C++ - in-process coverage-guided fuzzing. Covers harness authoring (LLVMFuzzerTestOneInput entry point), build with -fsanitize=fuzzer,address,undefined, runtime flags (-max_total_time, -runs, -dict, -fork, -workers), corpus + crash-artefact handling, and CI integration. Use for libraries / parsers / decoders in C/C++ where in-process fuzzing of a function is the right scope. Compose with ASan + UBSan from sanitiser-integration-reference and corpus discipline from corpus-management-reference.
ossfuzz-integration
Author and submit a project to Google OSS-Fuzz - the open-source continuous fuzzing service that runs libFuzzer / AFL++ / Honggfuzz campaigns on Google infrastructure 24x7. Covers the project.yaml + Dockerfile + build.sh contract, the $OUT/$WORK conventions, supported languages + sanitisers, seed-corpus + dictionary submission, the OSS-Fuzz Build Status dashboard, and the disclosure SLA (issues filed in Monorail with 90-day deadline). Use to offload long-running fuzz campaigns to dedicated infrastructure rather than self-hosting.
sanitiser-integration-reference
Pure-reference catalog of compiler sanitisers used with fuzz testing - AddressSanitizer (ASan), UndefinedBehaviorSanitizer (UBSan), MemorySanitizer (MSan), ThreadSanitizer (TSan), and LeakSanitizer (LSan). Explains what each detects, compatibility (can ASan + UBSan combine? - yes; ASan + MSan? - no), build flags, runtime options (ASAN_OPTIONS / UBSAN_OPTIONS env vars), and the typical ~2x slowdown per ASan. Use to pick the right sanitiser per fuzz target, configure the build, and interpret crash reports.