Testland
Browse all skills & agents

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-reference
View source

corpus-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

  • Bootstrapping a new fuzz target - what should the seed corpus contain?
  • Running a long-running fuzz campaign - when to minimise, where to back up, how to share across CI runs.
  • Debugging a crash - locating the crash artefact, reproducing it.
  • Migrating between fuzzers (libFuzzer ↔ AFL++) - corpus format compatibility.

Corpus components

A fuzzing corpus has three roles:

RoleWhat it isWhere it lives
Seed corpusHand-curated initial inputs covering known interesting pathsVersioned in repo (fuzz/seeds/)
Evolved corpusInputs the fuzzer added because they hit new coverageOutput directory (fuzz/corpus/) - typically not committed
Crash artefactsInputs that triggered a sanitiser / crash / timeoutOutput 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 binary

Invocation:

./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)
        seedfile2

Failures 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_1

Atheris (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.zip

Per google.github.io/oss-fuzz (opens in new window).

The corpus syncs to gs://<project>-corpus.clusterfuzz-external.appspot.com/.

Seed corpus construction strategies

StrategyWhenExample
From spec keywordsNew target, no inputs existExtract JSON keywords from a JSON parser spec, write each as a tiny file
From test fixturesExisting unit-test inputs cover pathsCopy fixtures from tests/fixtures/*.json to seeds/
From production dataMature target, prod logs availableSample 1000 prod requests, strip PII per pii-categories-reference, seed
From corpus minimisationReduce a large corpus to its coverage-equivalent coreRun afl-cmin or libFuzzer -merge=1
From OSS-Fuzz cousinSame format, different targetReuse <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:

  1. Reproduce locally:
    ./fuzz_target crash-<sha1>
    # Triggers the same sanitiser report
    
  2. Minimise the crash input (see above) so the bug report is small.
  3. File the bug via bug-report-from-failure with the minimised crash as an attachment.
  4. Add the original (non-minimised) crash to the seed corpus as a regression test - re-runs will catch reintroduction.

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-patternWhy it failsFix
No seed corpusFuzzer wanders randomly; takes hours to find shallow bugsAlways provide 5-50 hand-curated seeds
Committing evolved corpus to repoRepo bloats; PR diffs hide signalPersist via CI cache or object storage
Mixing seed and evolved corpus in one directoryLoses provenanceSeparate directories; seeds read-only
No dictionary for structured formatsFuzzer spends cycles re-discovering keywordsAlways provide a dict for JSON / XML / protobuf / SQL
Never minimisingCycle time grows; coverage redundantMinimise weekly or per major change
Crash artefact deleted after fixLose regression coverageAdd minimised crash to seed corpus
Single fuzzer assumedDifferent fuzzers find different bugsRun libFuzzer + AFL++ on same target periodically
Sharing prod-sourced corpus without PII reviewGDPR / HIPAA leakPass corpus through PII detection before sharing

Limitations

  • Corpus format incompatibility. libFuzzer / AFL++ / cargo-fuzz corpora aren't directly swap-compatible; cross-fuzzer testing needs conversion.
  • Corpus rot. Evolved corpora are tied to a specific binary's coverage instrumentation; rebuilding the target may invalidate some coverage information.
  • Dictionary maintenance. Dictionaries should evolve with the target's grammar - manual upkeep.
  • Corpus storage. Long campaigns produce GB-scale corpora; needs deliberate storage strategy (S3 / GCS / artifacts).
  • No semantic seeding for opaque formats. Custom binary formats may need a generator to bootstrap coverage.

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.