Skip to content
Agentic Control Plane

How to Block Dangerous Commands From a Coding Agent (2026)

David Crowe David Crowe · · Updated · 6 min read
claude-code codex cursor tool-policy safety
Share X HN LinkedIn

Real recording, nothing mocked: a coding agent tries to force-push main; the safety floor blocks it in the call path before it runs — no policy tuning, no prompt. Then a network call is held for approval, normal work is allowed, and the closing audit.jsonl shows every decision. This is option #4 below, running.

The failure mode is documented at this point: an agent gets a confident idea, runs a destructive command, and the transcript reads like an apology. A database deleted during a code freeze, a home directory wiped by one malformed command. The model cannot be the safety layer — the layer that says no has to sit outside it, in the call path.

Five ways to put it there, ranked by what they actually catch. The criteria: is the block deterministic (not a suggestion the model can talk itself past), does it survive respelling (rm -r -f, bash -c wrappers, chains after &&), does it cover every agent you run, and does it leave a record.

1. The permission prompts you already have

Claude Code, Codex, and Cursor all ship approval prompts, and if you run with them on, most catastrophes die in the dialog. This is the baseline and it’s genuinely good. Two ways it erodes: approval fatigue (the hundredth “yes” is a reflex, not a review), and --dangerously-skip-permissions — the moment the workflow gets annoying enough, the prompts go away entirely, and the flag is popular.

2. Built-in deny lists

One step better: rules like Claude Code’s permissions.deny. Free, native, and worth setting — our recommended list. The catch: they match strings. rm -rf / is denied; rm -r -f / or bash -c "rm -rf /" may not be, and we’ve written up how the deny list gets bypassed. String rules are a tripwire, not a wall. They’re also per-tool config — your Codex rules are a different file in a different dialect.

3. dcg — the dedicated shell tripwire

dcg does one thing well: a Rust hook that blocks destructive shell commands for Claude Code, Codex, and Gemini CLI, with far more spelling-awareness than a string deny list. If all you want is “stop rm -rf”, it’s the best single-purpose tool in the category and the right minimal answer. What it doesn’t try to be: a policy layer (allow/ask/deny per tool, per subcommand, per host), or an audit trail of everything else the agent did.

4. A local policy floor in the hook path

This is the layer we build, so judge accordingly — but the mechanism is the point, and it’s open. Every one of these harnesses fires a pre-tool-call hook; whatever answers that hook can deny the call deterministically, and hooks keep firing even under --dangerously-skip-permissions. So: parse every segment of the command token-by-token (catching the respellings and the bash -c laundering), deny a short catastrophic floor unconditionally, apply your own allow/ask/deny rules for everything else, and log every decision. One command installs it for Claude Code, Codex, and Cursor at once, no account, on-device:

curl -sf https://agenticcontrolplane.com/install.sh | bash -s -- --local

The decision engine is 268 lines you can read; the 60-second recording shows a real force-push dying at the hook. Weakness, stated plainly: it’s not isolation, and a hook can only govern the harness it’s wired into — a raw curl | sh you run yourself is out of scope.

5. Devcontainers — when the input is untrusted

If your agent ingests untrusted content — public issues, scraped pages, other people’s PRs — the right answer is a boundary, not a filter: devcontainers or a VM. Nothing command-level protects you from what a prompt-injected agent does with the access it legitimately has. Containment doesn’t replace command control, though: inside the container, your repo credentials still work, and a force-push to main is still a force-push. Run both.

The honest summary

Prompts on → you’re mostly fine until fatigue wins. Deny lists → set them, know they’re bypassable. dcg → best pure tripwire. A hook-path policy floor → deterministic, respelling-proof, cross-vendor, logged — our lane. Untrusted input → container first, then all of the above inside it.

Can Claude Code's built-in deny list block rm -rf?

It matches command strings, so the plain spelling yes — but rm -r -f, a bash -c wrapper, or a chain after && can slip past string rules. Blocking reliably needs token-level parsing of every segment of the command line.

Do hooks still run under --dangerously-skip-permissions?

Yes. The flag removes the permission prompts, not the hook dispatch — a PreToolUse hook that answers deny still blocks the call. That’s why enforcement that lives in a hook survives the flag while the built-in prompts don’t.

Is a sandbox enough on its own?

A sandbox bounds the blast radius; it doesn’t distinguish a good command from a bad one inside the boundary. An agent in a devcontainer can still force-push your repo to main, because the credentials you gave it work fine from inside. Containment and command control solve different failure modes.

What commands should always be blocked?

The list with no legitimate agent use is short: recursive force-deletes of root or home, mkfs, dd onto a raw disk device, fork bombs, recursive chmod 777 on /, and force-pushes to main. Everything past that is a judgment call that belongs in a policy you can edit.

Share X HN LinkedIn
Get the next data drop
What agents actually cost, new tool-surface captures, and the occasional incident post-mortem — sent when we publish something worth your inbox, not on a schedule. Unsubscribe anytime.
Share: Twitter LinkedIn
Related posts

← back to blog