zeroize-audit — Claude Skill
When to Use
- Auditing cryptographic implementations (keys, seeds, nonces, secrets)
- Reviewing authentication systems (passwords, tokens, session data)
- Analyzing code that handles PII or sensitive credentials
- Verifying secure cleanup in security-critical codebases
- Investigating memory safety of sensitive data handling
When NOT to Use
- General code review without security focus
- Performance optimization (unless related to secure wiping)
- Refactoring tasks not related to sensitive data
- Code without identifiable secrets or sensitive values
Purpose
Detect missing zeroization of sensitive data in source code and identify zeroization that is removed or weakened by compiler optimizations (e.g., dead-store elimination), with mandatory LLVM IR/asm evidence. Capabilities include:
- Assembly-level analysis for register spills and stack retention
- Data-flow tracking for secret copies
- Heap allocator security warnings
- Semantic IR analysis for loop unrolling and SSA form
- Control-flow graph analysis for path coverage verification
- Runtime validation test generation
Scope
- Read-only against the target codebase (does not modify audited code; writes analysis artifacts to a temporary working directory).
- Produces a structured report (JSON).
- Requires valid build context (
compile_commands.json) and compilable translation units.
- "Optimized away" findings only allowed with compiler evidence (IR/asm diff).
Inputs
See {baseDir}/schemas/input.json for the full schema. Key fields:
| Field |
Required |
Default |
Description |
path |
yes |
— |
Repo root |
compile_db |
no |
null |
Path to compile_commands.json for C/C++ analysis. Required if cargo_manifest is not set. |
cargo_manifest |
no |
null |
Path to Cargo.toml for Rust crate analysis. Required if compile_db is not set. |
config |
no |
— |
YAML defining heuristics and approved wipes |
opt_levels |
no |
["O0","O1","O2"] |
Optimization levels for IR comparison. O1 is the diagnostic level: if a wipe disappears at O1 it is simple DSE; O2 catches more aggressive eliminations. |
languages |
no |
["c","cpp","rust"] |
Languages to analyze |
max_tus |
no |
— |
Limit on translation units processed from compile DB |
mcp_mode |
no |
prefer |
off, prefer, or require — controls Serena MCP usage |
mcp_required_for_advanced |
no |
true |
Downgrade SECRET_COPY, MISSING_ON_ERROR_PATH, and NOT_DOMINATING_EXITS to needs_review when MCP is unavailable |
mcp_timeout_ms |
no |
— |
Timeout budget for MCP semantic queries |
poc_categories |
no |
all 11 exploitable |
Finding categories for which to generate PoCs. C/C++ findings: all 11 categories supported. Rust findings: only MISSING_SOURCE_ZEROIZE, SECRET_COPY, and PARTIAL_WIPE are supported; other Rust categories are marked poc_supported=false. |
poc_output_dir |
no |
generated_pocs/ |
Output directory for generated PoCs |
enable_asm |
no |
true |
Enable assembly emission and analysis (Step 8); produces STACK_RETENTION, REGISTER_SPILL. Auto-disabled if emit_asm.sh is missing. |
enable_semantic_ir |
no |
false |
Enable semantic LLVM IR analysis (Step 9); produces LOOP_UNROLLED_INCOMPLETE |
enable_cfg |
no |
false |
Enable control-flow graph analysis (Step 10); produces MISSING_ON_ERROR_PATH, NOT_DOMINATING_EXITS |
enable_runtime_tests |
no |
false |
Enable runtime test harness generation (Step 11) |
Prerequisites
Before running, verify the following. Each has a defined failure mode.
C/C++ prerequisites:
| Prerequisite |
Failure mode if missing |
compile_commands.json at compile_db path |
Fail fast — do not proceed |
clang on PATH |
Fail fast — IR/ASM analysis impossible |
uvx on PATH (for Serena) |
If mcp_mode=require: fail. If mcp_mode=prefer: continue without MCP; downgrade affected findings per Confidence Gating rules. |
{baseDir}/tools/extract_compile_flags.py |
Fail fast — cannot extract per-TU flags |
{baseDir}/tools/emit_ir.sh |
Fail fast — IR analysis impossible |
{baseDir}/tools/emit_asm.sh |
Warn and skip assembly findings (STACK_RETENTION, REGISTER_SPILL) |
{baseDir}/tools/mcp/check_mcp.sh |
Warn and treat as MCP unavailable |
{baseDir}/tools/mcp/normalize_mcp_evidence.py |
Warn and use raw MCP output |
Rust prerequisites:
| Prerequisite |
Failure mode if missing |
Cargo.toml at cargo_manifest path |
Fail fast — do not proceed |
cargo check passes |
Fail fast — crate must be buildable |
cargo +nightly on PATH |
Fail fast — nightly required for MIR and LLVM IR emission |
uv on PATH |
Fail fast — required to run Python analysis scripts |
{baseDir}/tools/validate_rust_toolchain.sh |
Warn — run preflight manually. Checks all tools, scripts, nightly, and optionally cargo check. Use --json for machine-readable output, --manifest to also validate the crate builds. |
{baseDir}/tools/emit_rust_mir.sh |
Fail fast — MIR analysis impossible (--opt, --crate, --bin/--lib supported; --out can be file or directory) |
{baseDir}/tools/emit_rust_ir.sh |
Fail fast — LLVM IR analysis impossible (--opt required; --crate, --bin/--lib supported; --out must be .ll) |
{baseDir}/tools/emit_rust_asm.sh |
Warn and skip assembly findings (STACK_RETENTION, REGISTER_SPILL). Supports --opt, --crate, --bin/--lib, --target, --intel-syntax; --out can be .s file or directory. |
{baseDir}/tools/diff_rust_mir.sh |
Warn and skip MIR-level optimization comparison. Accepts 2+ MIR files, normalizes, diffs pairwise, and reports first opt level where zeroize/drop-glue patterns disappear. |
{baseDir}/tools/scripts/semantic_audit.py |
Warn and skip semantic source analysis |
{baseDir}/tools/scripts/find_dangerous_apis.py |
Warn and skip dangerous API scan |
{baseDir}/tools/scripts/check_mir_patterns.py |
Warn and skip MIR analysis |
{baseDir}/tools/scripts/check_llvm_patterns.py |
Warn and skip LLVM IR analysis |
{baseDir}/tools/scripts/check_rust_asm.py |
Warn and skip Rust assembly analysis (STACK_RETENTION, REGISTER_SPILL, drop-glue checks). Dispatches to check_rust_asm_x86.py (production) or check_rust_asm_aarch64.py (EXPERIMENTAL — AArch64 findings require manual verification). |
{baseDir}/tools/scripts/check_rust_asm_x86.py |
Required by check_rust_asm.py for x86-64 analysis; warn and skip if missing |
{baseDir}/tools/scripts/check_rust_asm_aarch64.py |
Required by check_rust_asm.py for AArch64 analysis (EXPERIMENTAL); warn and skip if missing |
Common prerequisite:
| Prerequisite |
Failure mode if missing |
{baseDir}/tools/generate_poc.py |
Fail fast — PoC generation is mandatory |
Approved Wipe APIs
The following are recognized as valid zeroization. Configure additional entries in {baseDir}/configs/.
C/C++
explicit_bzero
memset_s
SecureZeroMemory
OPENSSL_cleanse
sodium_memzero
- Volatile wipe loops (pattern-based; see
volatile_wipe_patterns in {baseDir}/configs/default.yaml)
- In IR:
llvm.memset with volatile flag, volatile stores, or non-elidable wipe call
Rust
zeroize::Zeroize trait (zeroize() method)
Zeroizing<T> wrapper (drop-based)
ZeroizeOnDrop derive macro
Finding Capabilities
Findings are grouped by required evidence. Only attempt findings for which the required tooling is available.
| Finding ID |
Description |
Requires |
PoC Support |
MISSING_SOURCE_ZEROIZE |
No zeroization found in source |
Source only |
Yes (C/C++ + Rust) |
PARTIAL_WIPE |
Incorrect size or incomplete wipe |
Source only |
Yes (C/C++ + Rust) |
NOT_ON_ALL_PATHS |
Zeroization missing on some control-flow paths (heuristic) |
Source only |
Yes (C/C++ only) |
SECRET_COPY |
Sensitive data copied without zeroization tracking |
Source + MCP preferred |
Yes (C/C++ + Rust) |
INSECURE_HEAP_ALLOC |
Secret uses insecure allocator (malloc vs. secure_malloc) |
Source only |
Yes (C/C++ only) |
OPTIMIZED_AWAY_ZEROIZE |
Compiler removed zeroization |
IR diff required (never source-only) |
Yes |
STACK_RETENTION |
Stack frame may retain secrets after return |
Assembly required (C/C++); LLVM IR alloca+lifetime.end evidence (Rust); assembly corroboration upgrades to confirmed |
Yes (C/C++ only) |
REGISTER_SPILL |
Secrets spilled from registers to stack |
Assembly required (C/C++); LLVM IR load+call-site evidence (Rust); assembly corroboration upgrades to confirmed |
Yes (C/C++ only) |
MISSING_ON_ERROR_PATH |
Error-handling paths lack cleanup |
CFG or MCP required |
Yes |
NOT_DOMINATING_EXITS |
Wipe doesn't dominate all exits |
CFG or MCP required |
Yes |
LOOP_UNROLLED_INCOMPLETE |
Unrolled loop wipe is incomplete |
Semantic IR required |
Yes |
Agent Architecture
The analysis pipeline uses 11 agents across 8 phases, invoked by the orchestrator ({baseDir}/prompts/task.md) via Task. Agents write persistent finding files to a shared working directory (/tmp/zeroize-audit-{run_id}/), enabling parallel execution and protecting against context pressure.
| Agent |
Phase |
Purpose |
Output Directory |
0-preflight |
Phase 0 |
Preflight checks (tools, toolchain, compile DB, crate build), config merge, workdir creation, TU enumeration |
{workdir}/ |
1-mcp-resolver |
Phase 1, Wave 1 (C/C++ only) |
Resolve symbols, types, and cross-file references via Serena MCP |
mcp-evidence/ |
2-source-analyzer |
Phase 1, Wave 2a (C/C++ only) |
Identify sensitive objects, detect wipes, validate correctness, data-flow/heap |
source-analysis/ |
2b-rust-source-analyzer |
Phase 1, Wave 2b (Rust only, parallel with 2a) |
Rustdoc JSON trait-aware analysis + dangerous API grep |
source-analysis/ |
3-tu-compiler-analyzer |
Phase 2, Wave 3 (C/C++ only, N parallel) |
Per-TU IR diff, assembly, semantic IR, CFG analysis |
compiler-analysis/{tu_hash}/ |
3b-rust-compiler-analyzer |
Phase 2, Wave 3R (Rust only, single agent) |
Crate-level MIR, LLVM IR, and assembly analysis |
rust-compiler-analysis/ |
4-report-assembler |
Phase 3 (interim) + Phase 6 (final) |
Collect findings from all agents, apply confidence gates; merge PoC results and produce final report |
report/ |
5-poc-generator |
Phase 4 |
Craft bespoke proof-of-concept programs (C/C++: all categories; Rust: MISSING_SOURCE_ZEROIZE, SECRET_COPY, PARTIAL_WIPE) |
poc/ |
5b-poc-validator |
Phase 5 |
Compile and run all PoCs |
poc/ |
5c-poc-verifier |
Phase 5 |
Verify each PoC proves its claimed finding |
poc/ |
6-test-generator |
Phase 7 (optional) |
Generate runtime validation test harnesses |
tests/ |
The orchestrator reads one per-phase workflow file from {baseDir}/workflows/ at a time, and maintains orchestrator-state.json for recovery after context compression. Agents receive configuration by file path (config_path), not by value.
Execution flow
Phase 0: 0-preflight agent — Preflight + config + create workdir + enumerate TUs
→ writes orchestrator-state.json, merged-config.yaml, preflight.json
Phase 1: Wave 1: 1-mcp-resolver (skip if mcp_mode=off OR language_mode=rust)
Wave 2a: 2-source-analyzer (C/C++ only; skip if no compile_db) ─┐ parallel
Wave 2b: 2b-rust-source-analyzer (Rust only; skip if no cargo_manifest) ─┘
Phase 2: Wave 3: 3-tu-compiler-analyzer x N (C/C++ only; parallel per TU)
Wave 3R: 3b-rust-compiler-analyzer (Rust only; single crate-level agent)
Phase 3: Wave 4: 4-report-assembler (mode=interim → findings.json; reads all agent outputs)
Phase 4: Wave 5: 5-poc-generator (C/C++: all categories; Rust: MISSING_SOURCE_ZEROIZE, SECRET_COPY, PARTIAL_WIPE; other Rust findings: poc_supported=false)
Phase 5: PoC Validation & Verification
Step 1: 5b-poc-validator agent (compile and run all PoCs)
Step 2: 5c-poc-verifier agent (verify each PoC proves its claimed finding)
Step 3: Orchestrator presents verification failures to user via AskUserQuestion
Step 4: Orchestrator merges all results into poc_final_results.json
Phase 6: Wave 6: 4-report-assembler (mode=final → merge PoC results, final-report.md)
Phase 7: Wave 7: 6-test-generator (optional)
Phase 8: Orchestrator — Return final-report.md
Cross-Reference Convention
IDs are namespaced per agent to prevent collisions during parallel execution:
| Entity |
Pattern |
Assigned By |
| Sensitive object (C/C++) |
SO-0001–SO-4999 |
2-source-analyzer |
| Sensitive object (Rust) |
SO-5000–SO-9999 (Rust namespace) |
2b-rust-source-analyzer |
| Source finding (C/C++) |
F-SRC-NNNN |
2-source-analyzer |
| Source finding (Rust) |
F-RUST-SRC-NNNN |
2b-rust-source-analyzer |
| IR finding (C/C++) |
F-IR-{tu_hash}-NNNN |
3-tu-compiler-analyzer |
| ASM finding (C/C++) |
F-ASM-{tu_hash}-NNNN |
3-tu-compiler-analyzer |
| CFG finding |
F-CFG-{tu_hash}-NNNN |
3-tu-compiler-analyzer |
| Semantic IR finding |
F-SIR-{tu_hash}-NNNN |
3-tu-compiler-analyzer |
| Rust MIR finding |
F-RUST-MIR-NNNN |
3b-rust-compiler-analyzer |
| Rust LLVM IR finding |
F-RUST-IR-NNNN |
3b-rust-compiler-analyzer |
| Rust assembly finding |
F-RUST-ASM-NNNN |
3b-rust-compiler-analyzer |
| Translation unit |
TU-{hash} |
Orchestrator |
| Final finding |
ZA-NNNN |
4-report-assembler |
Every finding JSON object includes related_objects, related_findings, and evidence_files fields for cross-referencing between agents.
Detection Strategy
Analysis runs in two phases. For complete step-by-step guidance, see {baseDir}/references/detection-strategy.md.
| Phase |
Steps |
Findings produced |
Required tooling |
| Phase 1 (Source) |
1–6 |
MISSING_SOURCE_ZEROIZE, PARTIAL_WIPE, NOT_ON_ALL_PATHS, SECRET_COPY, INSECURE_HEAP_ALLOC |
Source + compile DB |
| Phase 2 (Compiler) |
7–12 |
OPTIMIZED_AWAY_ZEROIZE, STACK_RETENTION, REGISTER_SPILL, LOOP_UNROLLED_INCOMPLETE†, MISSING_ON_ERROR_PATH‡, NOT_DOMINATING_EXITS‡ |
clang, IR/ASM tools |
* requires enable_asm=true (default)
† requires enable_semantic_ir=true
‡ requires enable_cfg=true
Output Format
Each run produces two outputs:
final-report.md — Comprehensive markdown report (primary human-readable output)
findings.json — Structured JSON matching {baseDir}/schemas/output.json (for machine consumption and downstream tools)
Markdown Report Structure
The markdown report (final-report.md) contains these sections:
- Header: Run metadata (run_id, timestamp, repo, compile_db, config summary)
- Executive Summary: Finding counts by severity, confidence, and category
- Sensitive Objects Inventory: Table of all identified objects with IDs, types, locations
- Findings: Grouped by severity then confidence. Each finding includes location, object, all evidence (source/IR/ASM/CFG), compiler evidence details, and recommended fix
- Superseded Findings: Source findings replaced by CFG-backed findings
- Confidence Gate Summary: Downgrades applied and overrides rejected
- Analysis Coverage: TUs analyzed, agent success/failure, features enabled
- Appendix: Evidence Files: Mapping of finding IDs to evidence file paths
Structured JSON
The findings.json file follows the schema in {baseDir}/schemas/output.json. Each Finding object:
{
"id": "ZA-0001",
"category": "OPTIMIZED_AWAY_ZEROIZE",
"severity": "high",
"confidence": "confirmed",
"language": "c",
"file": "src/crypto.c",
"line": 42,
"symbol": "key_buf",
"evidence": "store volatile i8 0 count: O0=32, O2=0 — wipe eliminated by DSE",
"compiler_evidence": {
"opt_levels": ["O0", "O2"],
"o0": "32 volatile stores targeting key_buf",
"o2": "0 volatile stores (all eliminated)",
"diff_summary": "All volatile wipe stores removed at O2 — classic DSE pattern"
},
"suggested_fix": "Replace memset with explicit_bzero or add compiler_fence(SeqCst) after the wipe",
"poc": {
"file": "generated_pocs/ZA-0001.c",
"makefile_target": "ZA-0001",
"compile_opt": "-O2",
"requires_manual_adjustment": false,
"validated": true,
"validation_result": "exploitable"
}
}
See {baseDir}/schemas/output.json for the full schema and enum values.
Confidence Gating
Evidence thresholds
A finding requires at least 2 independent signals to be marked confirmed. With 1 signal, mark likely. With 0 strong signals (name-pattern match only), mark needs_review.
Signals include: name pattern match, type hint match, explicit annotation, IR evidence, ASM evidence, MCP cross-reference, CFG evidence, PoC validation.
PoC validation as evidence signal
Every finding is validated against a bespoke PoC. After compilation and execution, each PoC is also verified to ensure it actually tests the claimed vulnerability. The combined result is an evidence signal:
| PoC Result |
Verified |
Impact |
| Exit 0 (exploitable) |
Yes |
Strong signal — can upgrade likely to confirmed |
| Exit 1 (not exploitable) |
Yes |
Downgrade severity to low (informational); retain in report |
| Exit 0 or 1 |
No (user accepted) |
Weaker signal — note verification failure in evidence |
| Exit 0 or 1 |
No (user rejected) |
No confidence change; annotate as rejected |
| Compile failure / no PoC |
— |
No confidence change; annotate in evidence |
MCP unavailability downgrade
When mcp_mode=prefer and MCP is unavailable, downgrade the following unless independent IR/CFG/ASM evidence is strong (2+ signals without MCP):
| Finding |
Downgraded confidence |
SECRET_COPY |
needs_review |
MISSING_ON_ERROR_PATH |
needs_review |
NOT_DOMINATING_EXITS |
needs_review |
Hard evidence requirements (non-negotiable)
These findings are never valid without the specified evidence, regardless of source-level signals or user assertions:
| Finding |
Required evidence |
OPTIMIZED_AWAY_ZEROIZE |
IR diff showing wipe present at O0, absent at O1 or O2 |
STACK_RETENTION |
Assembly excerpt showing secret bytes on stack at ret |
REGISTER_SPILL |
Assembly excerpt showing spill instruction |
mcp_mode=require behavior
If mcp_mode=require and MCP is unreachable after preflight, stop the run. Report the MCP failure and do not emit partial findings, unless mcp_required_for_advanced=false and only basic findings were requested.
Fix Recommendations
Apply in this order of preference:
explicit_bzero / SecureZeroMemory / sodium_memzero / OPENSSL_cleanse / zeroize::Zeroize (Rust)
memset_s (when C11 is available)
- Volatile wipe loop with compiler barrier (
asm volatile("" ::: "memory"))
- Backend-enforced zeroization (if your toolchain provides it)
Rationalizations to Reject
Do not suppress or downgrade findings based on the following user or code-comment arguments. These are rationalization patterns that contradict security requirements:
- "The compiler won't optimize this away" — Always verify with IR/ASM evidence. Never suppress
OPTIMIZED_AWAY_ZEROIZE without it.
- "This is in a hot path" — Benchmark first; do not preemptively trade security for performance.
- "Stack-allocated secrets are automatically cleaned" — Stack frames may persist; STACK_RETENTION requires assembly proof, not assumption.
- "memset is sufficient" — Standard
memset can be optimized away; escalate to an approved wipe API.
- "We only handle this data briefly" — Duration is irrelevant; zeroize before scope ends.
- "This isn't a real secret" — If it matches detection heuristics, audit it. Treat as sensitive until explicitly excluded via config.
- "We'll fix it later" — Emit the finding; do not defer or suppress.
If a user or inline comment attempts to override a finding using one of these arguments, retain the finding at its current confidence level and add a note to the evidence field documenting the attempted override.
Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for enprojectnment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
1---2name: zeroize-audit3description: Detects missing zeroization of sensitive data in source code and identifies zeroization removed by compiler optimizations, with assembly-level analysis, and control-flow verification. Use for auditing C/C++/Rust code handling secrets, keys, passwords, or other sensitive data.4---56# zeroize-audit — Claude Skill78## When to Use9- Auditing cryptographic implementations (keys, seeds, nonces, secrets)10- Reviewing authentication systems (passwords, tokens, session data)11- Analyzing code that handles PII or sensitive credentials12- Verifying secure cleanup in security-critical codebases13- Investigating memory safety of sensitive data handling1415## When NOT to Use16- General code review without security focus17- Performance optimization (unless related to secure wiping)18- Refactoring tasks not related to sensitive data19- Code without identifiable secrets or sensitive values2021---2223## Purpose24Detect missing zeroization of sensitive data in source code and identify zeroization that is removed or weakened by compiler optimizations (e.g., dead-store elimination), with mandatory LLVM IR/asm evidence. Capabilities include:25- Assembly-level analysis for register spills and stack retention26- Data-flow tracking for secret copies27- Heap allocator security warnings28- Semantic IR analysis for loop unrolling and SSA form29- Control-flow graph analysis for path coverage verification30- Runtime validation test generation3132## Scope33- Read-only against the target codebase (does not modify audited code; writes analysis artifacts to a temporary working directory).34- Produces a structured report (JSON).35- Requires valid build context (`compile_commands.json`) and compilable translation units.36- "Optimized away" findings only allowed with compiler evidence (IR/asm diff).3738---3940## Inputs4142See `{baseDir}/schemas/input.json` for the full schema. Key fields:4344| Field | Required | Default | Description |45|---|---|---|---|46| `path` | yes | — | Repo root |47| `compile_db` | no | `null` | Path to `compile_commands.json` for C/C++ analysis. Required if `cargo_manifest` is not set. |48| `cargo_manifest` | no | `null` | Path to `Cargo.toml` for Rust crate analysis. Required if `compile_db` is not set. |49| `config` | no | — | YAML defining heuristics and approved wipes |50| `opt_levels` | no | `["O0","O1","O2"]` | Optimization levels for IR comparison. O1 is the diagnostic level: if a wipe disappears at O1 it is simple DSE; O2 catches more aggressive eliminations. |51| `languages` | no | `["c","cpp","rust"]` | Languages to analyze |52| `max_tus` | no | — | Limit on translation units processed from compile DB |53| `mcp_mode` | no | `prefer` | `off`, `prefer`, or `require` — controls Serena MCP usage |54| `mcp_required_for_advanced` | no | `true` | Downgrade `SECRET_COPY`, `MISSING_ON_ERROR_PATH`, and `NOT_DOMINATING_EXITS` to `needs_review` when MCP is unavailable |55| `mcp_timeout_ms` | no | — | Timeout budget for MCP semantic queries |56| `poc_categories` | no | all 11 exploitable | Finding categories for which to generate PoCs. C/C++ findings: all 11 categories supported. Rust findings: only `MISSING_SOURCE_ZEROIZE`, `SECRET_COPY`, and `PARTIAL_WIPE` are supported; other Rust categories are marked `poc_supported=false`. |57| `poc_output_dir` | no | `generated_pocs/` | Output directory for generated PoCs |58| `enable_asm` | no | `true` | Enable assembly emission and analysis (Step 8); produces `STACK_RETENTION`, `REGISTER_SPILL`. Auto-disabled if `emit_asm.sh` is missing. |59| `enable_semantic_ir` | no | `false` | Enable semantic LLVM IR analysis (Step 9); produces `LOOP_UNROLLED_INCOMPLETE` |60| `enable_cfg` | no | `false` | Enable control-flow graph analysis (Step 10); produces `MISSING_ON_ERROR_PATH`, `NOT_DOMINATING_EXITS` |61| `enable_runtime_tests` | no | `false` | Enable runtime test harness generation (Step 11) |6263---6465## Prerequisites6667Before running, verify the following. Each has a defined failure mode.6869**C/C++ prerequisites:**7071| Prerequisite | Failure mode if missing |72|---|---|73| `compile_commands.json` at `compile_db` path | Fail fast — do not proceed |74| `clang` on PATH | Fail fast — IR/ASM analysis impossible |75| `uvx` on PATH (for Serena) | If `mcp_mode=require`: fail. If `mcp_mode=prefer`: continue without MCP; downgrade affected findings per Confidence Gating rules. |76| `{baseDir}/tools/extract_compile_flags.py` | Fail fast — cannot extract per-TU flags |77| `{baseDir}/tools/emit_ir.sh` | Fail fast — IR analysis impossible |78| `{baseDir}/tools/emit_asm.sh` | Warn and skip assembly findings (STACK_RETENTION, REGISTER_SPILL) |79| `{baseDir}/tools/mcp/check_mcp.sh` | Warn and treat as MCP unavailable |80| `{baseDir}/tools/mcp/normalize_mcp_evidence.py` | Warn and use raw MCP output |8182**Rust prerequisites:**8384| Prerequisite | Failure mode if missing |85|---|---|86| `Cargo.toml` at `cargo_manifest` path | Fail fast — do not proceed |87| `cargo check` passes | Fail fast — crate must be buildable |88| `cargo +nightly` on PATH | Fail fast — nightly required for MIR and LLVM IR emission |89| `uv` on PATH | Fail fast — required to run Python analysis scripts |90| `{baseDir}/tools/validate_rust_toolchain.sh` | Warn — run preflight manually. Checks all tools, scripts, nightly, and optionally `cargo check`. Use `--json` for machine-readable output, `--manifest` to also validate the crate builds. |91| `{baseDir}/tools/emit_rust_mir.sh` | Fail fast — MIR analysis impossible (`--opt`, `--crate`, `--bin/--lib` supported; `--out` can be file or directory) |92| `{baseDir}/tools/emit_rust_ir.sh` | Fail fast — LLVM IR analysis impossible (`--opt` required; `--crate`, `--bin/--lib` supported; `--out` must be `.ll`) |93| `{baseDir}/tools/emit_rust_asm.sh` | Warn and skip assembly findings (`STACK_RETENTION`, `REGISTER_SPILL`). Supports `--opt`, `--crate`, `--bin/--lib`, `--target`, `--intel-syntax`; `--out` can be `.s` file or directory. |94| `{baseDir}/tools/diff_rust_mir.sh` | Warn and skip MIR-level optimization comparison. Accepts 2+ MIR files, normalizes, diffs pairwise, and reports first opt level where zeroize/drop-glue patterns disappear. |95| `{baseDir}/tools/scripts/semantic_audit.py` | Warn and skip semantic source analysis |96| `{baseDir}/tools/scripts/find_dangerous_apis.py` | Warn and skip dangerous API scan |97| `{baseDir}/tools/scripts/check_mir_patterns.py` | Warn and skip MIR analysis |98| `{baseDir}/tools/scripts/check_llvm_patterns.py` | Warn and skip LLVM IR analysis |99| `{baseDir}/tools/scripts/check_rust_asm.py` | Warn and skip Rust assembly analysis (`STACK_RETENTION`, `REGISTER_SPILL`, drop-glue checks). Dispatches to `check_rust_asm_x86.py` (production) or `check_rust_asm_aarch64.py` (**EXPERIMENTAL** — AArch64 findings require manual verification). |100| `{baseDir}/tools/scripts/check_rust_asm_x86.py` | Required by `check_rust_asm.py` for x86-64 analysis; warn and skip if missing |101| `{baseDir}/tools/scripts/check_rust_asm_aarch64.py` | Required by `check_rust_asm.py` for AArch64 analysis (**EXPERIMENTAL**); warn and skip if missing |102103**Common prerequisite:**104105| Prerequisite | Failure mode if missing |106|---|---|107| `{baseDir}/tools/generate_poc.py` | Fail fast — PoC generation is mandatory |108109---110111## Approved Wipe APIs112113The following are recognized as valid zeroization. Configure additional entries in `{baseDir}/configs/`.114115**C/C++**116- `explicit_bzero`117- `memset_s`118- `SecureZeroMemory`119- `OPENSSL_cleanse`120- `sodium_memzero`121- Volatile wipe loops (pattern-based; see `volatile_wipe_patterns` in `{baseDir}/configs/default.yaml`)122- In IR: `llvm.memset` with volatile flag, volatile stores, or non-elidable wipe call123124**Rust**125- `zeroize::Zeroize` trait (`zeroize()` method)126- `Zeroizing<T>` wrapper (drop-based)127- `ZeroizeOnDrop` derive macro128129---130131## Finding Capabilities132133Findings are grouped by required evidence. Only attempt findings for which the required tooling is available.134135| Finding ID | Description | Requires | PoC Support |136|---|---|---|---|137| `MISSING_SOURCE_ZEROIZE` | No zeroization found in source | Source only | Yes (C/C++ + Rust) |138| `PARTIAL_WIPE` | Incorrect size or incomplete wipe | Source only | Yes (C/C++ + Rust) |139| `NOT_ON_ALL_PATHS` | Zeroization missing on some control-flow paths (heuristic) | Source only | Yes (C/C++ only) |140| `SECRET_COPY` | Sensitive data copied without zeroization tracking | Source + MCP preferred | Yes (C/C++ + Rust) |141| `INSECURE_HEAP_ALLOC` | Secret uses insecure allocator (malloc vs. secure_malloc) | Source only | Yes (C/C++ only) |142| `OPTIMIZED_AWAY_ZEROIZE` | Compiler removed zeroization | IR diff required (never source-only) | Yes |143| `STACK_RETENTION` | Stack frame may retain secrets after return | Assembly required (C/C++); LLVM IR `alloca`+`lifetime.end` evidence (Rust); assembly corroboration upgrades to `confirmed` | Yes (C/C++ only) |144| `REGISTER_SPILL` | Secrets spilled from registers to stack | Assembly required (C/C++); LLVM IR `load`+call-site evidence (Rust); assembly corroboration upgrades to `confirmed` | Yes (C/C++ only) |145| `MISSING_ON_ERROR_PATH` | Error-handling paths lack cleanup | CFG or MCP required | Yes |146| `NOT_DOMINATING_EXITS` | Wipe doesn't dominate all exits | CFG or MCP required | Yes |147| `LOOP_UNROLLED_INCOMPLETE` | Unrolled loop wipe is incomplete | Semantic IR required | Yes |148149---150151## Agent Architecture152153The analysis pipeline uses 11 agents across 8 phases, invoked by the orchestrator (`{baseDir}/prompts/task.md`) via `Task`. Agents write persistent finding files to a shared working directory (`/tmp/zeroize-audit-{run_id}/`), enabling parallel execution and protecting against context pressure.154155| Agent | Phase | Purpose | Output Directory |156|---|---|---|---|157| `0-preflight` | Phase 0 | Preflight checks (tools, toolchain, compile DB, crate build), config merge, workdir creation, TU enumeration | `{workdir}/` |158| `1-mcp-resolver` | Phase 1, Wave 1 (C/C++ only) | Resolve symbols, types, and cross-file references via Serena MCP | `mcp-evidence/` |159| `2-source-analyzer` | Phase 1, Wave 2a (C/C++ only) | Identify sensitive objects, detect wipes, validate correctness, data-flow/heap | `source-analysis/` |160| `2b-rust-source-analyzer` | Phase 1, Wave 2b (Rust only, parallel with 2a) | Rustdoc JSON trait-aware analysis + dangerous API grep | `source-analysis/` |161| `3-tu-compiler-analyzer` | Phase 2, Wave 3 (C/C++ only, N parallel) | Per-TU IR diff, assembly, semantic IR, CFG analysis | `compiler-analysis/{tu_hash}/` |162| `3b-rust-compiler-analyzer` | Phase 2, Wave 3R (Rust only, single agent) | Crate-level MIR, LLVM IR, and assembly analysis | `rust-compiler-analysis/` |163| `4-report-assembler` | Phase 3 (interim) + Phase 6 (final) | Collect findings from all agents, apply confidence gates; merge PoC results and produce final report | `report/` |164| `5-poc-generator` | Phase 4 | Craft bespoke proof-of-concept programs (C/C++: all categories; Rust: MISSING_SOURCE_ZEROIZE, SECRET_COPY, PARTIAL_WIPE) | `poc/` |165| `5b-poc-validator` | Phase 5 | Compile and run all PoCs | `poc/` |166| `5c-poc-verifier` | Phase 5 | Verify each PoC proves its claimed finding | `poc/` |167| `6-test-generator` | Phase 7 (optional) | Generate runtime validation test harnesses | `tests/` |168169The orchestrator reads one per-phase workflow file from `{baseDir}/workflows/` at a time, and maintains `orchestrator-state.json` for recovery after context compression. Agents receive configuration by file path (`config_path`), not by value.170171### Execution flow172173```174Phase 0: 0-preflight agent — Preflight + config + create workdir + enumerate TUs175 → writes orchestrator-state.json, merged-config.yaml, preflight.json176Phase 1: Wave 1: 1-mcp-resolver (skip if mcp_mode=off OR language_mode=rust)177 Wave 2a: 2-source-analyzer (C/C++ only; skip if no compile_db) ─┐ parallel178 Wave 2b: 2b-rust-source-analyzer (Rust only; skip if no cargo_manifest) ─┘179Phase 2: Wave 3: 3-tu-compiler-analyzer x N (C/C++ only; parallel per TU)180 Wave 3R: 3b-rust-compiler-analyzer (Rust only; single crate-level agent)181Phase 3: Wave 4: 4-report-assembler (mode=interim → findings.json; reads all agent outputs)182Phase 4: Wave 5: 5-poc-generator (C/C++: all categories; Rust: MISSING_SOURCE_ZEROIZE, SECRET_COPY, PARTIAL_WIPE; other Rust findings: poc_supported=false)183Phase 5: PoC Validation & Verification184 Step 1: 5b-poc-validator agent (compile and run all PoCs)185 Step 2: 5c-poc-verifier agent (verify each PoC proves its claimed finding)186 Step 3: Orchestrator presents verification failures to user via AskUserQuestion187 Step 4: Orchestrator merges all results into poc_final_results.json188Phase 6: Wave 6: 4-report-assembler (mode=final → merge PoC results, final-report.md)189Phase 7: Wave 7: 6-test-generator (optional)190Phase 8: Orchestrator — Return final-report.md191```192193## Cross-Reference Convention194195IDs are namespaced per agent to prevent collisions during parallel execution:196197| Entity | Pattern | Assigned By |198|---|---|---|199| Sensitive object (C/C++) | `SO-0001`–`SO-4999` | `2-source-analyzer` |200| Sensitive object (Rust) | `SO-5000`–`SO-9999` (Rust namespace) | `2b-rust-source-analyzer` |201| Source finding (C/C++) | `F-SRC-NNNN` | `2-source-analyzer` |202| Source finding (Rust) | `F-RUST-SRC-NNNN` | `2b-rust-source-analyzer` |203| IR finding (C/C++) | `F-IR-{tu_hash}-NNNN` | `3-tu-compiler-analyzer` |204| ASM finding (C/C++) | `F-ASM-{tu_hash}-NNNN` | `3-tu-compiler-analyzer` |205| CFG finding | `F-CFG-{tu_hash}-NNNN` | `3-tu-compiler-analyzer` |206| Semantic IR finding | `F-SIR-{tu_hash}-NNNN` | `3-tu-compiler-analyzer` |207| Rust MIR finding | `F-RUST-MIR-NNNN` | `3b-rust-compiler-analyzer` |208| Rust LLVM IR finding | `F-RUST-IR-NNNN` | `3b-rust-compiler-analyzer` |209| Rust assembly finding | `F-RUST-ASM-NNNN` | `3b-rust-compiler-analyzer` |210| Translation unit | `TU-{hash}` | Orchestrator |211| Final finding | `ZA-NNNN` | `4-report-assembler` |212213Every finding JSON object includes `related_objects`, `related_findings`, and `evidence_files` fields for cross-referencing between agents.214215---216217## Detection Strategy218219Analysis runs in two phases. For complete step-by-step guidance, see `{baseDir}/references/detection-strategy.md`.220221| Phase | Steps | Findings produced | Required tooling |222|---|---|---|---|223| Phase 1 (Source) | 1–6 | `MISSING_SOURCE_ZEROIZE`, `PARTIAL_WIPE`, `NOT_ON_ALL_PATHS`, `SECRET_COPY`, `INSECURE_HEAP_ALLOC` | Source + compile DB |224| Phase 2 (Compiler) | 7–12 | `OPTIMIZED_AWAY_ZEROIZE`, `STACK_RETENTION`*, `REGISTER_SPILL`*, `LOOP_UNROLLED_INCOMPLETE`†, `MISSING_ON_ERROR_PATH`‡, `NOT_DOMINATING_EXITS`‡ | `clang`, IR/ASM tools |225226\* requires `enable_asm=true` (default)227† requires `enable_semantic_ir=true`228‡ requires `enable_cfg=true`229230---231232233## Output Format234235Each run produces two outputs:2362371. **`final-report.md`** — Comprehensive markdown report (primary human-readable output)2382. **`findings.json`** — Structured JSON matching `{baseDir}/schemas/output.json` (for machine consumption and downstream tools)239240### Markdown Report Structure241242The markdown report (`final-report.md`) contains these sections:243244- **Header**: Run metadata (run_id, timestamp, repo, compile_db, config summary)245- **Executive Summary**: Finding counts by severity, confidence, and category246- **Sensitive Objects Inventory**: Table of all identified objects with IDs, types, locations247- **Findings**: Grouped by severity then confidence. Each finding includes location, object, all evidence (source/IR/ASM/CFG), compiler evidence details, and recommended fix248- **Superseded Findings**: Source findings replaced by CFG-backed findings249- **Confidence Gate Summary**: Downgrades applied and overrides rejected250- **Analysis Coverage**: TUs analyzed, agent success/failure, features enabled251- **Appendix: Evidence Files**: Mapping of finding IDs to evidence file paths252253### Structured JSON254255The `findings.json` file follows the schema in `{baseDir}/schemas/output.json`. Each `Finding` object:256257```json258{259 "id": "ZA-0001",260 "category": "OPTIMIZED_AWAY_ZEROIZE",261 "severity": "high",262 "confidence": "confirmed",263 "language": "c",264 "file": "src/crypto.c",265 "line": 42,266 "symbol": "key_buf",267 "evidence": "store volatile i8 0 count: O0=32, O2=0 — wipe eliminated by DSE",268 "compiler_evidence": {269 "opt_levels": ["O0", "O2"],270 "o0": "32 volatile stores targeting key_buf",271 "o2": "0 volatile stores (all eliminated)",272 "diff_summary": "All volatile wipe stores removed at O2 — classic DSE pattern"273 },274 "suggested_fix": "Replace memset with explicit_bzero or add compiler_fence(SeqCst) after the wipe",275 "poc": {276 "file": "generated_pocs/ZA-0001.c",277 "makefile_target": "ZA-0001",278 "compile_opt": "-O2",279 "requires_manual_adjustment": false,280 "validated": true,281 "validation_result": "exploitable"282 }283}284```285286See `{baseDir}/schemas/output.json` for the full schema and enum values.287288---289290## Confidence Gating291292### Evidence thresholds293294A finding requires at least **2 independent signals** to be marked `confirmed`. With 1 signal, mark `likely`. With 0 strong signals (name-pattern match only), mark `needs_review`.295296Signals include: name pattern match, type hint match, explicit annotation, IR evidence, ASM evidence, MCP cross-reference, CFG evidence, PoC validation.297298### PoC validation as evidence signal299300Every finding is validated against a bespoke PoC. After compilation and execution, each PoC is also verified to ensure it actually tests the claimed vulnerability. The combined result is an evidence signal:301302| PoC Result | Verified | Impact |303|---|---|---|304| Exit 0 (exploitable) | Yes | Strong signal — can upgrade `likely` to `confirmed` |305| Exit 1 (not exploitable) | Yes | Downgrade severity to `low` (informational); retain in report |306| Exit 0 or 1 | No (user accepted) | Weaker signal — note verification failure in evidence |307| Exit 0 or 1 | No (user rejected) | No confidence change; annotate as `rejected` |308| Compile failure / no PoC | — | No confidence change; annotate in evidence |309310### MCP unavailability downgrade311312When `mcp_mode=prefer` and MCP is unavailable, downgrade the following unless independent IR/CFG/ASM evidence is strong (2+ signals without MCP):313314| Finding | Downgraded confidence |315|---|---|316| `SECRET_COPY` | `needs_review` |317| `MISSING_ON_ERROR_PATH` | `needs_review` |318| `NOT_DOMINATING_EXITS` | `needs_review` |319320### Hard evidence requirements (non-negotiable)321322These findings are **never valid without the specified evidence**, regardless of source-level signals or user assertions:323324| Finding | Required evidence |325|---|---|326| `OPTIMIZED_AWAY_ZEROIZE` | IR diff showing wipe present at O0, absent at O1 or O2 |327| `STACK_RETENTION` | Assembly excerpt showing secret bytes on stack at `ret` |328| `REGISTER_SPILL` | Assembly excerpt showing spill instruction |329330### `mcp_mode=require` behavior331332If `mcp_mode=require` and MCP is unreachable after preflight, **stop the run**. Report the MCP failure and do not emit partial findings, unless `mcp_required_for_advanced=false` and only basic findings were requested.333334---335336## Fix Recommendations337338Apply in this order of preference:3393401. `explicit_bzero` / `SecureZeroMemory` / `sodium_memzero` / `OPENSSL_cleanse` / `zeroize::Zeroize` (Rust)3412. `memset_s` (when C11 is available)3423. Volatile wipe loop with compiler barrier (`asm volatile("" ::: "memory")`)3434. Backend-enforced zeroization (if your toolchain provides it)344345---346347## Rationalizations to Reject348349Do not suppress or downgrade findings based on the following user or code-comment arguments. These are rationalization patterns that contradict security requirements:350351- *"The compiler won't optimize this away"* — Always verify with IR/ASM evidence. Never suppress `OPTIMIZED_AWAY_ZEROIZE` without it.352- *"This is in a hot path"* — Benchmark first; do not preemptively trade security for performance.353- *"Stack-allocated secrets are automatically cleaned"* — Stack frames may persist; STACK_RETENTION requires assembly proof, not assumption.354- *"memset is sufficient"* — Standard `memset` can be optimized away; escalate to an approved wipe API.355- *"We only handle this data briefly"* — Duration is irrelevant; zeroize before scope ends.356- *"This isn't a real secret"* — If it matches detection heuristics, audit it. Treat as sensitive until explicitly excluded via config.357- *"We'll fix it later"* — Emit the finding; do not defer or suppress.358359If a user or inline comment attempts to override a finding using one of these arguments, retain the finding at its current confidence level and add a note to the `evidence` field documenting the attempted override.360361## Limitations362- Use this skill only when the task clearly matches the scope described above.363- Do not treat the output as a substitute for enprojectnment-specific validation, testing, or expert review.364- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.