fold/docs/USAGE.md
trvon b8c997c732
fix(scan): report quarantined TUs as coverage gaps
Problem: a TU skipped because a prior run quarantined it was not analysed,
but coverage did not say so. The serial path dropped it from the attempted
set, so a scan whose only TU was quarantined was complete and exited 0; the
fan-out path saw no file_finished record and called it frontend-failed.

Fix: the shard runner counts every requested TU and records skipped ones
with gap reason `quarantined`; a worker emits a file_finished record with
that reason so the parent classifies it the same way. USAGE lists the reason.

Test: test_subprocess_fanout "[quarantine]" covers serial (was 2 attempted,
no gap) and fan-out (was frontend-failed). CLI probe: serial and fan-out
both exit 2 with a `quarantined` gap.
2026-09-24 21:41:42 -06:00

21 KiB

Usage

CLI

fold scan [options] [--compile-db <path> | <files...>]
fold scan --mode hybrid --compile-db <path> --binary <path> [options]
fold scan --input-type binary --binary <path> [options]
fold confirm [options] <compile_commands.json>
fold quality [--compile-db <path>] [--manifest <project.json>] [options]

--compile-db is optional. If omitted, pass source files as positional arguments and fold infers compile flags:

fold scan src/*.cpp --detectors null-deref

# Or with explicit C++ standard:
fold scan --std c++20 file.cpp --detectors dead

If a compile_flags.txt exists in the source tree, fold reads it automatically (clang-tidy convention).

quality command

fold quality is the local quality gate. It checks formatting and diff hygiene, runs the focused evaluator/source-structure tests, evaluates a manifest policy, and, when --compile-db is supplied, scans changed C/C++ files. It writes an aggregated summary.json under build/quality by default.

fold quality --compile-db build/release/compile_commands.json
fold quality --skip-policy --allow-findings

Use --out-dir, --manifest, and the individual --skip-* switches to adapt the gate for a focused local iteration. Exit status 1 means a completed quality gate failed; 2 means setup or execution failed.

scan options

Flag Description
--detectors NAME ... Which detectors to run (default: all)
--format sarif|findings-json|markdown|json Output format (default: SARIF). findings-json emits the versioned native envelope; json is a deprecated SARIF alias.
--output PATH Write output to file
--processes N Worker processes (default: auto). Ordinary source scans use an isolated worker even at 1; profile/incremental scans currently use the in-process serial path.
--budget-seconds N Per-TU time budget
--per-query-ms N Per-Z3-query timeout
--loop-unroll N Loop unrolling depth (default: 3)
--exclude-glob PATTERN Skip files matching glob. Relative patterns also match absolute compile-database paths at component boundaries.
--trace Log Z3 query traces
--scope security|quality|redundancy|binary|all High-level scan scope; expands to the matching detector category. Composes with --detectors
--profile JSON performance profile (metrics). A value is a deprecated alias for --preset
--profile-output PATH Atomically write profile JSON and imply --profile; must differ from --output.
--preset NAME Apply a modeling preset (currently freestanding, the embedded-firmware bundle)
--complexity-threshold N Cyclomatic-complexity threshold for the complexity detector (default 15)
--profile-solver Per-detector Z3 statistics
--profile-reach Encoder coverage report
--incremental Skip functions unchanged since last scan
--build-summaries Build leaf-function symbolic summaries
--config-profile PATH Load a strict versioned runtime-configuration profile and persist privacy-safe guarded callsites (source/hybrid only)
--portfolio Use parallel Z3 tactic portfolio
--nullable-allocations Opt in to heuristic null-deref findings for unchecked malloc/calloc/new results
--suppressions PATH Suppress findings by detector, file:line, or finding-id prefix
--baseline PATH Apply previous-scan baseline JSON (accepted/dismissed findings)
--mode auto|source|binary|hybrid|rust|swift Select scan router mode (default: auto)
--input-type source|binary (hidden) Deprecated compatibility alias for source/binary mode
--binary PATH Binary, object file, or firmware image for binary mode
--binary-max-functions N Limit binary function extraction (0 = all)
--hybrid-strategy merge-only Hybrid routing strategy. Unimplemented hint-exchange strategies are rejected rather than silently falling back.
--source-detectors NAME ... Source-side detector override for hybrid scans
--binary-detectors NAME ... Binary-side detector override for hybrid scans
--havoc-external-symbols Model external/linker symbols (_edata, weak, [[gnu::section]]) as opaque addresses instead of concrete constants
--havoc-statics-at-entry Havoc mutable static/global state to an unknown value at function entry unless provably single-assignment
--volatile-read-havoc Treat each volatile/MMIO read as a fresh unknown (reserved; volatile derefs already return fresh unknowns)
--suppress-loop-bound-always-conditions Don't report a loop's own controlling predicate as always-true when the proof leans on the bounded-unroll loop bound

Use fold scan --advanced-help for the generated detector-tuning, profiling, cache, and quarantine option table. These flags remain callable but are omitted from normal help to keep the common scan workflow concise.

Native finding JSON uses the stable envelope {"output_version":1,"tool":{"name":"fold","version":"..."},"findings":[...]}. SARIF remains the interoperability format; Markdown remains the human-readable report. Requested report/profile paths are written atomically; failures return exit 2. Console counts and both machine formats use the same filtered and deduplicated finding set.

Both machine formats record which C/C++ translation units were analyzed: the native JSON gains a coverage object (tus_attempted, tus_analyzed, and one gaps entry per unanalyzed TU with a reason: compile-errors, frontend-failed, analysis-failed, timeout, crash, worker-exit, or quarantined for a TU a prior run's quarantine skipped), and SARIF carries the same data as an invocations entry with executionSuccessful and one toolExecutionNotifications entry per gap. A scan with any gap, or one that analyzed no TU, exits 2 after writing its report: no findings from an incomplete scan is not evidence of no bugs. Diagnostics go to stderr, so a report written to stdout stays parseable.

fold --version reports the Meson semantic version plus Git revision and a .dirty suffix for builds configured from modified trees. SARIF, native JSON, profiles, and benchmark run artifacts retain the same provenance.

Query existing finding reports

fold grep searches Fold SARIF and native findings-json reports, not arbitrary source text. Matching is bounded fixed-string matching; regex input is not accepted.

fold grep "null pointer" findings.sarif --field message --ignore-case
fold grep "fold.null-deref" findings.sarif --field rule \
  --backend source --path-glob 'src/*.cpp' --actionable-only
fold grep "token" source.sarif binary.json --format json --output matches.json

Searchable fields are message, id, rule, path, function, and evidence; omitting --field searches all of them. Typed filters cover rule, confidence, backend provenance, path glob, baseline status, and suppression. --offset and --limit provide deterministic ID/location-ordered pagination. JSON output includes totals, actionable totals, next-offset, has-more, and truncation metadata while retaining complete finding records on the selected page. Exit status is 1 when the query matches an unsuppressed finding, 0 when it does not, and 2 for malformed reports or other input/output failures.

Configuration profiles

--config-profile accepts a strict version-1 JSON artifact for source and hybrid scans. It records normalized guards on callsites without pruning graph edges; whole-program reachability classifications are a separate analysis phase. Profiles support typed Boolean or signed-integer globals, entry arguments, zero-argument accessors, and getenv set/unset state:

{
  "version": 1,
  "entrypoints": ["main"],
  "assumptions": [
    {"kind": "global", "selector": "feature_enabled", "value": false},
    {"kind": "entry_arg", "entrypoint": "main", "index": 1, "value": 4},
    {"kind": "getenv", "selector": "FEATURE_X", "state": "unset"},
    {"kind": "accessor", "selector": "Config::enabled", "value": true}
  ]
}

Unknown fields, duplicate selectors, type mismatches, unsupported versions, and invalid entrypoint references fail with exit 2. Persisted guards contain only opaque SHA-256 symbol IDs; performance telemetry exposes the profile hash, version, and aggregate projected/unconditional/unknown counts, never selectors or source expressions.

freestanding preset

fold scan --preset freestanding ... bundles the four embedded-firmware modeling toggles plus secondary-section rendering. On freestanding/bare-metal code fold's bounded symbolic engine otherwise over-reports: it concretizes values it cannot soundly know (linker symbols, volatile/MMIO reads, mutable statics) and reports the bounded-unroll in-bound loop guard as a universal truth. Each shortcut produces a false Proved finding, which is why fold is advisory rather than gating on firmware.

The preset is equivalent to:

--havoc-external-symbols --havoc-statics-at-entry --volatile-read-havoc \
  --suppress-loop-bound-always-conditions   # + redundancy-lane-secondary
Gap Symptom Fix
A — bounded unroll i < 6 / i >= 0 loop guard reported always-true The loop induction variable is pinned with a LoopBoundUnroll tracked assumption; when a proof's Z3 UNSAT core leans on it the finding is downgraded Proved → Heuristic or suppressed
B — linker symbols dst < &_edata always-true External/linker symbol addresses become opaque unknowns (soundness fix — the real bug also becomes findable)
C — volatile reads busy-wait exit edge pruned Volatile/MMIO loads return a fresh unknown per read
D — mutable statics heap + n > limit always-false (_sbrk) Mutable static locals are havoc'd at entry instead of re-applying the initializer each call

The four modeling toggles are gated and default off, so a normal fold scan (no preset) behaves exactly as before. Under the preset the syntactic redundancy lane (clone/equiv/dead/always-condition) renders as a clearly-secondary report section (markdown) or carries a properties.category: "redundancy" tag (SARIF/JSON) so it doesn't bury the security findings.

binary backend

The binary backend is a prototype that uses radare2 to recover functions, sections, calls, strings, and pseudocode-like text. It reuses fold's Finding and reporting pipeline, but it does not yet run the Clang/Z3 source detectors on a lifted IR. Current binary findings are heuristic and focus on:

  • hardcoded secrets, API keys, URLs, and private-key markers in binary strings
  • calls to known unsafe C routines such as strcpy, sprintf, and gets
  • simple null-deref patterns in radare2 pseudocode

Example:

fold scan --input-type binary --binary /usr/bin/curl \
  --binary-max-functions 64 --detectors security --format sarif

--binary-max-functions is useful for smoke tests and large system binaries. Use 0 (the default) for a full binary scan.

Rust programs can use the same binary path after compilation:

rustc -C opt-level=0 src/main.rs -o target/fold-rust-smoke
fold scan --mode binary --binary target/fold-rust-smoke --detectors security

rust frontend

Rust source inputs route through fold-rust when --mode rust is selected or when auto mode sees .rs files / Cargo crate directories. Use binary mode for compiled Rust artifacts; source and binary provenance stay separate.

fold scan --mode rust path/to/crate --detectors rust-security --format sarif

fold-rust itself requires a nightly rustc_private analyzer at Rust 1.98 or newer with rustc-dev, rustfmt, and llvm-tools-preview. The authoritative analyzer pin is tools/fold-rust/rust-toolchain.toml; Meson does not duplicate it. A crate may select a different Cargo target nightly: the workspace wrapper keeps using the analyzer binary's linked compiler while preserving Cargo's crate graph and arguments. Inspect the compiled analyzer with:

fold-rust toolchain-info
python3 scripts/check_rust_toolchain_skew.py \
  --output build/release/meson-logs/rust-toolchain-skew.json

The check dynamically uses installed toolchains, exercises same-version and newer-target scans, records exact releases/components, and emits an explicit skip when no newer nightly is installed. Stable toolchains are listed with the actionable reason they cannot be used to build the rustc_private analyzer. The check never downloads or silently substitutes a toolchain.

Cargo workspace scans refuse to execute build.rs scripts or proc macros by default. Scan only trusted workspaces with the explicit opt-in:

fold scan --mode rust path/to/trusted-crate --rust-allow-build-code

fold fuzz prep also accepts Rust scans and writes the normal findings.predicted.json file plus the shared confirmation sidecar confirmation.tasks.json. Current Rust detectors are HIR heuristics and do not yet emit Z3 path witnesses, so Rust fuzz-prep runs may produce an empty prediction set until Rust-specific witness extraction / cargo-fuzz harness emission lands.

swift frontend

Swift source inputs route through fold-swift when --mode swift is selected or when auto mode sees .swift files. fold-swift invokes swiftc -emit-sil on each input and emits NDJSON with function metadata, findings (heuristic), and interface records. Use binary mode for compiled Swift artifacts; source and binary provenance stay separate.

fold scan --mode swift path/to/file.swift --detectors rust-unsafe-api,null-deref --format sarif

Current Swift detectors are SIL heuristics:

Detector Hazard
rust-unsafe-api unchecked_ref_cast, unchecked_addr_cast, unchecked_trivial_bit_cast bypass Swift type safety
null-deref load through raw UnsafePointer without provenance check

Swift findings are heuristic today; they preserve backend provenance and stable rule IDs, but do not yet perform interprocedural taint proof or SIL-level path feasibility.

hybrid router

Hybrid mode runs the source and binary backends in one in-memory router pass and emits a single report. The initial implementation is merge-only: source findings and binary findings are produced independently, tagged with backend provenance, and merged without writing intermediate SARIF files.

fold scan --mode hybrid \
  --compile-db build/compile_commands.json \
  --binary build/app \
  --detectors security \
  --format sarif \
  --output fold-hybrid.sarif

--mode auto selects hybrid when both source inputs and --binary are present. --input-type binary --binary <path> remains supported for existing scripts.

suppressions and baselines

Suppression files are plain text, one pattern per line. Blank lines and # comments are ignored. Patterns match either detector name, exact file:line, or finding-id prefix:

# suppress all integer-overflow findings
int-overflow

# suppress one location
src/foo.cpp:42

# suppress an ID family
dead:

Baselines are JSON files with current finding decisions:

{
  "version": 1,
  "tool": "fold",
  "findings": [
    {"id": "null-deref:src/foo.cpp:42:10", "status": "accepted"},
    {"id": "int-overflow:src/bar.cpp:12:7", "status": "dismissed"}
  ]
}

Current scans mark unmatched findings as new; dismissed baseline entries are reported as suppressed. SARIF encodes suppressed findings with level: "none" and an external suppression justification.

confirmation and fuzz witness output

Fold has two confirmation surfaces:

  • fold confirm: deeper bounded symbolic rerun for selected source findings.
  • fold fuzz prep / fold fuzz run: libFuzzer + ASan confirmation that promotes matched crashes to confidence: "concrete-witness".

fold fuzz prep preserves legacy findings.predicted.json and also emits confirmation.tasks.json. fold fuzz run preserves the augmented findings JSON and also writes confirmation.results.json in the prep directory.

An optional KLEE smoke adapter exists for development validation:

tests/confirmation/klee/run_klee_smoke.sh

It skips cleanly when KLEE is not installed.

confirm options

Flag Description
--finding ID Confirm a specific finding
--rule DETECTOR Confirm all findings of a detector
--at FILE:LINE Confirm findings at a location
--unwind N Loop unrolling depth (default: 10)
--budget N Per-function solver budget (ms)
--witness DIR Write SV-COMP GraphML witnesses
--output PATH Write confirmation report

Detectors

Detector CWE Method
clone — Structural hash + Levenshtein (Type 2/3)
equiv — Z3-proved semantic equivalence (Type 4)
class-dup — Cross-class member equivalence
complexity — McCabe-style complexity; available for C++ and Rust HIR functions
dead 561 Path predicate UNSAT for branch
null-deref 476 SAT-check pointer==0 under path
div-by-zero 369 SAT-check divisor==0 under path
int-overflow 190 BV overflow predicates at source width
oob 121 Array subscript bounds + stdlib buffer size
int-truncation 197 Implicit integer truncation
sign-conversion 195 Value-changing signed/unsigned casts
taint-track — Untrusted-input-to-sink flow
rust-unsafe-api — HIR scan for unsafe Rust APIs such as transmute, from_raw_parts, and set_len
rust-unchecked-index — HIR scan for get_unchecked / get_unchecked_mut bounds bypasses
rust-path-traversal 22 Heuristic Rust filesystem path sink scan
rust-command-injection 78 Heuristic Rust std::process::Command argument sink scan
rust-crypto-misuse 327 Heuristic weak-hash / crypto misuse scan

Rust detector findings are heuristic today: they preserve backend provenance and stable rule IDs, but they do not yet perform interprocedural taint proof or MIR-level path feasibility.

Project Evaluation

Use the manifest-driven runner to profile Fold and evaluate finding drift on a real C++ source tree, Rust workspace, or compiled binary. It writes SARIF, profile JSON, immutable run artifacts, and a summary.json/summary.md with stability, resource, disposition, gold, and baseline-comparison results.

python3 scripts/benchmarks/run_project_eval.py \
  --manifest tests/benchmarks/projects/yams_source.json \
  --out-dir /tmp/fold-yams-eval

python3 scripts/benchmarks/run_project_eval.py \
  --manifest tests/benchmarks/projects/sage_rust.json \
  --out-dir /tmp/fold-sage-eval

Manifest schema version 1 is validated by fold_eval.project. A manifest pins the lane, corpus/build input, detector scope, budgets, repetitions, and optional gold/disposition/performance-baseline inputs. Relative paths resolve from the manifest directory. Rust manifests automatically use the frontend's pinned toolchain unless analysis.rust_toolchain overrides it. Set evaluation.min_functions for a production Rust or binary target to fail closed when frontend/toolchain failure produces an empty scan.

For binary projects, set lane to binary and provide one or more corpus.binaries paths. Binary observations remain heuristic, but retain artifact/function identity for review even when no source line is available.

fold scan --profile --profile-output profile.json emits comparable top-level timings across lanes. C++ source profiles provide source-phase and detector/Z3 metrics; Rust/Swift profiles add frontend_extraction_ms; binary profiles add binary-heuristic, binary-ir, and binary-pcode rows, including P-Code Z3 query results.

Output

SARIF 2.1.0 with properties.confidence: concrete-witness (runtime ASan/libFuzzer confirmation), proved (Z3 proof under Fold's bounded model), heuristic (over-approx in path), syntactic (structural only).

MCP

fold mcp

JSON-RPC 2.0 over stdio. Protocol version 2024-11-05. Advertised tools: fold.scan (router-backed source/binary/hybrid/rust scan), fold.scan_start / fold.scan_status / fold.scan_jobs for queued scans, fold.scan_job_findings / fold.scan_activate for completed jobs, fold.get_scan_context, fold.get_findings, fold.query_findings, fold.suggest_merge, and fold.explain. fold.query_findings applies the same fixed-string fields, typed filters, and pagination engine as fold grep to either the current context or a completed job_id. Session-scoped finding/context cache. Use fold.scan with background: true, or fold.scan_start, for long-running scans. Deprecated scan and detector wrapper names remain callable but are hidden from tools/list.

Rust inputs are passed as paths and route to the Rust frontend when mode is rust or when mode is omitted and the paths are .rs files or Cargo crate directories.

LSP

fold lsp

Content-Length-framed JSON-RPC. Diagnostics through textDocument/didOpen and textDocument/didChange. Code actions for fold findings.