agentic/code/frameworks/security-engineering/skills/fuzzing-in-ci/SKILL.md
Detect candidate fuzz targets and emit CI recipes for libFuzzer/AFL/cargo-fuzz/atheris/fast-check harnesses, plus OSS-Fuzz integration patterns
npx skillsauth add jmagly/aiwg fuzzing-in-ciInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are the Fuzzing Integration Engineer — identify functions that take untrusted input, scaffold fuzz harnesses for them, and wire short-form PR-gating fuzz jobs that complement sanitizer-enabled builds.
"Coverage-guided fuzzing finds bugs the test suite never imagined." Fuzzers are the highest-leverage way to find input-handling bugs in parsers, deserializers, and protocol decoders. Wired into CI on every PR (with a small budget — minutes, not hours), they catch regressions immediately. Long-form fuzzing (OSS-Fuzz, dedicated runners) finds the deeper bugs over days and weeks.
| Language | Fuzzer | Property-based alternative | |----------|-------------------|----------------------------| | C / C++ | libFuzzer (Clang), AFL++ | — | | Rust | cargo-fuzz (libFuzzer-backed), AFL (afl.rs) | proptest, quickcheck | | Python | atheris (libFuzzer-backed) | Hypothesis | | Node.js | jazzer.js (libFuzzer-backed) | fast-check | | Java/JVM | jazzer (libFuzzer-backed) | jqwik |
Cycle-2 additions: Go (native go-fuzz), Swift, Ruby.
Same lib/toolchain-detect.sh helper as sanitizer-in-ci.
Heuristics for functions that benefit most from fuzzing:
bytes/&[u8]/Buffer inputparse_*, deserialize_*, decode_*, unmarshal_*from_str / from_bytes constructorsThe skill scans declarations using ripgrep:
# C/C++ — look for functions taking const uint8_t*/size_t pairs
rg -n --type c --type cpp -e 'parse_|decode_|deserialize_' src/
# Rust
rg -n --type rust -e 'fn (parse|decode|deserialize)_|FromStr|TryFrom<&\[u8\]>' src/
# Python
rg -n --type py -e 'def (parse|decode|deserialize)' src/
# Node
rg -n --type js --type ts -e 'function (parse|decode|deserialize)' src/
Report candidates to the operator for confirmation before scaffolding harnesses.
Reference emitter:
agentic/code/frameworks/security-engineering/skills/fuzzing-in-ci/scripts/emit.sh \
--language auto --ci auto --seconds-per-target 120
.aiwg/security-engineering/fuzzing/c/fuzz_parse.c:
// LLVMFuzzerTestOneInput is the entry point libFuzzer expects.
// Compile with: clang -fsanitize=fuzzer,address,undefined -o fuzz_parse fuzz_parse.c parse.c
#include <stdint.h>
#include <stddef.h>
#include "parse.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Constrain input shape if your parser has a minimum
if (size < 4) return 0;
parse_result_t *r = parse_buffer(data, size);
if (r) {
parse_result_free(r);
}
return 0;
}
.aiwg/security-engineering/fuzzing/c/seed-corpus/ — populate with a few representative valid inputs.
fuzz/fuzz_targets/fuzz_parse.rs (cargo-fuzz layout):
#![no_main]
use libfuzzer_sys::fuzz_target;
use mycrate::parse;
fuzz_target!(|data: &[u8]| {
let _ = parse(data); // ignore Result; we want it to not panic/UB
});
Project-side: cargo install cargo-fuzz then cargo fuzz init && cargo fuzz add fuzz_parse.
.aiwg/security-engineering/fuzzing/python/fuzz_parse.py:
import atheris
import sys
with atheris.instrument_imports():
from mypkg import parse
def TestOneInput(data: bytes) -> None:
try:
parse(data)
except (ValueError, mypkg.ParseError):
pass # expected on invalid input
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
.aiwg/security-engineering/fuzzing/node/fuzz_parse.js:
const { parse } = require("../../../src");
module.exports.fuzz = function (data /* Buffer */) {
try {
parse(data);
} catch (e) {
// Only catch the parser's documented error class; let everything else propagate
if (!(e instanceof require("../../../src").ParseError)) throw e;
}
};
Short-form fuzzing on every PR. Default budget: 2 minutes per target, with a corpus that grows over time.
# .aiwg/security-engineering/fuzzing/github/c.yaml
name: Fuzz (PR)
on:
pull_request:
push:
branches: [main]
jobs:
libfuzzer:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Install clang
run: apt-get update && apt-get install -y clang
- name: Cache fuzz corpus
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: .aiwg/security-engineering/fuzzing/c/corpus
key: fuzz-c-corpus-${{ github.run_id }}
restore-keys: fuzz-c-corpus-
- name: Build fuzz target
run: clang -fsanitize=fuzzer,address,undefined -O1 -g -o fuzz_parse \
.aiwg/security-engineering/fuzzing/c/fuzz_parse.c src/parse.c
- name: Fuzz for 120 seconds
run: |
mkdir -p .aiwg/security-engineering/fuzzing/c/corpus
./fuzz_parse -max_total_time=120 \
.aiwg/security-engineering/fuzzing/c/corpus \
.aiwg/security-engineering/fuzzing/c/seed-corpus
- name: Upload crashes
if: failure()
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: fuzz-crashes-c
path: crash-*
.aiwg/security-engineering/fuzzing/OSS-FUZZ.md:
# OSS-Fuzz Integration
OSS-Fuzz runs continuous fuzzing on Google infrastructure for free, for qualifying open-source projects.
## Eligibility
- Open-source project with significant impact (usage in production, dependency of widely-used software, etc.)
- Submit via https://github.com/google/oss-fuzz/blob/master/docs/getting-started/new_project_guide.md
## Required files
For a project named `myproject`, OSS-Fuzz needs:
1. `projects/myproject/project.yaml` — homepage, language, sanitizers
2. `projects/myproject/Dockerfile` — build environment
3. `projects/myproject/build.sh` — compiles fuzz targets
Example:
```yaml
# project.yaml
homepage: "https://example.com/myproject"
language: c
primary_contact: "[email protected]"
auto_ccs:
- "[email protected]"
sanitizers:
- address
- undefined
- memory # optional, requires MSan-clean deps
# Mirror what OSS-Fuzz does locally:
python infra/helper.py build_image myproject
python infra/helper.py build_fuzzers --sanitizer address myproject
python infra/helper.py run_fuzzer myproject fuzz_parse
### Phase 6: Property-based testing recipes
For languages or codebases where coverage-guided fuzzing is awkward (high-level Python, async-heavy JS, JVM with lots of reflection), property-based testing is often easier to adopt and complementary.
`.aiwg/security-engineering/fuzzing/python/property_test.py`:
```python
from hypothesis import given, strategies as st
from mypkg import parse, serialize
@given(st.binary(min_size=0, max_size=10_000))
def test_parse_never_panics(data):
try:
parse(data)
except mypkg.ParseError:
pass
@given(st.text())
def test_roundtrip(s):
assert parse(serialize(s)) == s
.aiwg/security-engineering/fuzzing/node/property_test.js (fast-check):
const fc = require("fast-check");
const { parse, serialize } = require("../../../src");
test("roundtrip", () => {
fc.assert(fc.property(fc.string(), (s) => parse(serialize(s)) === s));
});
test("parse handles arbitrary bytes", () => {
fc.assert(
fc.property(fc.uint8Array(), (bytes) => {
try { parse(Buffer.from(bytes)); } catch (e) { /* expected */ }
})
);
});
sanitizer-in-ci — fuzzers find inputs that trigger UB; sanitizers detect the UB. Pair them.ci-action-pinning — all action SHAs are pinned.scripts/emit.sh emits starter harnesses under .aiwg/security-engineering/fuzzing/{language}/..aiwg/security-engineering/fuzzing/{ci-platform}/.OSS-FUZZ.md and merge_corpus.sh placeholders..aiwg/security/curl-checklist-gap-analysis.md row 16data-ai
Report which research-corpus radar sidecars are overdue for refresh. Computes staleness (days since last refresh vs the cadence window) for every radar, sorted most-overdue-first. Runs via `aiwg corpus radar-status`.
data-ai
Aggregate research-corpus radar sidecars into a corpus or per-cluster freshness report — totals, overdue count, per-cluster / per-GRADE / per-trajectory breakdowns, an overdue table, and per-radar rationale snippets. Runs via `aiwg corpus radar-report`.
testing
Scaffold radar/freshness sidecars for research-corpus REFs. Pulls title/authors from the citation sidecar and GRADE from the analysis doc, defaults the refresh cadence from GRADE and the cluster from a corpus-local map, and stamps documentation/radar/REF-XXX-radar.md. Runs via `aiwg corpus radar-init`.
data-ai
Compute an entity's publication trajectory — per-year paper counts, topic drift, hot-streak detection (≥3 consecutive A-grade years), and career phase. Runs via `aiwg corpus profile-temporal`.