Fuzz Testing skill for Codex
Throw a million inputs at your parser first. Codex finds the code that parses untrusted bytes, writes fast deterministic fuzz harnesses that check invariants such as round-trip equality, and builds C and C++ targets with AddressSanitizer and UBSan. Crashes are reproduced, minimised, deduplicated and turned into regression tests, and a short fuzz run plus corpus replay is added to CI with longer scheduled runs.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- fuzz-testing Installs to ~/.agents/skills/fuzz-testing/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install fuzz-testing The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project fuzz-testing Installs to .agents/skills/fuzz-testing/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/fuzz-testing.zipunzip -o fuzz-testing.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) fuzz-testing PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $fuzz-testing, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Sets up coverage-guided fuzzing - Go native fuzzing (testing.F), libFuzzer harnesses with AddressSanitizer for C and C++, cargo-fuzz for Rust, Atheris for Python, Jazzer for the JVM - picking targets (parsers, decoders, protocol handlers), writing harnesses with structured input and invariants, seed corpora and dictionaries, triaging and minimising crashes into regression tests, and running fuzzers in CI with ClusterFuzzLite or OSS-Fuzz. Use when code parses untrusted input, when asked to fuzz a function or file format, or when a crash input needs to be reduced and fixed.
Use it when
- Code parses files, network frames or other untrusted input
- You want long unattended bug hunting on a decoder or protocol handler
- A fuzzer crash needs triage, minimisation and a regression test
- Adding fuzzing to CI or preparing an OSS-Fuzz integration
Use something else when
- Inputs are typed domain values checked inside the normal test suite: use property-based-testing
- You are auditing C memory handling by reading code: use c-memory-safety
How to check the result
A deliberately injected off-by-one is found within minutes, harness throughput stays in the thousands of executions per second, and every fixed crash input replays as a passing regression test.
The mistake it prevents
Fuzzing C or C++ without AddressSanitizer, or catching every exception in the harness, so real memory corruption and logic bugs never surface as crashes.
Fuzzing pays off on code that consumes untrusted bytes. Done well means: a harness that reaches deep logic quickly, sanitizers that turn silent memory corruption into crashes, invariants that turn wrong answers into failures, a committed seed corpus, every crash minimised into a regression test, and a time-boxed fuzz run in CI.
Inspect first
- Read
AGENTS.mdand the build system. Find existing fuzz targets:rg -n "func Fuzz|LLVMFuzzerTestOneInput|fuzz_target!|atheris|@FuzzTest" . - Rank candidate targets by exposure and complexity:
- parsers and decoders (file formats, image/font/archive handling, protocol frames)
- deserialisers, template engines, query or expression parsers, regex-like matchers
- round-trip pairs (encode/decode, compress/decompress, serialise/parse)
- code with hand-written bounds arithmetic, especially in C, C++ and
unsafeRust
- Check the toolchain: clang with
-fsanitize=fuzzer(Apple’s Xcode clang does not ship libFuzzer; use Homebrew LLVM or Linux), Go 1.18+, nightly Rust for cargo-fuzz, a Python version Atheris has wheels for. - Installing toolchains or corpora needs network; fuzzing itself runs offline.
Choose the engine
| Language | Engine | Run |
|---|---|---|
| Go | Native testing.F | go test -run='^$' -fuzz='^FuzzParse$' -fuzztime=5m ./pkg/parser |
| C / C++ | libFuzzer + ASan/UBSan (AFL++ for binaries without source changes) | ./fuzz_parse corpus/ -max_total_time=300 |
| Rust | cargo-fuzz (libFuzzer) | cargo +nightly fuzz run parse -- -max_total_time=300 |
| Python | Atheris | python fuzz_parse.py corpus/ -max_total_time=300 |
| Java / Kotlin | Jazzer (JUnit @FuzzTest) | JAZZER_FUZZ=1 ./gradlew test --tests '*FuzzTest' |
| Wide-input properties in any language | Property-based testing | see property-based-testing |
Fuzzing and property-based testing overlap: use fuzzing for byte-level inputs and long unattended runs guided by coverage; use property tests for typed domain inputs inside the normal test suite.
Harness rules
- Deterministic: no clock, randomness, network or global state that survives between inputs.
- Fast: thousands of executions per second. Avoid logging, disk I/O and expensive setup per input; initialise once.
- Never exit or panic on invalid input as a matter of course: reject it and return. A panic should mean a bug.
- Check invariants, not only crashes: round-trip equality, output size bounds, no exception types other than the documented ones.
- Bound input size (
-max_len) and recursion so timeouts point at real algorithmic bugs.
Go
func FuzzParseHeader(f *testing.F) {
f.Add([]byte("GET / HTTP/1.1\r\nHost: a\r\n\r\n"))
f.Add([]byte(""))
f.Fuzz(func(t *testing.T, data []byte) {
h, err := ParseHeader(data)
if err != nil {
return // invalid input is fine; a panic is not
}
out := h.Marshal()
h2, err := ParseHeader(out)
if err != nil || !h.Equal(h2) {
t.Fatalf("round trip failed: %q -> %q (%v)", data, out, err)
}
})
}
Failing inputs are written to testdata/fuzz/FuzzParseHeader/<hash>; commit them, and plain
go test replays them as regression cases from then on. Only one fuzz target runs per
go test -fuzz invocation.
C / C++ (libFuzzer)
#include <cstdint>
#include <cstddef>
#include "parser.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size > 1 << 16) return 0;
Doc doc;
if (parse_doc(data, size, &doc) == PARSE_OK) {
free_doc(&doc);
}
return 0;
}
clang++ -g -O1 -fsanitize=fuzzer,address,undefined fuzz_parse.cc parser.cc -o fuzz_parse
mkdir -p corpus && cp tests/fixtures/*.doc corpus/
./fuzz_parse corpus/ -dict=doc.dict -max_len=65536 -max_total_time=600 -jobs=4
./fuzz_parse crash-<sha1> # reproduce one input
./fuzz_parse -minimize_crash=1 -runs=100000 crash-<sha1>
Rust (cargo-fuzz)
cargo install cargo-fuzz # network
cargo fuzz init && cargo fuzz add parse
// fuzz/fuzz_targets/parse.rs
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
if let Ok(msg) = mycrate::Message::decode(data) {
let bytes = msg.encode();
assert_eq!(mycrate::Message::decode(&bytes).unwrap(), msg);
}
});
cargo fuzz tmin parse <artifact> minimises; derive arbitrary::Arbitrary on input
structs to fuzz typed APIs instead of raw bytes.
Python (Atheris)
import sys
import atheris
with atheris.instrument_imports():
import mylib.config_parser as cp
def test_one_input(data: bytes) -> None:
fdp = atheris.FuzzedDataProvider(data)
text = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 4096))
try:
cp.parse(text)
except cp.ConfigError:
pass # documented error; anything else is a finding
atheris.Setup(sys.argv, test_one_input)
atheris.Fuzz()
Atheris lags new CPython releases; if pip install atheris has no wheel for the project’s
version, run the fuzzer under the newest supported Python.
Corpus and dictionaries
- Seed with small, valid, diverse samples: test fixtures, spec examples, minimised real files. Ten good seeds beat ten thousand random ones.
- Add a dictionary of format tokens (magic bytes, keywords, delimiters) for grammars:
kw1="<?xml"lines in a.dictfile. - Periodically minimise the corpus (
-merge=1into a fresh directory,cargo fuzz cmin). - Store the corpus in the repo if small, or in CI cache or a bucket if large.
Triage a crash
- Reproduce with the single input and the sanitizer build.
- Minimise it.
- Classify: memory safety (ASan report: heap-buffer-overflow, use-after-free), undefined behaviour, panic or unexpected exception, invariant violation, timeout or OOM.
- Deduplicate by top stack frames; one bug often yields many crash files.
- Fix the root cause, then add the minimised input as a regression test (Go
testdata/fuzz, a unit test that loads the file, or the seed corpus that CI replays). - Memory-safety crashes in code that handles untrusted input are potential security bugs; tell the user before sharing details publicly and follow the project’s disclosure policy.
CI
- PRs: replay the corpus and regression inputs (fast, deterministic), plus a short fuzz run (2-10 minutes) on changed targets. ClusterFuzzLite provides GitHub Actions for this.
- Scheduled: long runs (hours) with corpus persistence; open projects can apply to OSS-Fuzz.
- Fail the job on any new crash and upload the crashing input as an artifact.
Verification
- Inject a known bug (off-by-one in a length check) and confirm the fuzzer finds it within minutes; if not, improve the harness or seeds.
- Check coverage of the harness (
-print_final_stats=1,go test -fuzzreports “new interesting” inputs,cargo fuzz coverage) reaches the logic you care about. - Executions per second stay in the thousands; if not, profile the harness.
Gotchas
- Fuzzing without sanitizers in C and C++ misses most memory bugs; they corrupt silently.
- A harness that catches every exception hides real bugs; catch only documented errors.
- Timeouts from huge inputs are noise unless the input size is realistic; set
-max_len. - Go fuzzing caches its generated corpus in
$GOCACHE/fuzz; CI without that cache restarts from the seeds every run.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Fuzz Testing"
short_description: "Throw a million inputs at your parser first"
default_prompt: "$fuzz-testing Write a Go fuzz test for our HTTP header parser" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.