Skip to content

Artifact Layout

This page describes the files and directories TokenFuzz creates while an audit is running, and where to look first when you want to understand the result of a run.

Set the active result directory once when you start inspecting:

export TARGET=<your-target>
export BACKEND=claude             # or codex, gemini, grok, oss
export RESULTS="output/$TARGET/$BACKEND/results"

Open the generated HTML pages first:

$RESULTS/crashes/CRASH-CLUSTERS.html
$RESULTS/findings/FINDING-CLUSTERS.html
$RESULTS/crashes-rejected/REJECTED-CRASHES.html
$RESULTS/findings-rejected/REJECTED-FINDINGS.html
$RESULTS/crashes/CRASH-*/REPORT.html
$RESULTS/findings/FIND-*/report.html

Each page answers one question. A cluster index lists every distinct problem once, with when its first report landed on the run's clock, which subsystem it sits in, which strategy lane reached it, and how many reports rediscovered it; a benchmark pool page keeps both conditions apart. A rejected index groups what did not hold up by the gate that turned it away. A report page frames the report's own text with an action card — the fix, the site, how to reproduce, how sure the review was — and a rail carrying the CVSS vector, the review receipt, the bundle files, and the timeline. Every figure on them is read from the Markdown indexes and receipts beside them, never recounted.

Use results/ for evidence and progress. Use logs/ only to debug orchestration, backend authentication, or wrapper failures.

The result tree is designed to surface which security results are ready for review, even when they are not sanitizer crashes.

Target root

targets/<target>/

This is the upstream source checkout. Build artifacts may also live here when the target's build system writes them under the source tree, and the harness keeps its own build recipes, leases, and caches under targets/<target>/.audit/.

Target output root

output/<target>/
  target.toml
  CRASH-CLUSTERS.md
  CRASH-CLUSTERS.html
  FINDING-CLUSTERS.md
  FINDING-CLUSTERS.html
  <backend>/

What each file is:

  • target.toml: the generated static configuration you review when inference leaves placeholders or target-specific values.
  • CRASH-CLUSTERS.html and FINDING-CLUSTERS.html: cross-backend aggregate review tables for every backend under this target. The .md siblings are the source files used to generate them.

A run started with bin/audit --experiment <name> uses output/<target>-<name>/ as its root instead, so trial runs never mix with the main audit.

Backend directory

output/<target>/<backend>/
  results/
  logs/

Backends get their own subdirectories so runs from different model providers do not overwrite each other's state. <backend> is one of claude, codex, gemini, grok, or oss.

Results directory

The paths an operator inspects after a run:

Path Purpose
crashes/ Crash candidates, including final and pending artifacts.
crashes-rejected/ Rejected crash artifacts and REJECTED-CRASHES.html / REJECTED-CRASHES.md.
findings/ Security finding candidates of any class, with or without a reproducer. See the note below.
findings-rejected/ FIND directories triage rejected at quorum (substance gate, unreachable trigger, or source-disproved consequence), plus REJECTED-FINDINGS.html / REJECTED-FINDINGS.md listing them with reasons.
corpus/ Inputs that reached new coverage, saved after each iteration for reuse as seeds. Deduplicated by content.
coverage/ Per-agent edge journals (edges-agent-N.journal) written by bin/hits, keyed by target-relative path; bin/coverage-summary and bin/rank-work read them.
hits-N.log One HIT/MISSED/COVERAGE_UNAVAILABLE row per coverage replay by agent N, at the results root.
fuzz/ S4 harness sources, binaries and manifests, persistent corpora, artifacts, slice logs, the campaign journal, and resumable per-harness state.
scratch-N/ Active testcase work for agent N.
.session-env Active backend-local RESULTS_DIR, TARGET_ROOT, TARGET_SLUG, TARGET_REV, TARGET_REPO_TYPE, LOGDIR, SESSION_STARTED, and TARGET_CONFIG_SHA256 values read by bin/probe.
.target.toml The post-preflight target.toml snapshot this session runs against, pinned by the TARGET_CONFIG_SHA256 digest above. Every config consumer in the session reads it instead of the shared output/<target>/target.toml. Editing or removing it fails the run loudly.

Structured progress

The tree also holds the work queue and the structured state the harness manages itself. Claims, runs, notes, and events are append-only ledgers. Hypothesis status is updated by atomically rewriting state/hypotheses.jsonl, and queue refreshes can replace work-cards.jsonl. JSONL is the storage format, not a promise that every file only grows.

work-cards.jsonl             the ranked queue
patch-cards.jsonl            prior-fix work cards (S1)
s6-peer-cards.jsonl          peer-project fix cards (S6)
state/claims.jsonl           card lease and release events
state/hypotheses.jsonl       current hypothesis rows
state/runs.jsonl             one row per bin/probe invocation
state/notes.jsonl            compact supporting notes
state/events.jsonl           audit events
state/run-config.json        the run's recorded mode, security profile, and delta scope
state/unreachable-routes.jsonl   anchored disproofs later work cards render
state/callgraph.json         optional call-neighbourhood context

state/claims.jsonl records every card claim with the queue_rank, queue_size, score, and strategy the card carried when it was offered, which is what bin/state card-yield replays.

state/runs.jsonl has one row per bin/probe invocation: verdict, sanitizer, duration, and, when a coverage replay ran, coverage (HIT, MISSED, UNAVAILABLE, and so on) with the closest frame it reached. An EXEC_FAIL carries a normalized execution_failure_class plus the detailed reason; resume aggregates a five-run same-class streak across the whole card and offers repair or seed guidance, but never closes or re-ranks work from that advisory signal. Older rows retain the same class token in reason and are read compatibly. This is also why wc -l on the file answers "did anything actually run?".

state/callgraph.json is present only with the optional call-neighbourhood analysis installed. It holds the per-file call maps work-card prompts quote, and deleting it costs prompt context and nothing else. The rest is internal bookkeeping.

For S4, private fuzz/bin/*.manifest.json files use schema 2 for new builds. They bind an optional source-grounding receipt to one harness binary, alongside the source digest, guidance, sanitizer, and linked library or tree the manifest already recorded. A harness that carries no receipt (hand-written, or built before schema 2) records an empty one, so "is this harness grounded" is readable straight off the field. fuzz/state.json retains first_slice independently from later high-water totals; bin/fuzz status joins both without changing the campaign's schedule or any security-evidence decision. These are agent-facing diagnostics, not maintainer finding or crash fields.

FIND directories without a report get a .needs-content marker and surface as NEEDS CONTENT in FINDING-CLUSTERS.html. A gate pass with Reject votes below quorum leaves .pending-drop; reaching quorum moves the directory to findings-rejected/ rather than deleting it. touch .reviewed (or .keep) inside a FIND directory requests a human override; the report must still contain complete boundary and trigger fields before the harness writes a final receipt. Editing the report's substance re-opens its review; mechanical severity, patch, enrichment, and cluster annotations do not.

Publication receipts

Every adjudicated artifact has a content-addressed validation.json. It binds the publication state to the report, saved evidence, target revision and config, and threat model. Its states are reportable, pending, and rejected. Pending and legacy artifacts remain visible on disk. Only a current reportable receipt enters the security benchmark total or receives numeric severity. pending is an artifact a review is still due on, which is neither credited nor written off. A defect the reviewers place outside the threat model, or one they cannot place inside it once every review the lane asks for has answered, is rejected with that reason and keeps its evidence under the rejected tree. Only a human-pinned artifact still records not-reportable in place; older trees may carry the state from before this rule.

When TARGET_ROOT is available, new receipts join each source review to a source_attestations entry. The harness re-reads the review's path, line, symbol, and excerpt, replaces any reviewer-supplied excerpt digest with its own, and binds the normalized anchors plus the review artifact's SHA-256 into the receipt evidence_id. Reading with the checkout pinned to the receipt's target revision repeats that verification. For a plain source tree without a VCS revision, an opaque source_context binds re-verification to the exact host checkout that issued the attestation. An unrelated live checkout is not allowed to refute historical evidence; an exported bundle without its pinned checkout retains an attestation already recorded. Trusted representation-only rewrites may update the bound review digest only while every verified anchor remains present. Older schema-2 receipts may omit these optional fields and gain them on their next review.

Changing the report, testcase, harness, sanitizer diagnostic, invocation evidence, cited source, target/config identity, or review evidence invalidates the receipt and returns the artifact to review.

A short run may leave crashes/ and findings/ empty. That is not a failed run by itself. Check the rejected indexes first to see whether the agent produced candidates that triage rejected.

Crash directory

Before export, a crash directory commonly includes:

CRASH-001-1/
  testcase.<ext>        # .html, .js, .py, .dat, … depending on the target
  sanitizer.txt         # saved sanitizer output
  report.md             # agent-authored narrative + fields
  patch.diff            # optional agent-suggested fix

A crash that triage has accepted but not finished promoting carries a .promotion_pending marker naming what is still missing. It clears once the export bundle below is complete. A directory still missing the same artifacts after ten triage passes is moved to crashes-rejected/, with those artifacts named in its rejection report.

Pending promotion is resumable work. bin/state resume --agent N presents an unfinished bundle before active hypotheses or new work cards. Its sanitizer proof remains countable in benchmark crash totals, but severity stays Unknown until the report is complete.

After export, the maintainer-facing bundle has:

CRASH-001-1/
  REPORT.md             # field table + sanitizer summary; hand-edit this
  REPORT.html           # auto-generated sibling of REPORT.md
  reproduce.sh          # ./reproduce.sh /path/to/source
  input.<ext>           # the testcase bytes
  harness.{c,cc,cpp,cxx} # only when the bug uses a C/C++ harness
  sanitizer.txt         # saved sanitizer output
  patch.diff            # optional: candidate fix
  validation.json       # current publication state + evidence identity
  severity.json         # only when a current reportable score exists
  .audit/

When no runnable route (testcase, harness, or wrapper) was captured, reproduce.sh is a stub that explains what is missing and exits 2.

Accepted crashes may carry other dot-files the triage gates leave behind (vote caches, timing and scoring markers, and the like). All of them are harness internals, safe to ignore when reviewing.

REPORT.md carries a Cluster: <ID> line naming the cluster and this member's role in it. The auto-generated REPORT.html is regenerated on every triage pass; edit REPORT.md only. See Triage and review for the cluster model.

Audit-side originals (the agent's report.md and intermediate scratch artifacts) are kept under .audit/ as an internal triage cache. They are not needed to reproduce or review the crash.

Crash directories are intentionally narrow. They should contain the evidence needed to rerun and prioritise a crash. Broader security observations belong in findings/.

crashes/ also contains CRASH-CLUSTERS.md and CRASH-CLUSTERS.html, the generated review table for crashes in this backend's results/ tree. The cross-backend aggregate lives at output/<target>/CRASH-CLUSTERS.md and output/<target>/CRASH-CLUSTERS.html.

Finding directory

Findings use:

FIND-001/
  report.md              # the narrative; hand-edit this (description.md also accepted)
  report.html            # auto-generated sibling of report.md (open in browser)
  validation.json        # current publication state + evidence identity
  severity.json          # only when a current reportable score exists
  affected-files.txt     # optional, operator-authored; the harness does not generate it
  .dup-of                # only on non-canonical cluster members
  .needs-content         # marker added when report.md is missing

report.md carries Cluster: <ID> and Dedup key: lines. report.html is regenerated on every triage pass; hand-edit only report.md.

findings/ also contains FINDING-CLUSTERS.md and FINDING-CLUSTERS.html, the review table grouping reports that share a root cause. The cross-backend aggregate lives at output/<target>/FINDING-CLUSTERS.md and output/<target>/FINDING-CLUSTERS.html.

See Triage and review for how cluster membership and .dup-of markers are used during review.

findings/ accepts any concrete security issue: memory safety, logic, auth bypass, injection, info disclosure, crypto, races, boundary violations, and so on. A sanitizer reproducer or runnable testcase is not required; a substantive report is. Each report needs:

  • a concrete location (file:function:line, an endpoint, a config key, and so on);
  • what is wrong from a security standpoint;
  • a rationale a reviewer can act on.

Vacuous candidates are not moved out of findings/ below reject quorum. The harness drops a .pending-drop marker in the FIND directory. Edit the report to address the marker, or touch .reviewed / .keep to override. Editing the report also invalidates saved quality votes so the revised content receives a fresh quorum. At quorum, the directory is moved to findings-rejected/.

The severity scorer writes severity.json and updates severity text only after a current final-state validation receipt exists. A pending FIND remains available for review without being silently interpreted as a low-severity security bug.

severity.json records the published level, score, and vector together with the scorer version and a hash of the report content they were derived from. Later passes rewrite reports (reach-field fills, enrichment, pool copies), so that binding is what distinguishes a current score from one an earlier scorer left behind. A score whose binding no longer matches is re-derived, never credited as-is.

Report narrative

Crash and finding reports share one narrative shape, so a reviewer reads every backend's output the same way. Before the narrative headings, one bare Location: path/to/file.ext:function:line names the root-cause operation. Use an endpoint, config key, or protocol step when no source location exists; do not list several candidate locations. Finding clustering uses this as the primary source identity. The narrative then follows this order:

Section Budget Answers
## Summary 60–90 words What the component does, what goes wrong, what the attacker gets
## Root Cause 120–200 words The invariant the code assumed and the input that breaks it
## Data Flow ≤ 8 bullets The path, as step: func (path/file.c:NN) — sentence
## Impact 40–70 words Who is exposed and what they lose
## Fix Direction 30–60 words Where the fix goes and what changes

## Summary is required because it feeds the reviewer TL;DR. Any other section with no evidence behind it is omitted rather than filled; an unevidenced Impact paragraph costs more than a missing one. Sections outside this set are written by the harness (## Fields, ## Patch, ## Severity rationale, ## Classification, ## Reproduce), not by the report author.

The contract lives in lib/prompts/report_prose.md.j2 and is rendered into both the harness session prompt and the model-direct baseline, so prose shape is never a difference between benchmark conditions.

Logs

output/<target>/<backend>/logs/
  README.md
  index.log
  index.jsonl
  llm-decisions.log
  session_<TS>_<launch>-<n>.log
  .raw/
    session_<TS>_<launch>-<n>.log.raw
    session_<TS>_<launch>-<n>.prompt.md

In the per-session filenames, <TS> is the launch timestamp, <launch> is cold-start or deep_investigation, and <n> is the agent number. Other files may appear alongside these (decision caches and similar bookkeeping); the listing above is what is worth opening, not an exhaustive inventory.

Logs are useful for:

  • backend CLI failures;
  • orchestrator launch problems;
  • unexpected wrapper behaviour.

For normal audit progress, prefer the generated HTML:

  • crashes/CRASH-CLUSTERS.html;
  • findings/FINDING-CLUSTERS.html;
  • crashes-rejected/REJECTED-CRASHES.html;
  • findings-rejected/REJECTED-FINDINGS.html;
  • per-result REPORT.html / report.html.

For debugging a run, start with logs/README.md, then index.log. Open the matching session_*.log for the session named in the timeline. Use index.jsonl when you want the same session data in a scriptable form. Each session row carries probes, probe_seconds, probe_diagnostics, and first_probe_seconds: how many bin/probe runs the session made, the wall they took, how many produced a diagnostic, and how long the session took to run its first. Usage rows also record delegation_events (subagent spawns the transcript shows, one per call id), spend_lower_bound (the delegated work ran where the row's usage cannot see it, so the row is a floor), delegation_observable (false where the backend cannot show its fan-out at all), and served_model when the provider billed the session to a model other than the one requested. The benchmark page says how the report reads them. Full backend transcripts and exact prompt dumps live under logs/.raw/; they are intentionally out of the way because they can be large and are rarely the first artifact you need.