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++.
Install with skills.sh (any agent)
npx skills add testland/qa --skill go-native-fuzzinggo-native-fuzzing
Overview
Per go.dev/doc/security/fuzz (opens in new window), unlike libFuzzer / AFL++, Go's native fuzzer is:
For sanitiser pairing: Go uses the race detector (-race) as its TSan-equivalent; ASan-equivalent comes via gcflags (limited).
For corpus discipline see corpus-management-reference.
When to use
For binary-level fuzzing of Go programs, AFL++ in -Q mode also works.
Authoring
Define a fuzz target
In any _test.go file alongside your unit tests:
package parser
import "testing"
func FuzzParseQuery(f *testing.F) {
f.Add("SELECT * FROM users WHERE id = 1")
f.Add("INSERT INTO foo VALUES (1, 'bar')")
f.Add("")
f.Fuzz(func(t *testing.T, q string) {
result, err := ParseQuery(q)
if err != nil {
return
}
if result == nil {
t.Fatalf("ParseQuery returned nil result with no error for %q", q)
}
})
}Per go.dev/doc/security/fuzz (opens in new window):
Supported parameter types
Go's fuzzer supports these types as fuzz parameters:
| Type | Notes |
|---|---|
[]byte | Variable-length byte slices |
string | Variable-length strings |
bool | Single byte |
byte, rune | Integers |
int, int8, int16, int32, int64 | Signed integers |
uint, uint8, uint16, uint32, uint64 | Unsigned integers |
float32, float64 | Floats |
Multi-parameter functions are fuzzed jointly:
f.Fuzz(func(t *testing.T, port int, host string, body []byte) {
handleRequest(host, port, body)
})The fuzzer mutates all parameters together.
Seed corpus
Two sources for seeds:
The seed file format (per Go docs):
go test fuzz v1
string("SELECT * FROM users WHERE id = 1")
int(42)For multi-parameter targets, each line corresponds to a parameter in order.
Failure auto-save
When go test -fuzz=Xxx finds a failure, it writes the failing input to testdata/fuzz/FuzzXxx/<sha256>. On the next go test run, this file becomes a regular regression test that must pass - no more -fuzz flag needed.
This is the unique strength of Go's approach: failing inputs become permanent regression coverage as part of the test fixture.
Running
Fuzz a target
# Run the unit tests + seeds (no exploration)
go test ./parser/
# Fuzz a specific target for 30 seconds
go test -fuzz=FuzzParseQuery -fuzztime=30s ./parser/
# Fuzz indefinitely (CI long-running)
go test -fuzz=FuzzParseQuery ./parser/Common flags
| Flag | Effect |
|---|---|
-fuzz=NAME | Run the fuzz target with the given name |
-fuzztime=DURATION | Stop after duration (e.g., 30s, 1h) or -1 for indefinite |
-fuzzminimizetime=DURATION | Time spent minimising failures (default 1m) |
-fuzzcachedir=PATH | Where to cache mutations (default $GOCACHE/fuzz/) |
-parallel=N | Concurrent workers |
-race | Enable race detector (TSan-equivalent) |
Race detector
Pair fuzzing with the race detector for thread-safety bugs:
go test -race -fuzz=FuzzConcurrentAccess -fuzztime=10m ./...Parsing results
When a failure occurs, Go prints:
--- FAIL: FuzzParseQuery (3.45s)
--- FAIL: FuzzParseQuery/c1d4e1...
fuzz: minimizing 50-byte failing input file
--- FAIL: FuzzParseQuery (0.00s)
parser_test.go:18: ParseQuery returned nil result with no error for "..."
Failing input written to testdata/fuzz/FuzzParseQuery/c1d4e1abc...
To re-run:
go test -run=FuzzParseQuery/c1d4e1abc... ./parser/Per the Go docs, the failing input lives at testdata/fuzz/FuzzXxx/<hash> - commit it as part of the fix to lock in regression coverage.
Reproducing a saved failure
go test -run=FuzzParseQuery/c1d4e1abc... ./parser/This treats the saved file as a regular t.Run sub-test, no fuzzing.
CI integration
- uses: actions/setup-go@v5
with: { go-version: '1.22' }
- name: Run tests (incl. seeds)
run: go test -race ./...
- name: Smoke fuzz (3 min per target)
run: |
for target in $(grep -rh "^func Fuzz" --include="*_test.go" | \
awk '{print $2}' | sed 's/(.*//'); do
echo "Fuzzing $target"
go test -fuzz=$target -fuzztime=180s ./... || true
done
- name: Commit any new regression fixtures
if: always()
run: |
if git diff --quiet testdata/; then exit 0; fi
git config user.name "fuzz-bot"
git config user.email "fuzz-bot@example.com"
git add testdata/
git commit -m "Add fuzz failure fixtures"
# PR or push - per team conventionAnti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Missing f.Add calls | Fuzzer starts from empty corpus; slow path discovery | Add 3-10 representative seeds |
Skipping t.Fatalf for invariant violations | Fuzzer can't detect logical bugs | Assert invariants explicitly |
Not committing testdata/fuzz/ | Lose regression coverage on next run | Commit alongside the fix |
-fuzztime=10s in CI | Too short to find anything new | Use 1-5 min smoke; long campaigns separate |
| One huge fuzz target | Slow iteration; unclear coverage attribution | Split per function |
Fuzz target without -race | Misses concurrency bugs in concurrent code | go test -race -fuzz=... |
Ignoring testdata/ after CI fuzz | New regression fixtures lost | Commit + PR them automatically |
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.
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.
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.
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.