Use this skill whenever a user wants to write, improve, or generate LibFuzzer fuzz drivers (harnesses) for a C or C++ library. Triggers when you see words like "fuzz", "fuzzing", "harness", "fuzz driver", "libfuzzer", "afl", "oss-fuzz", "coverage", or when the user has a compiled library (.so / .a) and wants to find bugs in it via fuzzing. Also use this skill if the user asks to "improve fuzzing coverage", "add more fuzz targets", or "write libfuzzer tests". Use this skill proactively — if the user is working with a C/C++ library and mentions security or robustness testing, this skill is almost certainly relevant.
A systematic process for producing high-quality, non-redundant LibFuzzer harnesses that maximise code coverage and find real bugs.
references/patterns.mdreferences/checklist.mdAlways use the .so file with nm -D (dynamic symbol table). Static archives
(.a) may include internal symbols and do not reflect the true public API surface.
The helper script scripts/export_symbols.sh wraps this command.
If build_harness/ exists (created by libfuzzer-lib-builder), use the ASan
.so — it carries the same public API as the MSan build:
# Preferred: use the helper script
bash scripts/export_symbols.sh build_harness/asan/lib<name>.so
# Or run directly
nm -D build_harness/asan/lib<name>.so | grep ' T ' | awk '{print $3}' | sort
If build_harness/ does not exist, build the library first for inspection only
(no sanitizer flags needed at this stage), then use the .so:
mkdir -p build_harness/inspect && cd build_harness/inspect
cmake <source_dir> -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
nm -D lib<name>.so | grep ' T ' | awk '{print $3}' | sort
This lists every exported function. Use it to understand the full public API surface.
Read every .h file that belongs to the library. Note:
Scan for any *fuzzer*.c, *fuzz*.c, or LLVMFuzzerTestOneInput in the repo.
For each existing harness, list the API functions it exercises. This becomes your
covered set.
Subtract the covered set from the full API list. Group the remaining functions by functional area (e.g., "parse", "build/create", "mutation", "query", "utils"). Each functional group becomes one harness.
Key rule: do NOT rewrite an existing harness. If a function is already covered, skip it. Only add new harness files for uncovered groups.
Before writing any code, decide for each harness:
| Design question | Why it matters |
|---|---|
| What is the natural input? | Determines whether to split input regions or parse it directly |
| How many API functions? | More functions → need multi-region input splitting |
| Does the harness do mutations? | If yes, use fixed-size op records (see patterns) |
| Are there semantic oracles? | Identify invariants that should always hold |
Read references/patterns.md for the concrete implementation templates.
Follow the patterns in references/patterns.md. The most important rules:
IsReference flag) must NOT be freed after their referentAddItemToArray/Object succeeds → item is owned by the container, caller does NOT freeAddItemToArray/Object fails → caller MUST free the itemconsume_lstr) instead of NUL-delimited scanning — NUL scanning eats unpredictable amounts of entropy and makes corpus evolution unstableGetArrayItem, ReplaceItemInArray), test valid index, index -1, and index == sizeSetValuestring), test on correct type, wrong type, and NULL argumentWhere possible, add an assertion that catches logical bugs, not just crashes:
Compare(original, Duplicate(original, 1)) must be truePrintUnformatted(Parse(PrintUnformatted(X))) == PrintUnformatted(X) (text equality, not cJSON_Compare — avoids float precision false positives)Use __builtin_trap() to fire the oracle so ASan/libFuzzer reports it as a crash.
This step is a hard gate. A harness may not advance to Step 6 until both you and codex independently agree it is ready. The goal is to catch logical bugs and coverage gaps that compile-time checks and smoke tests cannot see.
Call mcp__codex__codex with the full harness source and ask it to evaluate:
AddItemToArray/Object success vs. failure__builtin_trap() used correctly?Iteration protocol with codex:
If codex and you disagree on a point, err on the side of correctness over coverage — a harness that leaks memory or fires false-positive oracles is worse than one with slightly lower coverage.
Work through references/checklist.md in full. At minimum verify:
Even without codex, do not self-certify until you have walked through every checklist item for every harness and found no remaining issues.
Compile the harness against the ASan build and smoke-test it. The MSan build is compiled for portability validation but not smoke-tested locally — a clean ASan run is sufficient to confirm harness correctness.
Do not rebuild the library here; link against the pre-built archives from
libfuzzer-lib-builder.
Use the scripts provided in scripts/:
# ── ASan build + smoke test (required) ───────────────────────────────────────
bash scripts/compile_fuzz.sh asan \
<harness.c> \
build_harness/asan/lib<name>.a \
build_harness/asan/include \
/tmp/<harness>_asan
bash scripts/smoke_test.sh /tmp/<harness>_asan [corpus_dir]
# ── MSan build only — compile to catch portability issues (no smoke test) ────
bash scripts/compile_fuzz.sh msan \
<harness.c> \
build_harness/msan/lib<name>.a \
build_harness/msan/include \
/tmp/<harness>_msan
# Do NOT run smoke_test.sh for MSan — no local MSan environment.
A harness is acceptable when the ASan build satisfies:
-Wall -Wextra -Werror (zero warnings)CRASH, ERROR, or abort (aligned with OSS-Fuzz)The MSan build must compile cleanly (zero warnings/errors). Do not run the smoke test for MSan — there is no local MSan environment.
For each issue found by codex or the smoke test:
A harness is finished only when both conditions hold simultaneously:
The final deliverables are .c files placed in the project's fuzzing/ directory,
alongside existing harnesses. Each file has a header comment listing: