PlatformTesting & Adversarial Analysis
Formal Assurance
Fuzz infrastructure
libFuzzer harnesses exercise selected parser and verifier entry points, and inventory tests keep the target list, the owner map, and the corpora in step.
Standalone workspace by design
fuzz/Cargo.toml carries an empty [workspace] stanza so libFuzzer's nightly-only runtime requirements do not leak into the main stable workspace. The main workspace denies unwrap_used and expect_used; the fuzz workspace inherits the same lints so harnesses cannot mask panics behind a hidden .unwrap().What fuzzing does
libFuzzer is a coverage-guided in-process fuzzer. It starts from a seed corpus, executes the harness, observes which code edges the input touched, and mutates the input toward inputs that cover new edges. When an input causes a panic, an assertion violation, or an ASAN/UBSAN report, libFuzzer captures it as a crash and shrinks toward a minimal reproducer.
Chio targets trust-boundary entry points: places where externally controlled bytes first enter Chio code. The contract for each target is that no input causes a panic, an unwrap failure, or undefined behavior; structurally invalid input must result in a typed Err return.
Fuzz targets
Every target is a [[bin]] entry in fuzz/Cargo.toml backed by a fuzz_target! in fuzz/fuzz_targets/<name>.rs. Three further files name the same set: the workflow matrix in .github/workflows/fuzz.yml, the trigger globs in fuzz/target-map.toml, and the owning crate in fuzz/owners.toml as [targets.<name>] with crate and path fields. The header of fuzz/target-map.toml requires all of them to move in one change set, and fuzz/tests/smoke.rs turns that requirement into a test rather than a convention.
The table below is the target list as fuzz/owners.toml declares it. The input-boundary column is the notes field each target carries in fuzz/target-map.toml.
| Target | Source | Owning crate | Input boundary |
|---|---|---|---|
attest_verify | fuzz_targets/attest_verify.rs | chio-attest-verify | Sigstore bundle parser + cert chain verify; trust-boundary |
jwt_vc_verify | fuzz_targets/jwt_vc_verify.rs | chio-credentials | JWT VC verifier; trust-boundary; constant-time compare assertions live here |
oid4vp_presentation | fuzz_targets/oid4vp_presentation.rs | chio-credentials | OID4VP holder response decode |
did_resolve | fuzz_targets/did_resolve.rs | chio-did | chio-did parser plus resolver |
anchor_bundle_verify | fuzz_targets/anchor_bundle_verify.rs | chio-anchor | Anchor proof bundle plus checkpoint records; trust-boundary |
mcp_envelope_decode | fuzz_targets/mcp_envelope_decode.rs | chio-mcp-edge | MCP NDJSON decode plus edge dispatch; pipes into evaluator post-decode |
a2a_envelope_decode | fuzz_targets/a2a_envelope_decode.rs | chio-a2a-adapter | A2A SSE parse plus per-event fan-out |
acp_envelope_decode | fuzz_targets/acp_envelope_decode.rs | chio-acp-edge | ACP NDJSON plus handle_jsonrpc dispatch |
wasm_preinstantiate_validate | fuzz_targets/wasm_preinstantiate_validate.rs | chio-wasm-guards | ComponentBackend, WasmtimeBackend, format detect; trust-boundary |
wasm_guard_escape | fuzz_targets/wasm_guard_escape.rs | chio-wasm-guards | Runtime-execution surface; 8 escape classes via evaluate |
wasm_guard_smith | fuzz_targets/wasm_guard_smith.rs | chio-wasm-guards | wasm-smith modules and components through bounded load and evaluate paths |
wit_host_call_boundary | fuzz_targets/wit_host_call_boundary.rs | chio-wasm-guards | GuardRequest/GuestDenyResponse serde deserialization; trust-boundary |
chio_yaml_parse | fuzz_targets/chio_yaml_parse.rs | chio-config | chio-config YAML loader |
openapi_ingest | fuzz_targets/openapi_ingest.rs | chio-openapi-mcp-bridge | OpenApiMcpBridge::from_spec ingest path |
receipt_log_replay | fuzz_targets/receipt_log_replay.rs | chio-kernel-core | Receipt log replay decode plus chain-invariant state-machine, including cognition-market terminal and payment paths |
eval_receipt_bundle | fuzz_targets/eval_receipt_bundle.rs | chio-eval-receipt | Eval-report bundle parser and fail-closed verifier |
canonical_json | fuzz_targets/canonical_json.rs | chio-core-types | Round-trips canonical JSON; catches sort drift, float canonicalization, and signed cognition-market artifact regressions |
capability_receipt | fuzz_targets/capability_receipt.rs | chio-core-types | Capability + receipt round-trip; exercises the capability-algebra invariants |
manifest_roundtrip | fuzz_targets/manifest_roundtrip.rs | chio-manifest | Tool-manifest decode plus canonicalization |
revocation_oracle_merkle | fuzz_targets/revocation_oracle_merkle.rs | chio-revocation-oracle | Revocation oracle sparse-Merkle insert, inclusion, and non-inclusion proof surface |
federation_trust_establishment | fuzz_targets/federation_trust_establishment.rs | chio-federation | Kernel trust-establishment envelopes, peer pins, freshness, and fail-closed resolution |
finding_worker_protocol | fuzz_targets/finding_worker_protocol.rs | chio-finding-worker | Hosted worker capability, job, request, transfer, result, and attestation protocol validation |
underwriting_policy_input | fuzz_targets/underwriting_policy_input.rs | chio-underwriting | Underwriting policy, decision, marketplace, and premium decode surfaces |
fuzz_policy_parse_compile | fuzz_targets/policy_parse_compile.rs | chio-policy | HushSpec parser, validator, compiler, and YAML round-trip |
policy_analyze | fuzz_targets/policy_analyze.rs | chio-policy | Bounded policy relations, analyzer totality, and evaluator-confirmed refinement witnesses |
fuzz_sql_parser | fuzz_targets/sql_parser.rs | chio-data-guards | SQL parser and SQL guard fail-closed analysis across dialects |
fuzz_merkle_checkpoint | fuzz_targets/merkle_checkpoint.rs | chio-kernel | Merkle tree inclusion proofs, signed checkpoint validation, and cognition-market public status proof coverage |
fuzz_tool_action | fuzz_targets/tool_action.rs | chio-guards | Tool action classification and guard verdicts for egress, shell, SQL, memory, MCP, and HTTP authority projection |
Each target carries the source-path globs that, when changed, must trigger it on the pull request. The header of fuzz/target-map.toml names the two build scripts the list stays in lockstep with: .clusterfuzzlite/build.sh and fuzz/oss-fuzz/build.sh.
The inventory tests
Four registries name the target set and nothing forces a new target into all four at once, so fuzz/tests/smoke.rs asserts the agreement as ordinary cargo test assertions. fuzz_workflow_matrix_matches_cargo_bins compares the parsed [[bin]] names against the .github/workflows/fuzz.yml matrix. owners_toml_covers_all_matrix_targets compares the owner map against the same matrix, so a target whose [targets.<name>] block is missing fails the test rather than failing scripts/promote_fuzz_seed.sh during a crash triage.
fn owners_toml_covers_all_matrix_targets() {
assert_eq!(
sorted(owners_toml_targets()),
sorted(workflow_fuzz_targets())
);
}The third one is the seed floor. MINIMUM_SEED_COUNT is 3, and every target in the matrix has to meet it, so an empty or missing corpus directory is a test failure rather than a silently vacuous fuzz run.
fn all_matrix_targets_meet_seed_floor() {
for target in workflow_fuzz_targets() {
let count = each_seed(&target, |_| {});
assert!(
count >= MINIMUM_SEED_COUNT,
"target {target} has {count} seeds; expected at least {MINIMUM_SEED_COUNT}"
);
}
}A fourth test, all_matrix_targets_have_declared_smoke_posture, requires every matrix target to appear in exactly one of two declared lists: CORPUS_SMOKE_TARGETS, which get a per-target in-process smoke test that feeds every seed through the owning crate's fuzz entry point, and NO_IN_PROCESS_SMOKE_TARGETS, which are declared as deliberately outside that reusable harness. Adding a target without picking one of the two fails.
Harness shape
The smallest harnesses delegate to a fuzz_ entry point exported by the owning crate behind that crate's fuzz feature.
//! Trust-boundary fuzz target for `chio_credentials::verify_chio_passport_jwt_vc_json`.
#![no_main]
use chio_credentials::fuzz::fuzz_jwt_vc_verify;
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
fuzz_jwt_vc_verify(data);
});Targets that need a structure-aware mutator declare one with fuzz_mutator!. The canonical-JSON mutator at fuzz/mutators/canonical_json.rs is re-exported through fuzz/src/lib.rs as chio_fuzz::canonical_json::canonical_json_mutate and is consumed by six targets: canonical_json, capability_receipt, manifest_roundtrip, mcp_envelope_decode, federation_trust_establishment, and underwriting_policy_input.
//! Trust-boundary fuzz target for `chio-core-types` `CapabilityToken` and `ChioReceipt` canonical-JSON deserialization.
#![no_main]
use chio_core_types::{capability::token::CapabilityToken, receipt::body::ChioReceipt};
use chio_fuzz::canonical_json::canonical_json_mutate;
use libfuzzer_sys::{fuzz_mutator, fuzz_target};
fuzz_target!(|data: &[u8]| {
// Try CapabilityToken first; on success, drive the verify path.
if let Ok(token) = serde_json::from_slice::<CapabilityToken>(data) {
// Signature verification is fail-closed: we accept Ok(false) /
// Err(_) as expected outcomes. The point is that the call must
// not panic on any structurally valid CapabilityToken.
let _ = token.verify_signature();
}
// Try ChioReceipt independently. Same byte stream, different typed
// deserializer, different fail-closed surface.
if let Ok(receipt) = serde_json::from_slice::<ChioReceipt>(data) {
let _ = receipt.verify_signature();
}
});
fuzz_mutator!(|data: &mut [u8], size: usize, max_size: usize, seed: u32| {
canonical_json_mutate(data, size, max_size, seed)
});Two targets embed their seed corpus in the binary with include_bytes! so the first iteration always exercises a known-good shape: fuzz_targets/tool_action.rs and fuzz_targets/policy_parse_compile.rs.
Corpus
Seed corpora live under fuzz/corpus/<target>/. The cargo-fuzz convention is corpus/<target>/ for unprefixed targets and corpus/fuzz_<target>/ for the main-branch fuzz_* binaries. A typical corpus directory holds:
- A handful of hand-curated seed inputs covering the major structural cases (one valid case, one boundary case, one intentionally malformed case).
- Promoted seeds: every crash that triaged through
scripts/promote_fuzz_seed.shbecomes a permanent corpus entry plus a regression test in the owning crate.
Adding a new seed:
# Drop the input bytes
cp my-seed.bin fuzz/corpus/<target>/
# Run the target with the seed in the corpus
cd fuzz
cargo +nightly fuzz run <target>
# If the seed survives the run, commit it:
git add fuzz/corpus/<target>/my-seed.binCorpus snapshot
Seed counts per committed corpus directory under fuzz/corpus/, one directory per target and none of them empty. Each directory name matches its [[bin]] name, so the four targets whose binary name carries the fuzz_ prefix (fuzz_merkle_checkpoint, fuzz_policy_parse_compile, fuzz_sql_parser, fuzz_tool_action) take a fuzz_-prefixed corpus directory and the rest use the plain target name. The floor of three is the one all_matrix_targets_meet_seed_floor asserts, so the smallest counts below sit exactly on it.
| Target | Seeds | Target | Seeds |
|---|---|---|---|
a2a_envelope_decode | 8 | fuzz_tool_action | 12 |
acp_envelope_decode | 7 | jwt_vc_verify | 5 |
anchor_bundle_verify | 6 | manifest_roundtrip | 7 |
attest_verify | 3 | mcp_envelope_decode | 7 |
canonical_json | 5 | oid4vp_presentation | 4 |
capability_receipt | 14 | openapi_ingest | 9 |
chio_yaml_parse | 7 | policy_analyze | 3 |
did_resolve | 6 | receipt_log_replay | 7 |
eval_receipt_bundle | 3 | revocation_oracle_merkle | 3 |
federation_trust_establishment | 3 | underwriting_policy_input | 5 |
finding_worker_protocol | 3 | wasm_guard_escape | 8 |
fuzz_merkle_checkpoint | 3 | wasm_guard_smith | 5 |
fuzz_policy_parse_compile | 6 | wasm_preinstantiate_validate | 7 |
fuzz_sql_parser | 10 | wit_host_call_boundary | 7 |
Targets sitting on the floor (e.g. attest_verify, fuzz_merkle_checkpoint) rely on the libFuzzer mutator to grow coverage from the three curated seeds. Targets with double-digit counts (e.g. capability_receipt, fuzz_tool_action, fuzz_sql_parser) carry promoted regression seeds from prior crash triages.
Promoted regression seeds also land as a tests/ file in the owning crate (per owners.toml) so the seed stays exercised even when the fuzz lane is offline.
ClusterFuzzLite integration
ClusterFuzzLite (CFLite) is a continuous fuzzing layer for repos that do not run on Google's OSS-Fuzz infrastructure. Three workflows share the target list, and the header of .github/workflows/fuzz.yml states the division between them:
.github/workflows/cflite_pr.yml· changed-target sampling on a pull request, 60 seconds per target. It computes which source-path globs changed, intersects them with thefuzz/target-map.tomltriggers, and runs the matching subset..github/workflows/cflite_batch.yml· a nightly rotation that runs one target per night for 1,800 seconds, picked byday_index % cycle_sizeover the sorted target-map keys, with aninputs.targetoverride on manual dispatch..github/workflows/fuzz.yml· a nightly nativecargo fuzzmatrix over the full inventory, 30 minutes per target under AddressSanitizer, withtimeout-minutes: 45per matrix job.
A shared budget guard, scripts/check-fuzz-budget.sh, counts all three toward a 1,800 runner-minute trailing-30-day cap. On the pull-request lane the guard runs with GH_FUZZ_BUDGET_CAP_MODE: warn, which the workflow explains as reporting budget exhaustion without blocking an unrelated pull request.
The build entrypoint is .clusterfuzzlite/build.sh, which its own header says mirrors fuzz/oss-fuzz/build.sh target for target. Corpus storage is a sibling private repo rather than GCS. The nightly rotation reads secrets.FUZZ_CORPUS_PAT into a storage-repo action input when the secret is present and runs a no-storage variant of every step when it is not. The pull-request lane passes no storage-repo at all; both of its run steps are named for running without corpus storage.
The pull-request check
The required check is cflite_pr / changed-target-sampling. A budget check runs first, then the sampling job with a 75-minute job timeout whose comment records the sizing assumption behind it. The fuzz: full label promotes the run to a 120-second-per-target full sweep, and labeled is in the trigger list so adding the label to an open pull request fires a fresh run.
jobs:
budget-check:
name: fuzz-budget
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
- name: Verify 30-day fuzz budget
env:
GH_TOKEN: ${{ github.token }}
GH_FUZZ_BUDGET_RATE_LIMIT_MODE: warn
GH_FUZZ_BUDGET_CAP_MODE: warn
GH_FUZZ_BUDGET_WORKFLOWS: cflite_pr.yml,cflite_batch.yml,fuzz.yml
run: |
set -euo pipefail
# PR-time CFLite is an advisory release qualification signal. Report
# budget exhaustion without blocking unrelated PRs while the shared
# trailing window is already over cap.
scripts/check-fuzz-budget.sh "${{ github.repository }}"
changed-target-sampling:
name: changed-target-sampling
needs: budget-check
runs-on: ubuntu-latest
# The 25-target full sweep has a 50-minute fuzz budget before build time.
timeout-minutes: 75The detection step diffs against the merge base rather than the base commit, glob-matches each fuzz/target-map.toml trigger against the changed paths, and falls back to the full inventory when the diff touches the control plane itself. If nothing fires, a following step fails the job and prints the changed files, so a diff with no mapped target is a visible failure rather than a silent pass.
- name: Compute changed targets
id: targets
if: steps.mode.outputs.full == 'false'
run: |
set -euo pipefail
base_sha="${{ github.event.pull_request.base.sha }}"
head_sha="${{ github.event.pull_request.head.sha }}"
merge_base="$(git merge-base "${base_sha}" "${head_sha}")"
echo "merge-base: ${merge_base}"
git diff --name-only "${merge_base}" "${head_sha}" > changed.txt
mapfile -t targets < <(yq -p=toml -r '.targets | keys | .[]' \
fuzz/target-map.toml)
fired=()
for tgt in "${targets[@]}"; do
mapfile -t triggers < <(yq -p=toml -r \
".targets.${tgt}.triggers[]" fuzz/target-map.toml)
for trig in "${triggers[@]}"; do
regex="$(printf '%s' "${trig}" | sed -e 's|\.|\\.|g' \
-e 's|\*\*|__DSTAR__|g' -e 's|\*|[^/]*|g' \
-e 's|__DSTAR__|.*|g')"
if grep -qE "^${regex}$" changed.txt; then
fired+=("${tgt}")
break
fi
done
done
# Inventory fallback: builder, workflow, and target-map edits
# can break changed-target sampling without matching a source
# trigger. Run the full inventory for those control-plane edits.
if grep -qE '^(\.cargo/|\.clusterfuzzlite/|\.github/workflows/cflite_pr\.yml$|Cargo\.(toml|lock)$|fuzz/(Cargo\.toml|target-map\.toml)$|Makefile$|scripts/(ci-pr-tier|spec-drift-check|check-review-slices)\.(sh|py)$)' changed.txt; then
echo "fuzz inventory edit detected; firing the full inventory"
fired=("${targets[@]}")
fi
if ((${#fired[@]} == 0)); then
: > fired.txt
else
printf '%s\n' "${fired[@]}" | sort -u > fired.txt
fi
fired_count="$(wc -l < fired.txt | tr -d ' ')"
printf 'fired targets: %s\n' "${fired_count}"
cat fired.txt
{
echo "count=${fired_count}"
printf 'targets<<EOF\n%s\nEOF\n' "$(cat fired.txt)"
} >> "${GITHUB_OUTPUT}"The fuzz invocation is the upstream google/clusterfuzzlite action, pinned by commit SHA, fed the target list through the CHIO_CFLITE_TARGETS environment variable that .clusterfuzzlite/build.sh reads.
- name: Run ClusterFuzzLite changed-target sampling without corpus storage
if: steps.mode.outputs.full == 'false' && steps.targets.outputs.count != '0'
uses: google/clusterfuzzlite/actions/run_fuzzers@82652fb49e77bc29c35da1167bb286e93c6bcc05
env:
CHIO_CFLITE_TARGETS: ${{ steps.targets.outputs.targets }}
with:
language: rust
fuzz-seconds: ${{ steps.mode.outputs.fuzz_seconds }}
mode: code-change
sanitizer: address
github-token: ${{ secrets.GITHUB_TOKEN }}
report-unreproducible-crashes: falseCrashes upload as the cflite-pr-crashes-<PR> artifact from ./out/artifacts with a 14-day retention window. The nightly rotation picks its single target arithmetically from the sorted target-map keys, so the cycle length is the target count and every target comes round once per cycle.
- name: Pick today's target
id: pick
env:
FORCED_TARGET: ${{ inputs.target }}
run: |
set -euo pipefail
mapfile -t targets < <(yq -p=toml -r '.targets | keys | .[]' \
fuzz/target-map.toml | sort)
if (( ${#targets[@]} == 0 )); then
echo "fuzz/target-map.toml has no targets" >&2
exit 1
fi
if [[ -n "${FORCED_TARGET}" ]]; then
picked="${FORCED_TARGET}"
if ! printf '%s\n' "${targets[@]}" | grep -Fxq -- "${picked}"; then
echo "unknown forced fuzz target: ${picked}" >&2
exit 1
fi
else
day_index=$(( $(date -u +%s) / 86400 ))
picked="${targets[$(( day_index % ${#targets[@]} ))]}"
fi
printf 'picked: %s (rotation cycle: %d targets)\n' \
"${picked}" "${#targets[@]}"
{
echo "target=${picked}"
echo "cycle_size=${#targets[@]}"
} >> "${GITHUB_OUTPUT}"The project config both CFLite workflows share names the security contact and the sanitizer set.
# ClusterFuzzLite project config for Chio.
#
# Storage backend: backbay-labs/chio-fuzz-corpus sibling private repo (no GCS).
# The repo is created out-of-band before the first cflite_batch.yml run.
#
# NOTE: ClusterFuzzLite reads the corpus storage-repo as an input to
# the `run_fuzzers` and `build_fuzzers` GitHub Actions, NOT from
# project.yaml. The `storage-repo:` input is wired with `FUZZ_CORPUS_PAT`
# in `.github/workflows/cflite_pr.yml` and `.github/workflows/cflite_batch.yml`.
language: rust
primary_contact: "security@backbay.io"
auto_ccs:
- "security@backbay.io"
- "fuzzing@backbay.io"
sanitizers:
- address
- undefined
architectures:
- x86_64
fuzzing_engines:
- libfuzzer
report_to_oss_fuzz: falseRunning locally
Setup once:
rustup toolchain install nightly
cargo install cargo-fuzz --lockedRun a single target. The cargo-fuzz wrapper builds the binary with libFuzzer instrumentation, links the seed corpus from fuzz/corpus/<target>/, and forwards the rest of the args to libFuzzer:
cd fuzz
cargo +nightly fuzz run attest_verifylibFuzzer writes its own progress trailer to stderr as it runs: a line per pulse pairing the cumulative iteration count with the coverage edge count (cov), the feature count (ft), and the corpus size. The numbers are machine and seed dependent, so read the trend rather than the values.
Build only (the gate that mirrors CI's build_fuzzers action):
cargo +nightly fuzz build attest_verifyRun with a maximum total time budget (useful for local soak runs):
cargo +nightly fuzz run attest_verify -- -max_total_time=300The scheduled matrix installs a nightly toolchain rather than pinning a dated one: rustup toolchain install nightly --profile minimal --component rust-src. The dudect lane installs the same way, and its comment gives the reason: keeping both lanes on one toolchain keeps cross-tool diffing and sanitizer runs comparable.
Crashes, minimization, promotion
When libFuzzer finds a crashing input it writes the bytes under the artifact prefix and prints a sanitizer report naming the frame that faulted. The triage path is reproduce, minimize, classify, fix, promote.
cd fuzz
# Reproduce. cargo-fuzz consumes the crash-file path positionally, so
# libFuzzer replays that single input instead of running a fuzz loop.
cargo +nightly fuzz run <target> fuzz/artifacts/<target>/crash-<id>
# Minimize. tmin shrinks the input toward a smaller reproducer that
# still triggers the same crash signature.
cargo +nightly fuzz tmin <target> fuzz/artifacts/<target>/crash-<id>Promotion is scripts/promote_fuzz_seed.sh, which takes named arguments rather than positional ones. It reads fuzz/owners.toml to resolve the owning crate, computes the seed's SHA-256, and in every mode moves the seed to fuzz/corpus/<target>/<sha>.bin so later runs keep exercising it. Re-promoting a seed that already sits at that canonical path is a no-op rather than a delete.
# Reads fuzz/owners.toml to find the owning crate for the named fuzz
# target, computes sha256 of the input seed, then either:
#
# --mode libfuzzer Writes a libtest #[test] fn that calls the fuzz
# wrapper directly with the seed bytes. Output:
# crates/<owner>/tests/regression_<target>_<sha16>.rs.
# The 16-hex prefix plus target component avoids
# filename collisions when one owner crate hosts
# multiple targets or accumulates multiple promoted
# seeds.
#
# --mode proptest Writes the same regression test plus a paired
# proptest property when the owner crate has proptest
# in [dev-dependencies]. When proptest is missing,
# emits the plain regression test and prints a warning.
#
# --mode adversarial Promotes a libFuzzer crash that decodes cleanly
# through one of the trust-boundary surfaces into
# a triage-pending case under
# crates/core/chio-adversarial-suite/cases/<class>/<sha>.json
# with `expected_verdict: "DENY"` placeholder and
# `pending: true`. Requires --class (one of the
# eight attack classes) and --threat-id (a
# chio-threat-model.v1.json identifier). The
# pending flag is stripped only by a human triager.
# No regression .rs file is emitted; the
# adversarial harnesses already iterate the
# bundled cases.
#
# In all three modes the seed is moved into fuzz/corpus/<target>/<sha>.bin
# so future fuzz runs continue exercising it. Re-promoting a seed whose
# --input path already resolves to the canonical corpus location is a
# no-op (the script does not delete the corpus seed under itself).scripts/promote_fuzz_seed.sh --target canonical_json --input fuzz/artifacts/canonical_json/minimized-<id> --mode libfuzzerIn libfuzzer mode the script writes crates/<owner>/tests/regression_<target>_<sha16>.rs, a libtest #[test] that calls the fuzz wrapper directly with the seed bytes. The 16-hex prefix plus the target name keeps two promoted seeds in one owner crate from colliding. Because the result is a normal cargo test, the seed stays exercised when the fuzz lane is not running.
proptest mode adds a paired proptest property when the owner crate carries proptest in [dev-dependencies], and warns and falls back to the plain regression when it does not. adversarial mode emits no Rust file at all: it writes a case under crates/core/chio-adversarial-suite/cases/<class>/<sha>.json carrying expected_verdict: "DENY" and pending: true, and the script's header records that only a human triager strips the pending flag. That mode requires --class from a fixed list of eight (clock_rewound, future_dated, replayed_nonce, partial_signature, scope_superset, revocation_rollback, anchor_grafted, sigstore_bundle_payload_mismatch) and a --threat-id matching ^[a-z][a-z0-9_]*$. Passing either flag with another mode is rejected with --class and --threat-id are only accepted with --mode adversarial.
Coverage tracking
cargo-fuzz can dump an HTML coverage report after a run:
cd fuzz
cargo +nightly fuzz coverage <target>
cargo +nightly fuzz coverage <target> --htmlThe output lands in target/<target-triple>/coverage/<target>/ with per-line and per-region edge counts. Regions that the corpus does not reach are candidates for new seeds or for tightening the fuzz_target body to drive more state.
Corpus smoke test
fuzz/tests/smoke.rs runs on stable as a regular cargo test. Every target in CORPUS_SMOKE_TARGETS gets a <target>_smoke test that feeds its committed seeds through the owning crate's fuzz entry point; the targets in NO_IN_PROCESS_SMOKE_TARGETS are declared as outside that reusable harness and are covered by the seed-floor and inventory assertions only. Neither explores new inputs.Reproduce
The stable-toolchain check of everything on this page is the smoke suite. It parses fuzz/Cargo.toml, .github/workflows/fuzz.yml and fuzz/owners.toml itself, so a disagreement between the target list, the matrix, the owner map, or the corpora fails it.
cd fuzz
cargo test --test smokeA single coverage-guided target needs the nightly toolchain and cargo-fuzz from the setup above.
cd fuzz
cargo +nightly fuzz run attest_verify -- -max_total_time=60Scope and limits
- Trust-boundary only. Each target wraps the entry point where externally controlled bytes first hit Chio code. Internal helpers are not fuzzed directly; if they fail, the failure surfaces through one of the wrappers above.
- Fail-closed contract. Targets accept
Err(_)as an expected outcome; the harness rejects a panic, an assert failure, an unwrap, or a sanitizer report. - Sanitizers.
.clusterfuzzlite/project.yamldeclares bothaddressandundefined. Each CFLite action invocation names one, and both the pull-request and the nightly-rotation steps passsanitizer: address, so the undefined-behavior sanitizer is declared rather than exercised by those two lanes. - Coverage, not exhaustion. Fuzzing can find bugs reachable by mutating from the corpus within the run budget. It does not enumerate all inputs. Kani explores selected functions within configured bounds.
Next
- Kani Harnesses · the bounded model-checking complement to coverage-guided fuzzing.
- Differential Tests · the property-based lane for the same parser surfaces.
- Formal Assurance Overview · where the fuzz lane sits among the evidence kinds the proof manifest records.