- name
- create-fuzzing
- description
- Use when the user asks to write, scaffold, run, or extend a fuzz harness for a parser/decoder/templater identified in a security-audit report — Go-native by default, plus Python (atheris), JavaScript/TypeScript (Jazzer.js), Rust (cargo-fuzz), and Java (jazzer, manual run) via the language field in targets.json and templates under harnesses/_templates/. Adds a target to targets.json, authors a harness dropped into the audited repo's tree, drives `make clone install build fuzz-<id>`, and triages any crashers into follow-up findings. Offline only: no cluster, cloud creds, or service mocks.
- metadata
- {"harness.tier":"primary"}
# Fuzz Harnesses
> **Paths.** `analysis-results/…` and `progress-tracker/…` in this skill are the
> default workspace layout. They resolve through `locations.yaml` in
> `$TRAUST_CONFIG_HOME` (`docs/setup.md`, Storage locations); substitute your
> configured roots.
Author and run **offline Go-native fuzz tests** against pure-function attack
surface (parsers, decoders, templaters, regex matchers) that a
`secure-code-audit` report has flagged as fuzz-worthy. Every harness is a
white-box `*_fuzz_test.go` copied *into* the target repo's own package tree so
it can reach unexported identifiers, driven by the Go 1.18+ native fuzzer
(`go test -fuzz`).
This skill wraps the existing tooling in this directory — `targets.json`,
`Makefile`, `harnesses/<id>/*_fuzz_test.go`, and the batch-plan docs. Read
[README.md](README.md) alongside this file for the full layout and design
notes.
## Input
`$ARGUMENTS` is one of:
1. **A repository URL or `<org>/<repo>`** — scaffold new harnesses for that
repo. Look up its audit report under
`analysis-results/findings/**/<repo>/<repo>-security-audit.{json,md}` to
find recommended fuzz entry points (findings whose `remediation` mentions
fuzzing, or the report's remediation-roadmap "add fuzz harness" items).
**Also check the refuted register** —
`<repo>-refuted-register.json` alongside the audit report (emitted by
python3 -m traust.cli ledger emit-triage) lists findings dismissed as
false positives; entries in fuzzable classes (parsers, decoders,
memory safety, injection) are PRIORITY targets, because a reproducing
crash overrides the FP assertion via evidence-class precedence
(`fp_overridden`). An FP is a falsifiable claim, not an exclusion.
2. **A finding ID** (e.g. `KUBE_RBAC_PROXY-9cb7556-019`) — scaffold a harness
for that specific finding's entry point.
2b. **An impact-analysis artifact**
(`analysis-results/impact/<cve>-impact-analysis.json`, from
`/impact-analysis`) — scaffold harnesses for the repos classified
`affected`. This is the assurance-promotion path: `affected` is
call-graph reachability (machine static, evidence class 3); a
reproducing input is execution evidence (class 1) that flips the
finding to `confirmed` in the disposition ledger. Target the entry
point from the repo's `evidence.govulncheck_trace` — the first
in-repo frame is the natural harness entry — and seed the corpus
with advisory-shaped input (`metadata.feature_description` names the
vulnerable capability). Set the target's `audit_ref` to the CVE ID.
Prioritize by severity and by
`attacker_influence: plausible` where triage has judged the trace.
3. **A target ID already in `targets.json`** — skip authoring; just
`make clone install build fuzz-<id>` and triage results.
4. **Nothing** — run the whole current batch: `make clone install build` then
`make FUZZTIME=<duration> fuzz-all` (or `BATCH=<n> fuzz-all`).
## Non-Go languages (C4)
Go stays the default (`language` absent = `go`; nothing changes for
existing targets). For other ecosystems, set `"language"` on the target
and author from the matching template in `harnesses/_templates/`:
| language | engine | template | run |
|---|---|---|---|
| `python` | atheris (Apache-2.0) | `python_fuzz.py` | `make fuzz-<id>` — per-clone `.fuzzvenv`, FUZZTIME-bounded |
| `javascript` | Jazzer.js (Apache-2.0) | `js_fuzz.js` | `make fuzz-<id>` — `npx jazzer`, FUZZTIME-bounded |
| `rust` | cargo-fuzz + libFuzzer | `rust_fuzz.rs` | `make fuzz-<id>` — requires nightly toolchain; dest under `fuzz/fuzz_targets/` |
| `java` | jazzer (JVM), **pinned v0.30.0** (sha256-verified download; Apache-2.0 verified at the tag — docs/external-dependencies.md) | `JavaFuzz.java` | `make fuzz-<id>` — builds the target (maven `package` or gradle `assemble`), assembles the classpath, runs the pinned jazzer against the manifest's `.fuzz` class, FUZZTIME-bounded |
Same rules as Go targets: harnesses live in `analysis-results/fuzz-harnesses/<id>/` and are
copied into the clone by `make install` (dest inside the target tree so
imports resolve); expected parse errors are swallowed in the harness —
crashes, hangs, and memory blowups are the findings; crashers are
triaged identically (reproducer + follow-up finding + rollup entry, with
the target's `language` carried through). Engine licenses are verified
in `docs/external-dependencies.md`; the engines are dependencies of the
*generated harnesses in target repos*, never of this repository.
## Preconditions
Working directory must be this skill's directory:
```bash
cd traust/harnessing/6-fuzz/create-fuzzing
```
Requires: `go` ≥ 1.22, `jq`, `git`, network access for `make clone` only.
## Workflow
### 1. Locate or add the target
Check whether `$ARGUMENTS` already has an entry:
```bash
jq -r '.targets[] | select(.id=="<id>" or (.repo | contains("<repo>")))' targets.json
```
If **absent**, append a new object to `.targets[]` in `targets.json`:
```json
{
"id": "<repo-name>",
"repo": "https://github.com/<org>/<repo>",
"module": "<go module path from go.mod>",
"commit": "<7-or-40-char SHA from the audit report's metadata.commit, or HEAD>",
"product_reach": <int — number of products shipping this repo, from repo-graph>,
"audit_ref": "<finding ID that recommended fuzzing>",
"batch": <next batch number>,
"harnesses": [
{
"file": "<id>/<name>_fuzz_test.go",
"dest": "<pkg-path>/<name>_fuzz_test.go",
"fuzz": "Fuzz<Name>",
"pkg": "./<pkg-path>"
}
]
}
```
Optional per-target keys the Makefile honours: `clone_alias` (share a clone
across targets), `workdir` (subdir containing `go.mod`), `goos` (skip on other
host OS).
### 2. Author the harness
Create `analysis-results/fuzz-harnesses/<id>/<name>_fuzz_test.go`. Rules:
- **`package` must match the target package** (white-box, so unexported
identifiers are reachable). `dest` in `targets.json` places the file
in-tree.
- **Seed corpus via `f.Add(...)`.** Prefer real fixtures the repo already
ships (`docs/samples/*.yaml`, `testdata/*`). Otherwise hand-roll
minimal-valid + minimal-invalid + known-hostile (YAML anchor bomb, deeply
nested JSON, oversized varint, `../` path, `%n` format string) seeds.
- **No network, cluster, or cloud at fuzz time.** Substitute
`k8s.io/client-go/dynamic/fake`, `discovery/fake`, in-memory `io.Reader`,
synthetic `net.Addr`. If the entry point insists on I/O, drive the layer
*below* it.
- **Prefer exported entry points**; if the target is unexported, keep the
harness as thin as possible around it so upstream refactors surface as a
compile error, not a silent no-op.
- Header comment: drop-in `dest` path, `go test -fuzz` invocation, target
file:line, and the crash classes you expect.
See `analysis-results/fuzz-harnesses/kube-rbac-proxy/config_fuzz_test.go` for a reference harness
and `BATCH-*.md` for pattern guidance (sprig/`text/template` DoS,
`strings.Split(...)[N]` index panics, differential invariants).
### 3. Compile-check
```bash
make clone # shallow-fetch repos at pinned commits (idempotent)
make install build # copy harnesses in-tree and go test -run '^$' compile-check
```
`make build` must pass before fuzzing. A compile error means the pinned
commit's internals moved — re-scout the entry point and update the harness,
do **not** bump `commit` to HEAD just to make it compile.
### 4. Fuzz
```bash
make FUZZTIME=10m fuzz-<id> # one target
make FUZZTIME=1h BATCH=<n> fuzz-all # a whole batch
```
Crashers land in
`clones/<id>/<pkg>/testdata/fuzz/<FuzzName>/<hash>`.
### 5. Triage crashers
For each crasher:
1. `go test -run <FuzzName>/<hash> ./<pkg>` to reproduce deterministically.
2. Minimise the input (the Go fuzzer usually already has).
3. Classify: panic (index-OOB, nil deref, type assertion), hang/timeout
(CWE-770 resource exhaustion), or invariant violation (differential).
4. Copy the reproducer into `analysis-results/findings/**/<id>/fuzz-corpus/<FuzzName>/`.
5. **File a follow-up finding** — either amend the source
`*-security-audit.json` with a new finding whose `evidence` embeds the
reproducer and whose `validation_status` is `confirmed`, or open a new
finding via `/file-security-defect`. Use the campaign ID format
`{REPO_SLUG}-{SHORTSHA}-{NNN}` and set `source_findings` to the fuzz target's
`audit_ref`.
6. **Record it in the disposition ledger.** A crasher that only exists as
prose in a batch summary is not recorded — the ledger is the register of
what we know about a finding, and for months it held none of these.
```bash
python3 harnessing/6-fuzz/create-fuzzing/scripts/emit_fuzz_events.py # dry run
python3 harnessing/6-fuzz/create-fuzzing/scripts/emit_fuzz_events.py --apply
```
One event per crasher: `source.type: fuzz_report`, the bug record as
`source.ref`, the reproducer directory in `evidence_refs`. Where the
target's `audit_ref` is a clean `FIND-NNN` the event attaches to that
finding; otherwise it carries a new one. Emits under gate A15 — the
baseline audit report is never written — and is idempotent by `event_id`.
`fuzz_report` is evidence class 1 because the triggering input is kept, so
the claim is replayable. The converse is the part that gets misread: a
fuzz run that finds nothing emits NOTHING here. Absence of a crash is not
evidence of correctness.
A repo audited under several products has one layer per product. The tool
refuses to guess which and reports the ambiguity; `--all-layers` records
the crasher under every product that ships the code.
### 6. Report (batch mode)
When running a full batch, `run-sweep.sh` and `gen-reports.sh` produce a
per-batch summary under this directory. Update the relevant `BATCH-<n>-PLAN.md`
with results (hit / miss / compile-gap per target) so the next batch's
selection can learn from it.
## Output
| Artefact | Path |
|---|---|
| New/updated manifest entry | `targets.json` |
| Harness source | `analysis-results/fuzz-harnesses/<id>/*_fuzz_test.go` — the CORPUS, not this repo: a harness is test code written ABOUT an audited repository, the same category as a finding about it. `file:` in `targets.json` is relative to that root (`HARNESSES` in the Makefile) |
| Reproducers | `analysis-results/findings/**/<id>/fuzz-corpus/<FuzzName>/<hash>` |
| Crashers (git-ignored) | `clones/<id>/<pkg>/testdata/fuzz/<FuzzName>/` |
| Follow-up finding | carried on a ledger event in `analysis-results/findings/**/<repo>-findings-layer.json` (gate A15 — never the baseline) |
| Campaign summary + rollup (via `build_fuzz_rollup.py`) | `analysis-results/FUZZ-CAMPAIGN-SUMMARY.{md,json}` + `FUZZ-FINDINGS-ROLLUP.md` — the `.json` is the machine-readable sidecar dashboards consume; all three also published to `progress-tracker/metrics/dashboards/fuzz/` |
No JSON schema validation step — this skill produces Go source and manifest
edits, not audit reports. The report artefact is the follow-up finding carried
on its ledger event; the originating audit report is not modified (gate A15).
## Integrations
- **Inputs:** audit reports' fuzz-worthy entry points, refuted registers
(FP falsification targets), and `/impact-analysis` artifacts (form 2b
— the class-3→class-1 promotion path).
- **Outputs consumed by:** the event-carried follow-up finding
(with reproducer evidence) flows through `/track-findings` as
execution evidence; `FUZZ-CAMPAIGN-SUMMARY.json` feeds
`/validation-fuzz-dashboard`; crashers override FP assertions via
evidence-class precedence in the disposition ledger.
## Spend declaration (calibration tuple)
After this skill's report/artifact is written, declare the run's spend
against the target so `estimate_scan` can calibrate per-skill cost
models (contract: docs/model-routing.md; analysis:
progress-tracker/metrics/estimate-calibration-analysis.md §F5):
```bash
python3 -m traust.cli registry models spend --skill create-fuzzing \
--model <resolved model id> [--tokens-in <N>] [--tokens-out <N>] \
--repo <target-slug> --loc <target size, if known> [--batch <batch-id>]
```
Token counts are OPTIONAL and best-effort: pass them when the
orchestrator has them (Task results carry per-subagent usage),
otherwise omit them — an agent cannot observe its own usage mid-run.
**This row is a routing marker, not a cost claim**; actual per-lane
cost is attributed from session transcripts by
python3 -m traust.cli metrics attribute-spend. Never skip the row: an
unattributed run is a calibration gap.
GitHubで見る