| name | secure-fuzz-testing |
| description | Coverage-guided fuzzing with Atheris, cargo-fuzz, and native Go fuzzing, combined with compilers/sanitizers (ASan, MSan, UBSan) for security validation. Use whenever the user wants to fuzz test code, find memory safety bugs, validate parsers/deserializers against malformed input, or harden security-critical code paths. Trigger on mentions of fuzzing, fuzz testing, AddressSanitizer, libFuzzer, cargo-fuzz, Atheris, go test -fuzz, or security validation of parsing/deserialization logic.
|
Secure Fuzz Testing
Coverage-guided fuzzing across Python, Rust, and Go with sanitizer integration.
Why Fuzz
Fuzzing finds bugs unit tests miss: crashes, panics, memory corruption, infinite loops, and
logic errors triggered by malformed or unexpected input. Critical for any code that parses
untrusted input — file formats, network protocols, deserializers, APIs.
Python Fuzzing with Atheris
Setup
uv add --dev atheris
Basic Fuzz Harness
import atheris
import sys
with atheris.instrument_imports():
from myapp.parser import parse_config
def TestOneInput(data: bytes) -> None:
fdp = atheris.FuzzedDataProvider(data)
text = fdp.ConsumeUnicodeNoSurrogates(fdp.remaining_bytes())
try:
parse_config(text)
except (ValueError, KeyError):
pass
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
python fuzz/fuzz_parser.py
mkdir corpus
python fuzz/fuzz_parser.py corpus/
python fuzz/fuzz_parser.py -max_total_time=300
python fuzz/fuzz_parser.py crash-<hash>
Structured Fuzzing (JSON-like inputs)
import atheris
import json
def TestOneInput(data: bytes) -> None:
fdp = atheris.FuzzedDataProvider(data)
payload = {
"user_id": fdp.ConsumeIntInRange(-1000, 1000000),
"name": fdp.ConsumeUnicodeNoSurrogates(50),
"active": fdp.ConsumeBool(),
"tags": [fdp.ConsumeUnicodeNoSurrogates(20) for _ in range(fdp.ConsumeIntInRange(0, 5))],
}
try:
validate_user_payload(payload)
except ValidationError:
pass
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
Rust Fuzzing with cargo-fuzz
Setup
cargo install cargo-fuzz
cargo fuzz init
Fuzz Target
#![no_main]
use libfuzzer_sys::fuzz_target;
use myapp::parser::parse_config;
fuzz_target!(|data: &[u8]| {
if let Ok(s) = std::str::from_utf8(data) {
let _ = parse_config(s);
}
});
Structured Fuzzing with arbitrary
#![no_main]
use libfuzzer_sys::fuzz_target;
use arbitrary::Arbitrary;
#[derive(Arbitrary, Debug)]
struct UserPayload {
id: u32,
name: String,
age: u8,
tags: Vec<String>,
}
fuzz_target!(|payload: UserPayload| {
let _ = validate_user(&payload);
});
[dependencies]
libfuzzer-sys = "0.4"
arbitrary = { version = "1", features = ["derive"] }
myapp = { path = ".." }
Running with Sanitizers
cargo fuzz run parse_input
cargo fuzz run parse_input -- -max_total_time=300
cargo fuzz run parse_input fuzz/corpus/parse_input/
RUSTFLAGS="-Z sanitizer=memory" cargo fuzz run parse_input
cargo fuzz tmin parse_input fuzz/artifacts/parse_input/crash-<hash>
cargo fuzz run parse_input fuzz/artifacts/parse_input/crash-<hash>
Go Native Fuzzing
Fuzz Test (Go 1.18+, built-in)
package parser
import "testing"
func FuzzParseConfig(f *testing.F) {
f.Add("key=value")
f.Add("")
f.Add("key=")
f.Add(`key="quoted value"`)
f.Fuzz(func(t *testing.T, input string) {
result, err := ParseConfig(input)
if err != nil {
return
}
if result == nil {
t.Errorf("ParseConfig returned nil with no error for input: %q", input)
}
serialized := result.Serialize()
reparsed, err := ParseConfig(serialized)
if err != nil {
t.Errorf("Failed to reparse serialized output: %v", err)
}
if !reparsed.Equals(result) {
t.Errorf("Round-trip mismatch for input: %q", input)
}
})
}
go test -fuzz=FuzzParseConfig -fuzztime=60s
go test -fuzz=FuzzParseConfig -fuzztime=60s -gcflags=all=-d=checkptr
Sanitizers Reference
| Sanitizer | Detects | Use With |
|---|
| ASan (AddressSanitizer) | Buffer overflows, use-after-free, double-free | C/C++/Rust |
| MSan (MemorySanitizer) | Uninitialized memory reads | C/C++/Rust (Clang) |
| UBSan (UndefinedBehaviorSanitizer) | Integer overflow, null deref, type confusion | C/C++/Rust |
| TSan (ThreadSanitizer) | Data races, deadlocks | Concurrent code |
C/C++ with libFuzzer + Sanitizers
clang++ -g -O1 -fsanitize=address,fuzzer,undefined \
-fsanitize-address-use-after-scope \
fuzz_target.cc -o fuzz_target
./fuzz_target -max_total_time=300 corpus/
Building Effective Fuzz Harnesses
Good Harness Checklist
What to Fuzz First (Priority Order)
- Parsers — JSON, YAML, config files, binary formats
- Deserializers — anything from
bytes → struct
- Network protocol handlers — anything reading from sockets
- Compression/decompression — zip, gzip bombs, decompression ratio attacks
- Auth token / JWT parsing — malformed tokens shouldn't panic
- Regular expressions — catastrophic backtracking (ReDoS)
- Path/URL handling — path traversal via malformed input
CI Integration
name: Fuzz Testing
on:
schedule:
- cron: '0 2 * * *'
pull_request:
paths:
- 'src/parser/**'
jobs:
fuzz-rust:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- run: cargo install cargo-fuzz
- name: Run fuzz targets (5 min each)
run: |
for target in $(cargo fuzz list); do
cargo fuzz run $target -- -max_total_time=300
done
fuzz-go:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- name: Run Go fuzz tests
run: go test -fuzz=Fuzz -fuzztime=60s ./...
Triaging Fuzz Findings
cargo fuzz tmin <target> <crash-file>
RUST_BACKTRACE=full cargo fuzz run <target> <crash-file>
Key Rules
- Fuzz parsers and deserializers first — highest bug density, highest risk
- Catch expected errors in the harness — don't waste cycles on known-invalid input paths
- Always minimize crashes before filing/fixing — smaller reproducers are easier to debug
- Run ASan by default — it's nearly free and catches the most dangerous bugs
- Seed corpus matters — start fuzzing from realistic inputs, not just empty/random
- Check invariants, not just crashes — round-trip properties catch logic bugs
- Time-box CI fuzzing — 1-5 min per target in PR CI, longer runs nightly
- Every fixed crash becomes a permanent corpus entry — prevents regression
- Fuzz before every major release — especially after touching parsing code
- Treat OOM and timeouts as bugs too — not just crashes/panics