Your AI agent shouldn't be able to rm -rf your life.
destructive-guard is a Claude Code hook that intercepts destructive shell commands — rm -rf, git reset --hard, docker prune, kubectl delete, terraform destroy, DROP TABLE — and turns them into a y/N confirmation. Even in bypassPermissions.
/plugin install destructive-guard@destructive-guard
Pure Python · zero deps · zero config · ~200 ms budget · macOS & Linux
Deletes data outside your working copy. This can't be undone.
The problem
Agents move faster than you can read.
You hand an AI coding agent the keys, walk away for coffee, and it decides the cleanest
fix is git reset --hard — or DROP TABLE, or terraform destroy.
In bypassPermissions mode there's nothing between the model and your data.
destructive-guard puts a human back in the loop — but only for the commands that can actually hurt you.
Coverage
40+ destructive patterns across 10 categories.
Token-level parsing — so perform, transform and terraform plan
never trip it. Sees through sudo, \rm, bash -c "…",
here-strings and xargs.
› Files & disk
irreversible removal
› git
history you can't get back
› Docker
containers & volumes
› Kubernetes & IaC
infra teardown
› Cloud
aws · gcloud · az
› Databases
SQL, Mongo & Redis
How it works
One hook. Three moves.
Install
Add the plugin from the OntoShip marketplace — one command, no config, no daemon.
It registers
The hook binds to Claude Code's PreToolUse event and inspects every Bash command before it runs.
You confirm
Safe commands pass silently. Destructive ones raise a y/N + a banner — even in bypass mode. Reversible ones stay quiet there.
Rules reference
Every rule, in one place.
CRIT = irreversible / reaches outside your repo · ORD = local & reversible. In bypassPermissions only CRIT asks; ORD passes silently.
Files & disk
| Command | Condition | Sev |
|---|---|---|
rm / rm -rf | abs/system path, ~, *, .., or recursive on a non-regenerable dir | CRIT |
rm -rf ./build|dist|node_modules | recursive only on regenerable dirs | ORD |
rm <file> · rmdir · unlink · truncate | single local target | ORD |
shred · srm · dropdb | — | CRIT |
dd of=/dev/… | target is a device / abs path | CRIT |
dd of=<file> | overwrite a file in cwd | ORD |
mkfs · mkfs.* · wipefs · blkdiscard | — | CRIT |
find
| Pattern | Condition | Sev |
|---|---|---|
find … -delete | — | CRIT |
find … -exec rm|unlink|shred|srm|rmdir | also -execdir/-ok/-okdir | CRIT |
git (sees through -C, -c, --git-dir)
| Subcommand | Condition | Sev |
|---|---|---|
git rm | — | ORD |
git clean | without -f | ORD |
git clean -f · reset --hard · push --force/-f | --force-with-lease is allowed | CRIT |
git branch -D · filter-branch · stash clear · update-ref -d | — | CRIT |
Docker / podman (sees through --context, -H)
| Pattern | Condition | Sev |
|---|---|---|
docker rm / rmi · (image|network|container) rm | — | ORD |
docker volume rm · (…) prune | volume/image/system/network/container/builder | CRIT |
docker[-]compose down -v | down without -v is allowed | CRIT |
Kubernetes & IaC
| Pattern | Condition | Sev |
|---|---|---|
kubectl delete … | after stripping -n/--namespace/--context | CRIT |
terraform|tofu|terragrunt destroy | also apply -destroy, -chdir= | CRIT |
pulumi destroy | — | CRIT |
Cloud
| Pattern | Condition | Sev |
|---|---|---|
aws … rb / delete-* / terminate-* / remove-* | — | CRIT |
aws s3 rm --recursive | single object = ORD | CRIT |
gcloud … delete · az … delete | — | CRIT |
Databases (needs a db client: psql, mysql, mongosh, sqlite3…)
| Pattern | Condition | Sev |
|---|---|---|
SQL DROP TABLE|DATABASE|SCHEMA · TRUNCATE · DELETE FROM | — | CRIT |
Mongo dropDatabase() · .drop() · deleteMany() | — | CRIT |
redis-cli/valkey-cli FLUSHALL/FLUSHDB | — | CRIT |
Misc & unwrapping
| Pattern | Note | Sev |
|---|---|---|
crontab -r | wipes the user's crontab | CRIT |
Unwraps sudo · \rm · bash -c "…" · <<< · xargs | recursive, depth ≤4 | CRIT |
> redirects · compose down · push --force-with-lease · python x.py | deliberately not flagged (signal-to-noise) | SKIP |
FAQ
Questions before you install.
Does it slow me down?
No. Safe commands pass silently — you only see a prompt on genuinely destructive ones. Parsing runs under a ~200 ms budget and fails open: if anything hangs, the command proceeds rather than blocking your workflow (flip NDG_FAIL_CLOSED=1 to invert).
Won't it flag every rm and terraform?
No — it parses by token. terraform plan, transform, perform, rm ./build stay quiet. It tiers severity: reversible local deletes are ORD and pass silently in bypass mode; only irreversible CRIT commands raise a prompt.
Does it really work in bypassPermissions?
Yes — that's the point. An explicit ask from the hook survives bypass, so even a fully-autonomous agent gets stopped at a DROP TABLE. Reversible commands still pass silently there so you're not spammed.
Can I customize what it catches?
It's a single readable Python file, stdlib-only. Tune behaviour with env vars — NDG_NOTIFY, NDG_SOUND, NDG_TIMEOUT_MS, NDG_FAIL_CLOSED — or edit the rule sets directly. Dry-run any command with --test.
What about the macOS banner & sound?
On a detect you get a system sound + banner via osascript (always reliable). Want the banner click to focus your terminal instead of Script Editor? Opt in with brew install terminal-notifier + NDG_NOTIFIER=terminal-notifier.
Install
Put a y/N between your agent and a very bad day.
Two lines in Claude Code. No account, no service, no telemetry.
/plugin marketplace add vakovalskii/destructive-guard
/plugin install destructive-guard@destructive-guard