Files
trailofbits__skills/plugins/zeroize-audit/agents/2b-rust-source-analyzer.md
Jonathan Hefner debfb29c8e Fix allowed-tools to use spec-compliant space-delimited strings (#139)
* Fix `allowed-tools` to use spec-compliant space-delimited strings

Per the agentskills.io specification, `allowed-tools` must be a single
string of space-delimited patterns, not a YAML list. Converted all 23
SKILL.md files from the `- Item` list format to the correct
`"Item1 Item2"` string format. Also updated the frontmatter examples in
CLAUDE.md and the workflow-skill-design skill template to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix remaining allowed-tools format in firebase-apk-scanner and workflow-skill-design docs

- Convert firebase-apk-scanner from comma-separated to space-delimited
- Update anti-patterns.md and tool-assignment-guide.md examples from YAML lists to space-delimited strings
- Remove unnecessary quotes from SKILL.md template placeholder

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Cover commands, new SKILL.md files, and fix template placeholder

Extends the previous spec-compliance fixes:

* Convert command frontmatter (commands/*.md) — per Claude Code
  docs, command files use the same frontmatter as skills, so the
  same space-delimited rule applies.
* Convert three SKILL.md files added since the original PR:
  mutation-testing, trailmark-structural, trailmark-summary.
* Fix the placeholder in the workflow-skill-design template.
  The previous "[minimum tools needed, space-delimited]" was YAML
  flow-sequence syntax, which parses as a list — the opposite of
  what the placeholder claims. Replaced with a concrete-looking
  space-delimited example plus a comment.

Zeroize-audit agent files still use `allowed-tools:` in YAML list
form. They are intentionally excluded: per the project's own docs
(workflow-skill-design references), agents declare tools with
`tools:` (not `allowed-tools:`). Fixing those requires changing
the field name as well as the format and is out of scope for this
PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* zeroize-audit agents: switch allowed-tools to tools

Subagents declare their tool allowlist via `tools:` (comma-separated),
not `allowed-tools:` — see Claude Code's subagent docs and this
repo's own designing-workflow-skills/SKILL.md:47:

> Skills use `allowed-tools:` in frontmatter. Agents use `tools:`
> in frontmatter.

Before this change, the zeroize-audit agents declared their tool list
under `allowed-tools:`, which Claude Code does not read for subagents.
The field was effectively a no-op; the spawned agents had no tool
restriction enforced.

Renames the field on all 11 agents to `tools:` and reformats the YAML
list as comma-separated to match the documented format and existing
agents elsewhere in the repo (e.g. function-analyzer.md,
spec-compliance-checker.md). Tool sets are unchanged.

Behavior change: tools now actually constrain what each spawned agent
can call. The lists are the ones the original author intended.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* skill-improver: convert command allowed-tools to space-delimited

The two command files in plugins/skill-improver/commands/ still used
the JSON flow-array format (`allowed-tools: ["..."]`), which the rest
of this PR converted everywhere else. Convert them to the spec-compliant
space-delimited string form for consistency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Dan Guido <dan@trailofbits.com>
2026-04-28 19:50:30 -04:00

8.5 KiB
Raw Permalink Blame History

name, description, model, tools
name description model tools
2b-rust-source-analyzer Performs source-level zeroization analysis for Rust crates in zeroize-audit. Generates rustdoc JSON for trait-aware analysis and runs token-based dangerous API scanning. Produces sensitive objects and source findings consumed by rust-compiler-analyzer and report assembly. inherit Read, Grep, Glob, Write, Bash

2b-rust-source-analyzer

Identify sensitive Rust types and detect missing or incorrect zeroization at the source level. Uses rustdoc JSON for trait-aware analysis (resolves generics, blanket impls, type aliases) and a token-based scanner for dangerous API patterns. Produces source findings that drive crate-level compiler analysis.

Input

You receive these values from the orchestrator:

Parameter Description
workdir Run working directory (e.g. /tmp/zeroize-audit-{run_id}/)
repo_root Repository root path
cargo_manifest Absolute path to Cargo.toml
rust_crate_root Directory containing Cargo.toml (i.e. dirname(cargo_manifest))
rust_tu_hash Short hash identifying this crate (e.g. a1b2c3d4)
config Merged config object (sensitive patterns, approved wipes)
baseDir Plugin base directory (for tool paths)

Process

Step 1 — Generate Rustdoc JSON

Generate the rustdoc JSON file for the crate. This provides trait implementation data, derive macros, and type information needed for semantic analysis.

cargo +nightly rustdoc \
  --manifest-path <cargo_manifest> \
  --document-private-items -- \
  -Z unstable-options --output-format json

The output is written to <rust_crate_root>/target/doc/<crate_name>.json. Find it with:

find <rust_crate_root>/target/doc -name "*.json" -not -name "search-index*.json" | head -1

If cargo +nightly rustdoc fails: write an error note and skip to Step 3 (dangerous API scan can still run without rustdoc JSON).

Step 2 — Semantic Audit (Rustdoc JSON)

Run the trait-aware semantic auditor:

uv run {baseDir}/tools/scripts/semantic_audit.py \
  --rustdoc <rustdoc_json_path> \
  --cargo-toml <cargo_manifest> \
  --out {workdir}/source-analysis/rust-semantic-findings.json

This detects:

  • #[derive(Copy)] on sensitive types → SECRET_COPY (critical)
  • No Zeroize/ZeroizeOnDrop/Drop → MISSING_SOURCE_ZEROIZE (high)
  • Zeroize without auto-trigger → MISSING_SOURCE_ZEROIZE (high)
  • Partial Drop impl → PARTIAL_WIPE (high)
  • ZeroizeOnDrop with heap fields → PARTIAL_WIPE (medium)
  • Clone on zeroizing type → SECRET_COPY (medium)
  • From/Into returning non-zeroizing type → SECRET_COPY (medium)
  • Source file containing ptr::write_bytes and no compiler_fence(...) call → OPTIMIZED_AWAY_ZEROIZE (medium, needs_review)
  • #[cfg(feature=...)] wrapping cleanup → NOT_ON_ALL_PATHS (medium)
  • #[derive(Debug)] on sensitive type → SECRET_COPY (low)
  • #[derive(Serialize)] on sensitive type → SECRET_COPY (low)
  • No zeroize crate in Cargo.toml → MISSING_SOURCE_ZEROIZE (low)

If the script is missing or fails: write a status-bearing error object to the output file and continue:

{
  "status": "error",
  "error_type": "script_failed",
  "step": "semantic_audit",
  "message": "<stderr or missing-script reason>",
  "findings": []
}

Step 3 — Dangerous API Scan

Run the token/grep-based scanner across all .rs source files:

uv run {baseDir}/tools/scripts/find_dangerous_apis.py \
  --src <rust_crate_root>/src \
  --out {workdir}/source-analysis/rust-dangerous-api-findings.json

This detects:

  • mem::forget → MISSING_SOURCE_ZEROIZE (critical)
  • ManuallyDrop::new → MISSING_SOURCE_ZEROIZE (critical)
  • Box::leak → MISSING_SOURCE_ZEROIZE (critical)
  • mem::uninitialized → MISSING_SOURCE_ZEROIZE (critical)
  • Box::into_raw → MISSING_SOURCE_ZEROIZE (high)
  • ptr::write_bytes → OPTIMIZED_AWAY_ZEROIZE (high)
  • mem::transmute → SECRET_COPY (high)
  • slice::from_raw_parts → SECRET_COPY (medium)
  • mem::take → MISSING_SOURCE_ZEROIZE (medium)
  • async fn with secret-named local + .await → NOT_ON_ALL_PATHS (high)

Findings without sensitive names in ±15 surrounding lines are downgraded to needs_review.

If the script is missing or fails: write a status-bearing error object to the output file and continue:

{
  "status": "error",
  "error_type": "script_failed",
  "step": "dangerous_api_scan",
  "message": "<stderr or missing-script reason>",
  "findings": []
}

Step 4 — Build Sensitive Objects List

From the rustdoc JSON analysis, extract all sensitive types into the shared sensitive-objects.json. Read the existing file first (C/C++ objects may already be present) to determine the next available ID.

ID offset: Use SO-5000 as the starting ID for Rust objects to avoid collisions with C/C++ SO-NNNN IDs (which start at SO-0001).

Each sensitive object entry:

{
  "id": "SO-5001",
  "language": "rust",
  "name": "HmacKey",
  "kind": "struct",
  "file": "src/crypto.rs",
  "line": 12,
  "confidence": "high",
  "heuristic": "sensitive_name_match",
  "has_wipe": false,
  "wipe_api": null,
  "related_findings": []
}

Append Rust entries to {workdir}/source-analysis/sensitive-objects.json. Create the file with [] if it does not exist.

Step 5 — Merge Source Findings

Combine both finding arrays from Steps 2 and 3 into source-findings.json. Assign F-RUST-SRC-NNNN IDs (sequential, starting at 0001).

Each finding must include:

{
  "id": "F-RUST-SRC-0001",
  "language": "rust",
  "category": "SECRET_COPY",
  "severity": "critical",
  "confidence": "likely",
  "file": "src/crypto.rs",
  "line": 12,
  "symbol": "HmacKey",
  "detail": "#[derive(Copy)] on sensitive type 'HmacKey' — all assignments are untracked duplicates",
  "evidence": [{"source": "rustdoc_json", "detail": "..."}],
  "related_objects": ["SO-5001"],
  "related_findings": [],
  "evidence_files": []
}

Append Rust findings to {workdir}/source-analysis/source-findings.json. Create with [] if the file does not exist.

Step 6 — Update TU Map

Add the Rust crate entry to {workdir}/source-analysis/tu-map.json. Only add if at least one sensitive Rust object was found (otherwise no compiler-level analysis is needed).

{ "<cargo_manifest_path>": "<rust_tu_hash>" }

Read the existing tu-map.json first and merge; do not overwrite existing C/C++ entries.

Output

Write all output files to {workdir}/source-analysis/:

File Content
sensitive-objects.json Appended Rust entries with SO-5000+ IDs
source-findings.json Appended Rust findings with F-RUST-SRC-NNNN IDs
tu-map.json Updated with {"<cargo_manifest>": "<rust_tu_hash>"} if sensitive objects found
rust-semantic-findings.json Intermediate output from semantic_audit.py (raw findings array or status-bearing error object)
rust-dangerous-api-findings.json Intermediate output from find_dangerous_apis.py (raw findings array or status-bearing error object)
rust-notes.md Summary: steps executed, finding counts by category, skipped steps and reasons

Finding ID Convention

Entity Pattern Example
Rust sensitive object SO-5000+ SO-5001, SO-5002
Rust source finding F-RUST-SRC-NNNN F-RUST-SRC-0001

Sequential numbering within this agent run. Zero-padded to 4 digits.

Error Handling

  • rustdoc JSON generation fails: Write error to rust-notes.md, skip Step 2, continue with Step 3.
  • semantic_audit.py missing or fails: Write status-bearing error object to rust-semantic-findings.json, continue.
  • find_dangerous_apis.py missing or fails: Write status-bearing error object to rust-dangerous-api-findings.json, continue.
  • Both scripts fail: No Rust source findings. Write status-bearing error notes in both intermediate files and record source-analysis-partial-failure in rust-notes.md.
  • Always write sensitive-objects.json (even if unchanged from C/C++ run) and tu-map.json.
  • If the shared files (sensitive-objects.json, source-findings.json, tu-map.json) are missing, create them from scratch.

Categories Produced

Category Severity Source
MISSING_SOURCE_ZEROIZE medium–critical Steps 2 + 3
SECRET_COPY low–critical Steps 2 + 3
PARTIAL_WIPE medium–high Step 2
NOT_ON_ALL_PATHS medium–high Steps 2 + 3
OPTIMIZED_AWAY_ZEROIZE medium–high Step 2 (flag at source; confirmed at IR in rust-compiler-analyzer)