Skip to main content

zeroize-audit

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.

Source facts

Repository
trailofbits/skills
Last source activity
October 14, 2026 at 19:37
Detected SKILL.md language
English
Stars
7,574
Forks
651

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
50 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
zeroize-audit
description
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.
allowed-tools
Read Grep Glob Bash Write Task AskUserQuestion mcp__serena__activate_project mcp__serena__find_symbol mcp__serena__find_referencing_symbols mcp__serena__get_symbols_overview
# zeroize-audit ## 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 --- ## Execution Contract Use the agent environment's available tools for file access, shell commands, worker execution, and user input. The `allowed-tools` entry and role-file frontmatter are legacy host metadata; they do not define another environment's tool names or grant permissions there. - **Resolve resources.** Set `baseDir` to the absolute directory containing this `SKILL.md`. Confirm the linked [input schema](schemas/input.json) and [orchestrator prompt](prompts/task.md) exist there. `{baseDir}`, `{workdir}`, and `{{input}}` are notation for values the orchestrator must substitute, not environment variables or automatic host expansion. Resolve relative Markdown links against the directory of the file containing them; resolve `{baseDir}/…` paths against the skill directory. Pass resolved paths to workers and shell commands. - **Launch a role.** Read the role prompt linked in the Agent Architecture table. When the environment has a registered worker for the role, use its `zeroize-audit:<role-name>` identifier (for example, `zeroize-audit:0-preflight`); otherwise create an independent worker with the Markdown body of that prompt, the current phase's instructions, and its parameter values. Always include the resolved `baseDir` and, once created, `workdir`, even when a phase table does not repeat them. Resolve resource references using the rules above before delegation. Worker file/search/shell actions use the environment's actual tool schemas and permissions. The role's name identifies its instructions, not a required dispatch API or model. - **Wait for results.** Retain each worker's handle and wait through the environment's completion mechanism. A launch acknowledgement is not completion. Read the phase's required artifacts before updating its completion state or starting a dependent phase. On worker failure or unavailable dispatch, apply only that phase's documented fallback; otherwise record the failure in the existing run state and report the affected phase as blocked. - **Concurrency.** Respect available concurrency and phase barriers. Run workers sequentially when parallel execution is unavailable. Per-TU compiler workers write to separate directories and may run concurrently. - **Collect decisions.** Use the environment's user-input facility or ask in the conversation. When a phase requires an answer, wait for it; do not substitute a default or elapsed time for the user's decision. Preserve the validation and verification outcomes when recording any accepted or rejected PoC result. ## How to Run On a request like "audit this crate for secrets left in memory" or "check that this C library actually wipes its keys": 1. **Collect inputs.** Map the request onto the Inputs table below (full schema: [schemas/input.json](schemas/input.json)). `path` is required, plus at least one of `compile_db` (C/C++) or `cargo_manifest` (Rust); if neither is given or derivable from the repo, ask the user, because preflight stops the run without one. Leave all other fields at their defaults unless the user says otherwise. 2. **Read the [orchestrator prompt](prompts/task.md)**, substituting the collected inputs for its `{{placeholder}}` values. You act as the orchestrator it describes: it defines state recovery, the phase loop, early termination, and error handling. Read the [shared instructions](prompts/system.md) alongside it for the shared working-directory layout and the agent error protocol every phase depends on. 3. **Execute its phase loop.** Run Phases 0-7 sequentially. Before each phase, read that phase's workflow file from `{baseDir}/workflows/phase-{N}-{name}.md` and follow its Preconditions, Instructions, State Update, and Error Handling sections. Each workflow specifies the role prompt and parameters to use under the Execution Contract above. Honor the per-phase skip conditions and the early-termination rules in task.md. 4. **Return the report** (Phase 8, inline): read `{workdir}/report/final-report.md` and return its contents as the skill output. To resume an interrupted run: if a `workdir` is known from prior context, read `{workdir}/orchestrator-state.json` and continue from its `current_phase` instead of starting at Phase 0 (see the Recovery section of task.md). --- ## 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 [schemas/input.json](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 | `50` | 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 | `10000` | 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 worker roles across 8 phases, launched by the [orchestrator](prompts/task.md) using the Execution Contract above. 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](../../agents/0-preflight.md) | Phase 0 | Preflight checks (tools, toolchain, compile DB, crate build), config merge, workdir creation, TU enumeration | `{workdir}/` | | [1-mcp-resolver](../../agents/1-mcp-resolver.md) | Phase 1, Wave 1 (C/C++ only) | Resolve symbols, types, and cross-file references via Serena MCP | `mcp-evidence/` | | [2-source-analyzer](../../agents/2-source-analyzer.md) | Phase 1, Wave 2a (C/C++ only) | Identify sensitive objects, detect wipes, validate correctness, data-flow/heap | `source-analysis/` | | [2b-rust-source-analyzer](../../agents/2b-rust-source-analyzer.md) | Phase 1, Wave 2b (Rust only, parallel with 2a) | Rustdoc JSON trait-aware analysis + dangerous API grep | `source-analysis/` | | [3-tu-compiler-analyzer](../../agents/3-tu-compiler-analyzer.md) | 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](../../agents/3b-rust-compiler-analyzer.md) | Phase 2, Wave 3R (Rust only, single agent) | Crate-level MIR, LLVM IR, and assembly analysis | `rust-compiler-analysis/` |
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub