- Case ID
- #6993
- Severity
- CRIT
- Decision
- BLOCK
- Agent
- Claude Code
- Category
- Shell Safety
Correction note (2026-10-13). An earlier version of this article said a Zig/Rust AST engine evaluates the rule and that blocked actions are written to a file named
~/.ssg/audit.log. The rule engine is written in Go. The local audit log is theaudit_logtable in~/.sigmashake/rules.db, andssg audit searchreads it. The per-call timings and latency figures that appeared in this article had no benchmark behind them and were removed. Sample output in this article is illustrative, not a recorded run. The only published measurement is p50 0.39 ms and p99 1.91 ms (sigmashake-bench daemon-socket run, 2026-06-10, ssg 0.29.156, n=500). Sub-2 ms evaluation is a design target. The local audit log records decisions on a best-effort basis: entries can be dropped if the daemon queue is full, and old rows are deleted at the row caps. It is local, not append-only, and not immutable.ssg evalreads the tool call as JSON on stdin and has no --content, --content-file, --file or positional form.
Describe the task
You're using Claude Code to clean up stale build artifacts in a monorepo. The agent is working inside packages/legacy-api/dist/ — but it miscalculates the working directory and constructs the path as an absolute root reference. Before you can blink, it's about to run:
rm -rf /
This is the classic "rm disaster" — an agent operating in YOLO mode (no human confirmations) with unchecked shell access. SigmaShake's no-rm-rf rule catches it the moment the tool call is evaluated, before execution begins.
Here's what the blocked audit entry looks like:
# illustrative sample output
[BLOCK] bash › rm -rf / — rule: no-rm-rf
Give SSG context
SigmaShake evaluates Claude Code's bash tool calls through the PreToolUse hook before they ever reach your shell.
Required context
A .sigmashake/rules/bash.rules file in your project root. The rule that fires here:
rule no-rm-rf {
enabled true
priority 100
severity error
DENY execution
IF command STARTS_WITH "rm -rf"
MESSAGE "Destructive recursive delete blocked. Use a scoped path."
}
Optional context
Add a custom MESSAGE to tell the agent exactly what to do instead — it reads the error and can course-correct:
rule no-rm-rf-guided {
enabled true
priority 100
severity error
DENY execution
IF command STARTS_WITH "rm -rf"
MESSAGE "Blocked: rm -rf is not allowed. Target a specific subdirectory like 'dist/' instead."
}
What SSG does
SSG evaluates the incoming tool call payload against your rule file using its Go rule engine. The match is deterministic — no LLM in the loop, no probability. The command either matches the condition or it doesn't.
On a match, SSG returns a DENY decision to Claude Code's hook handler, which surfaces the block as a tool error. Claude Code reads the message and can retry with a safe alternative.
Ask the agent to retry with a scoped path
After the block, Claude Code will see the error message. You can guide it:
The recursive delete was blocked. Please delete only the dist/ directory relative to the current package: rm -rf packages/legacy-api/dist/
The agent reformulates the command, SSG re-evaluates — this time it passes because rm -rf packages/legacy-api/dist/ is scoped and doesn't match STARTS_WITH "rm -rf /".
See the block in your audit dashboard
Blocked actions are recorded on a best-effort basis in the local audit log (the audit_log table in ~/.sigmashake/rules.db). List blocked calls with ssg audit search --decision block --since 24h, or run ssg dashboard to see a live web UI showing blocked commands and the rule that fired.
Customize the severity for your team
Your security team may want rm -rf to trigger human approval (ASK) rather than a hard block, giving developers an escape hatch with oversight:
rule confirm-rm-rf {
enabled true
priority 100
severity warning
ASK execution
IF command STARTS_WITH "rm -rf"
PROMPT "This will recursively delete files. Are you sure?"
MESSAGE "This will recursively delete files. Are you sure?"
}
How your rule syntax shapes enforcement
STARTS_WITH is stricter than CONTAINS — if you use CONTAINS "rm -rf", you'll also catch embedded occurrences like echo "rm -rf" in a script. For shell commands, STARTS_WITH is usually the right choice. Test your rules with echo '{"tool":"Bash","input":{"command":"rm -rf /tmp/test"}}' | ssg eval before shipping.
Check the rule against your environment
The no-rm-rf rule blocks all rm -rf invocations by default. If your build pipeline legitimately uses rm -rf on scoped paths (like CI cleanup scripts), you can add an EXCEPT clause or use a more specific condition. Always validate with ssg lint .sigmashake/rules/ after editing.