- name
- rust-panic-audit
- description
- Standalone Rust audit for panic and brutal-exit hazards, including panic macros, production `.unwrap()` / `.expect()`, process exits, overflow, and indexing. Use for a dedicated panic check; `/rust-review-all` uses KMS Caveman and does not invoke this skill again.
# Rust Panic & Brutal-Exit Audit
Detect every construct in Rust code that can cause a panic, abort, or brutal process exit
outside of `#[cfg(test)]` blocks. Produces a report in `./review-YYYY-MM-DD/rust-panic-audit.md`.
**Usage**: `/rust-panic-audit` (diff scope) or `/rust-panic-audit crate/server/src/`
---
## Step 0 — Create output directory
```bash
mkdir -p ./review-$(date +%F)
REPORT="./review-$(date +%F)/rust-panic-audit.md"
echo "# Rust Panic & Brutal-Exit Audit" > "${REPORT}"
echo "Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "${REPORT}"
echo "" >> "${REPORT}"
```
---
## Step 1 — Determine scope
If a path was provided, scan only that path.
Otherwise default to recently changed Rust files:
```bash
SCOPE=$(git diff --name-only origin/develop...HEAD 2>/dev/null || git diff --name-only HEAD~1)
# Fall back to full codebase if diff is empty
[ -z "${SCOPE}" ] && SCOPE="crate/"
```
---
## Step 2 — Mechanical scan (ripgrep passes)
Run each grep pass; collect file + line number + snippet.
### Pass A — Direct panic macros
```bash
rg -n '(^|[^/])\bpanic!\s*(' --type rust ${SCOPE}
```
### Pass B — Placeholder macros (always panic at runtime)
```bash
rg -n '\b(todo!|unimplemented!)\s*(' --type rust ${SCOPE}
```
### Pass C — `.unwrap()` in production code
```bash
rg -n '\.unwrap\(\)' --type rust ${SCOPE}
```
Filter out lines inside `#[cfg(test)]` modules (manual review for context).
### Pass D — `.expect(...)` in production code
```bash
rg -n '\.expect\(' --type rust ${SCOPE}
```
### Pass E — `unreachable!` outside debug-only guards
```bash
rg -n '\bunreachable!\s*(' --type rust ${SCOPE}
```
Flag each hit unless it is inside `#[cfg(debug_assertions)]` or `#[cfg(test)]`.
### Pass F — Process exit / abort
```bash
rg -n '\bstd::process::(exit|abort)\b|\bprocess::(exit|abort)\b' --type rust ${SCOPE}
```
### Pass G — Integer arithmetic without overflow protection on untrusted data
```bash
# Look for direct + / * on integer types in path-handling, length, or index contexts
rg -n '\b(len|index|offset|size|count)\s*[+\*]\s' --type rust ${SCOPE}
```
### Pass H — Direct slice indexing on non-literal indices
```bash
rg -n '\[\s*[a-z_][a-zA-Z0-9_]*\s*\]' --type rust ${SCOPE} \
| grep -v 'cfg\|macro\|derive\|//\|test'
```
### Optional: shrink wide-scope hit lists
When `omp-jev-tools` is installed (OMP only — not available in VS Code
Copilot Chat, Copilot CLI, or Copilot Cloud Agent) and Pass C, G, or H
returns more hits than fit a manual pass, rerank each hit with `jev_rerank`:
`query` is the pass's own heading text (e.g. "Direct slice indexing on
non-literal indices"), one candidate per hit (`id` = `path:line`, `text` =
the matched line plus two lines of context), keep only the top half by
relevance before Step 3. Never apply this to Pass A, B, D, E, or F — every
hit from those passes must still reach Step 3. Without the tool, review
every hit from every pass.
---
## Step 3 — Classify findings
For each finding, assign a severity:
| Severity | Criteria |
|----------|----------|
| **CRITICAL** | `panic!`, `todo!`, `unimplemented!`, `process::exit`, or `process::abort` in production code outside the documented `fn main()` exception |
| **HIGH** | Any `.unwrap()` or `.expect()` confirmed in production code; this repository forbids both |
| **MEDIUM** | `unreachable!` outside debug guards, unchecked slice indexing on untrusted input |
| **INFO** | `.expect()` in test code (allowed but must have a meaningful message) |
---
## Step 4 — Filter allowed occurrences
The following are **allowed** and should be marked `[OK]`:
- Any line inside a `#[cfg(test)]` module.
- `unreachable!()` inside a `#[cfg(debug_assertions)]` block with a `// PANIC:` comment.
- `process::exit(0)` inside `fn main()` only, with a `// PANIC:` comment.
---
## Step 5b — Scan for all lint suppressions added in this diff
Covers `#[allow(...)]` and `#[expect(...)]` for **every lint**, not just Clippy.
```bash
# Single pass: catches all new #[allow(...)] and #[expect(...)] on any lint
git diff origin/develop...HEAD -- '*.rs' \
| grep '^+' \
| grep -v '^+++' \
| grep -E '#\[(allow|expect)\(' \
| grep -v '//.*tracked\|//.*issue\|//.*#[0-9]\|//.*false.positive' \
| tee /tmp/lint-suppressions.txt
cat /tmp/lint-suppressions.txt
```
Classify each hit by severity:
| Pattern found | Severity |
|---------------|----------|
| `#[allow(warnings)]` | **CRITICAL** — unconditionally forbidden, no exception |
| `#[allow(unused_imports)]` | **CRITICAL** — remove the import |
| `#[allow(dead_code)]` / `#[expect(dead_code)]` | **CRITICAL** — remove the dead item |
| `#[allow(unused_*)]` / `#[expect(unused_*)]` | **HIGH** — remove or rename to `_` |
| `#[allow(deprecated)]` / `#[expect(deprecated)]` | **HIGH** — migrate off the deprecated API |
| `#[allow(clippy::*)]` / `#[expect(clippy::*)]` without justification | **HIGH** — fix the lint |
| `#[allow(clippy::*)]` / `#[expect(clippy::*)]` with `// tracked in #N` | **LOW** (pre-existing, logged only) |
Any CRITICAL or HIGH match without a `// tracked in #<N>` or `// false positive:` justification comment on the same line is a **blocker** — reject and fix.
Append all findings to `./review-YYYY-MM-DD/rust-panic-audit.md` under a `## Lint Suppressions` heading.
---
## Step 6 — Generate patches for CRITICAL and HIGH findings
For each CRITICAL/HIGH finding, produce a concrete patch:
**`.unwrap()` on `Result`:**
```rust
// ❌ Before
let key = kms.get_key(id).await.unwrap();
// ✅ After
let key = kms.get_key(id).await
.map_err(|e| KmsError::InvalidRequest(format!("Failed to get key {id}: {e}")))?;
```
**`.expect()` on `Option`:**
```rust
// ❌ Before
let val = map.get(&key).expect("key must exist");
// ✅ After
let val = map.get(&key)
.ok_or_else(|| KmsError::ItemNotFound(key.clone()))?;
```
**`panic!` with static message:**
```rust
// ❌ Before
panic!("unexpected state");
// ✅ After
return Err(KmsError::InvalidRequest("unexpected state".to_owned()));
```
---
## Step 6 — Write report
Append to `./review-YYYY-MM-DD/rust-panic-audit.md`:
```markdown
## Summary
| Severity | Count |
|----------|-------|
| CRITICAL | N |
| HIGH | N |
| MEDIUM | N |
| LOW | N |
| INFO | N |
| OK | N |
## Findings
### [CRITICAL] panic! in request handler
- **File**: `crate/server/src/core/operations/get.rs:42`
- **Code**: `panic!("key not found")`
- **Impact**: Aborts the Actix-web worker thread; potential DoS via crafted request.
- **Fix**: Replace with `return Err(KmsError::ItemNotFound(...))`.
...
```
---
## Step 7 — Summary verdict
Print to chat:
```
Rust Panic Audit complete.
CRITICAL: N HIGH: N MEDIUM: N LOW: N
Report: ./review-YYYY-MM-DD/rust-panic-audit.md
```
If CRITICAL or HIGH count > 0, recommend running `/rust-review-all` for a full quality gate
before the PR is submitted.
View on GitHub