Every agent that can run commands has some version of a permission model: ask each time, ask once per command, allow a list, or allow everything inside a sandbox.
People tend to settle at one of two extremes. Approve-everything, because the prompts were annoying. Or approve-each-time, and then stop using the agent for anything long because it needs babysitting.
There is a middle, and it is mostly a matter of sorting commands by what they can destroy.
Sort by blast radius, not by danger#
The useful question is not "is this command dangerous" but "if this runs a hundred times with wrong arguments, what is gone?"
That gives you three tiers.
Tier 1 — Allow always#
Read-only, or trivially reversible, or scoped entirely to files git already tracks.
ls,cat,grep,rg,find,git status,git diff,git log- Your test command, your linter, your type-checker, your build
- Formatters that only touch tracked files
These are the commands the agent needs constantly. Every approval prompt here is pure friction, and the worst outcome of running them wrong is a confusing error message.
Getting this tier right is what makes an agent feel usable. If it has to ask before running your tests, it will not run your tests, and it will hand you unverified code.
Tier 2 — Allow with a boundary#
Writes, but recoverable through normal means.
git add,git commit,git checkout -b— recoverable, and it leaves a trail- Package installs in a project directory
- Writing files inside the repo
- Local database migrations against a dev database
Allow these, and rely on git as the undo mechanism. The important precondition: the working tree should be clean before you start a task. An agent operating on top of your uncommitted work makes it impossible to separate its changes from yours, which removes the undo you were relying on.
Tier 3 — Always ask#
Anything that reaches outside the repository or cannot be undone with git.
git push, especially with--force- Deploy commands
- Anything against a production or staging database
rm -rfoutside the working directory- Anything with a credential in the arguments
- Package publishing
curl/wgetto arbitrary URLs, especially piped to a shell
There is no version of "the agent is good enough now" that makes an unattended git push --force a reasonable default.
Write it down as config#
Most tools support this declaratively — an allow list and a deny list in a settings file you commit. Do that rather than clicking through prompts, for three reasons: it is reviewable, it is shared with the team, and it makes the policy visible when someone questions it.
Prefer a deny list that is short and absolute plus an allow list you extend as you go. Every time you approve something for the third time, move it into the allow list. Every time something surprises you, add it to the deny list.
Sandboxes change the calculation, but not entirely#
Some agents run commands inside a container or an OS sandbox with no network and a restricted filesystem. That genuinely does raise how much you can allow — a destructive command inside a sandbox destroys a sandbox.
Two things it does not fix:
The repository is usually mounted for real. A sandbox that cannot see your codebase is not useful, which means the codebase is exactly the thing still at risk.
Network access is the whole game. A sandbox with network can still exfiltrate whatever it can read. If you are running with broad permissions and secrets in the environment, no amount of filesystem isolation helps.
Secrets#
The rule is short: the agent should never need to read a real credential.
- Keep secrets out of files the agent reads.
.envin.gitignoreis not enough — add it to the tool's deny list too. - Use a hook or a deny rule to block commits containing key-shaped strings.
- If the agent needs to hit an API, give it a scoped test key, not the production one.
The realistic threat here is not a malicious model. It is an agent helpfully pasting a key into a log line, a test fixture, or a commit message, because that made the error easier to debug.
A reasonable default#
If you want somewhere to start:
- Allow all reads, all searches, and your project's test/lint/build commands, unconditionally.
- Allow writes inside the repo and local git operations, with a clean tree before every task.
- Deny push, deploy, production access, and anything outside the working directory — with no exceptions you approve in the moment.
Then adjust from what actually annoys you rather than from what might theoretically go wrong.
Next: what to put in the context window, and what to keep out.