Pushary
Blog
Guides

Allowlist vs denylist for AI agent permissions, and why prefix denylists leak

Allowlist vs denylist for AI agent permissions: why a prefix denylist leaks, plus a read-only safe floor and a copyable allow/ask/deny set.

AG
Aadil Ghani
Founder, Pushary
May 28, 20265 min read
Share

For AI agent permissions, prefer an allowlist over a denylist. A denylist tries to name every dangerous command and always leaves a gap, because an agent or an attacker can chain a command around a prefix rule. The safer shape is a read-only safe floor that auto-approves proven read commands, an allowlist of writes you trust, and a human approval for everything else.

Key takeaways

  • An allowlist denies by default and only lets through what you named. A denylist allows by default and only blocks what you named, so anything you forgot runs.
  • Prefix denylists leak because commands chain. git push blocked still lets git config x y && git push or a subshell slip past a naive prefix match.
  • Start from a read-only safe floor (cd, ls, cat, git status, grep), allowlist the writes you trust, and route the rest to a human approval on your phone.

The two models, plainly

A denylist starts from yes. Everything the agent wants to run is allowed unless it matches a rule that says block. You write down rm -rf, git push --force, DROP TABLE, and hope you covered the dangerous set.

An allowlist starts from no. Nothing runs unless it matches a rule that says allow. You write down git status, npm test, ls, and everything you did not name stops for a human.

The difference is what happens to the command you never thought of. On a denylist it runs. On an allowlist it pauses. For an agent that writes its own commands at runtime, the set of commands you never thought of is large, and it grows every time the model gets more capable.

Why a prefix denylist leaks

Most denylists match on a prefix. Block anything starting with git push. Block anything starting with curl. This feels tight and it is not, because a shell lets you reach the same effect without the prefix.

Say you blocked the prefix git push. These get the same result and do not start with it:

git config alias.p push && git p origin main
cd /repo; git push origin main          # leading cd, not a push prefix
GIT_DIR=.git git push origin main       # env var first
echo done && git push origin main       # chained after a harmless command

A prefix match reads the first token, sees git config or cd or echo or GIT_DIR, decides the rule does not apply, and allows the whole line. The push rides along after the && or the ;. You do not need an attacker for this. An agent improvising a fix will chain commands on its own, and the denylist waves it through because the dangerous part is not at the front.

You can keep patching: block ;, block &&, block subshells, block env-var prefixes. Every patch is another special case, and the next bypass is a pipe, a backtick, a $(), a here-doc, or a base64 string piped to sh. A denylist is a blocklist of strings against a language that has infinite ways to say the same thing. That is the leak, and it is structural, not a bug you can finish fixing.

A denylist that blocks git push but allows git will leak the moment a command chains the push after a safe token. Allowing by default means every gap in your list is an open door.

The shape that holds: safe floor plus allowlist

Flip the default. Deny everything, then carve out what you trust in two layers.

The first layer is a read-only safe floor. There is a known set of shell commands that only read state and change nothing: cd, ls, cat, pwd, git status, git log, git diff, grep, head, find. Auto-approving these costs you nothing and removes most of the interruptions, because reads are the bulk of what an agent runs. Pushary ships this floor on by default. We did not guess the list. It came from looking at 1,721 real production questions agents sent to humans and keeping only the commands that cannot mutate anything.

The second layer is your allowlist of writes. npm test, git commit, bun run build, the specific things you are fine letting an agent do unattended in your project. You name them, they run, nothing else does.

Everything outside both layers stops and asks a human. git push, rm, anything touching production, anything that spends money. The agent waits, you get a push notification, you approve or deny from your phone, and the answer goes back. Because the default is deny, a command you never imagined lands in the ask bucket instead of running silently.

Allowlist vs denylist at a glance

DenylistAllowlist + safe floor
Default for unknown commandsRunsStops for a human
Chained / obfuscated commandOften leaks past the prefixStill not on the allowlist, so it asks
MaintenanceAdd a rule after every new bad command you discoverAdd a rule when you want to grant something new
Failure directionFails open (silent run)Fails closed (human approval)
Best forA small, fully known command setAgents that write commands at runtime

A denylist is reasonable when the command space is small and you know it completely. An agent does not give you that. It generates commands you have never seen, so the only default that stays safe is deny.

A copyable starter set

Three buckets. Auto-allow the safe reads, allow a few trusted writes, ask a human for the rest, and deny the clearly destructive ones outright.

# allow (auto-approve, read-only safe floor is already on)
git status
git log
git diff
ls
cat
grep
npm test
bun run build

# ask (stop and notify a human)
git push
git commit
npm publish
rm
psql
terraform apply

# deny (never run)
git push --force
rm -rf /
DROP TABLE

Tune the buckets to your project. Move git commit to allow if you trust it, move npm test to ask if your tests hit prod. The structure is the point: a deny-by-default base, a small allow set, and a human for the gray area. Pushary matches on tool arguments, not just the tool name, with exact, prefix, and tool precedence, so git status can be allowed while git push still asks. See permission policies in the docs for the matching rules, and Permission Autopilot to turn your own approve and deny history into one-tap allowlist suggestions.

Common questions

Is an allowlist or a denylist better for AI agents?

An allowlist, in almost every case. Agents generate commands at runtime, so the set of inputs you did not anticipate is large. A denylist allows that set by default. An allowlist stops it and asks a human, which is the direction you want to fail in.

Why does a prefix denylist leak?

Because shells chain and rewrite commands. A rule that blocks the prefix git push does not match cd /repo && git push or git config alias.p push && git p. The first token is harmless, the prefix check passes, and the dangerous part runs after it.

What is a read-only safe floor?

A fixed set of shell commands that only read state and change nothing, like cd, ls, cat, git status, and grep. Auto-approving them removes most interruptions without adding risk. Pushary's floor was decided from 1,721 real production questions, keeping only commands that cannot mutate anything.

Can a permission policy stop every bad command on its own?

No, and you should not expect it to. Enforced gating works where there is a hook to intercept the tool, which covers Claude Code, Codex, Gemini CLI, Cursor, and Hermes through the CLI. Claude Desktop has no hooks, so its connector can notify and ask but cannot enforce a gate. Treat the policy as the floor and the human approval as the backstop.

Set the default to deny, keep the safe floor on, and let a human clear the gray area from their phone. That is what the permission policy and audit trail are built around, and you can wire it into every agent you run from one place.

AG
Aadil Ghani
Founder, Pushary

Building Pushary so an AI agent can reach you on your phone and wait for a yes before it does something you would not want.

Read next

Guides

What an AI agent audit log should capture for teams and compliance

The fields a coding-agent audit record needs to be worth keeping, and the honest line on what GDPR-aligned and self-assessed actually means.

Jun 27, 20265 min readAadil Ghani
Guides

Who is accountable when an AI agent makes a mistake?

An agent has no accountability of its own. The human who ran it owns the outcome, which is why a record of who approved what matters.

Jun 25, 20264 min readAadil Ghani
Guides

How to run multiple AI agents at once without losing track

The workflow and the board for running concurrent agent sessions without losing track of which one needs you.

Jun 22, 20264 min readAadil Ghani

Get a push the moment your agent needs you

Approvals, done alerts, and a kill switch for Claude Code, Codex, Cursor, and the rest. It takes a couple of minutes to set up.