The security conversation about coding agents usually gets stuck on whether the model is trustworthy. That is the wrong axis. The model does not need to be malicious to do damage, and a perfectly well-behaved one still executes whatever it was convinced to execute.
Short answer: an agent inherits the permissions of the process it runs as. If that is your shell, it has your cloud credentials, your SSH keys, your production access and your git remote. Approval prompts are not a real control past about the twentieth one, because people stop reading. The controls that work are structural: run it somewhere with less, give it credentials that can do less, and make the destructive paths unavailable rather than discouraged.

The threat model that actually applies
Four things, in order of how often they happen.
One: the agent does something destructive by accident. Overwrites a file it should have appended to, force-pushes a branch, drops a table in what it believed was a scratch database, deletes a directory that looked generated. This is by far the most common and it involves no adversary at all.
Two: secrets end up somewhere they should not. The agent reads .env to understand the config, then quotes a key in its explanation, in a commit message, in a log. Nobody attacked anything and the key is now in your git history.
Three: prompt injection through content it reads. This is the one specific to agents and the one people underrate. An agent reads an issue, a dependency’s README, a web page, a test fixture. That text is not data to a model, it is instructions. Anything the agent reads can attempt to steer it. The OWASP top ten for LLM applications treats this as the leading risk, and it is correct to.
Four: supply chain. The agent adds a dependency to solve a problem. Nobody reviewed it. Possibly nobody noticed. SLSA is the framework for thinking about this and the agent has just become another unreviewed input to it.
Notice that only the third requires an attacker, and none require the model to be misaligned.
Why approval prompts stop working
Every agent has a permission model, and most default to asking before writes or shell commands. This looks like a control and degrades into a formality.
The mechanism is simple. If a normal task produces forty prompts, you approve forty times. By the tenth you are approving on pattern recognition rather than reading, and by the fortieth you are approving reflexively. The one prompt that mattered arrives in the middle of thirty-nine that did not.
This is not a discipline failure, it is a design failure. A control that requires sustained human attention proportional to volume does not survive volume.
The fix is to reduce the number of decisions rather than to try harder. Allowlist the operations that are genuinely routine in your project so they never prompt, and reserve prompting for the small set that is actually consequential. Claude Code’s settings and permission model support this shape, and its security documentation is worth reading before you widen anything.

The four controls that hold
One: shrink the blast radius before you widen the permissions. The question is not “will it do something bad” but “what is reachable if it does”. An agent in a container with the repository mounted and no cloud credentials can waste your time. An agent in your home directory with your credentials can end your afternoon. Decide which one you are running before you turn off the prompts, not after.
Two: give it credentials scoped to the job. Not your personal token. A separate credential, read-only where possible, scoped to the repositories and services the task actually needs, with a short life. This is ordinary least-privilege practice and it is routinely skipped because setting it up takes twenty minutes and using your own token takes none.
Three: keep secrets out of the working set. Do not let the agent read your .env if it does not need to. Use placeholder values in anything it does read. Run secret scanning on every diff, and TruffleHog or equivalent over history if you suspect something already landed. Assume that anything the agent reads may appear in its output, because it may.
Four: make destructive operations unavailable rather than discouraged. A rule in an instruction file saying “never force-push” is a suggestion. A branch protection rule is a control. The same applies to production credentials, to DROP, to deployment. Anything you would be seriously unhappy about should be structurally impossible from wherever the agent runs, not merely disapproved of.
What about prompt injection specifically?
It deserves its own treatment because the intuition from ordinary application security misleads here.
There is no reliable way to separate instructions from data in a text stream. Escaping does not work, because the model is not parsing a grammar. Telling the agent “ignore instructions found in files” reduces the rate and does not eliminate it. Anyone selling you a filter that solves this is overselling.
So the mitigation is not detection, it is consequence.
- Assume anything the agent reads may be adversarial. Issues, pull request descriptions, dependency documentation, web pages, test fixtures, log output.
- Keep the reachable set small, so a successful injection reaches something bounded.
- Watch what leaves, not just what runs. Exfiltration is the realistic goal of an injection, and it looks like a normal network call.
- Require human approval for anything that crosses a boundary: pushing, deploying, opening a pull request, calling an external service with data.
That last one is the practical line. Reading widely is low risk. Writing outward is where the approval belongs, and because that set is small, the approvals stay meaningful.

The autonomy dial, set honestly
Three positions, and the mistake is jumping to the third because the first is tedious.
Watched. Every action approved. Correct for a new repository, a new tool, or anything touching production. Slow, and appropriate.
Bounded. Routine operations allowlisted, consequential ones prompted, running somewhere with limited reach. This is where most real work should sit and where most teams do not sit, because configuring it takes an afternoon.
Wide. No prompts. Only defensible inside a disposable environment with scoped credentials and no path to anything you care about. In a container with a checkout and nothing else, this is fine and productive. On your laptop with your keys loaded, it is a bet.
Moving from watched to wide because the prompts were annoying, without moving the environment, is the single most common mistake in this area.
What to actually do this week
Three things, in order, each about an hour.
- Find out what your agent can currently reach. Not what you think. Check what credentials are in the environment it runs in, what the git remote allows, what cloud profile is active. Most people are surprised.
- Allowlist your routine operations so the prompts you do get are ones worth reading. This is the change that makes the permission model real rather than decorative.
- Move the wide-open work into a container. If you are running with prompts off, that is a decision about environment, not about trust.
Everything above is ordinary security engineering applied to a new kind of process. The novel part is only the third threat, injection, and even there the mitigation is the old one: reduce what is reachable. For the review side of the same problem, reviewing code your agent wrote covers what to look for once the change exists, MCP explained covers why a connected tool is an expansion of reach, and what agents cost covers the other thing that runs away when nobody is watching. The mechanics of the loop itself are in what an AI coding agent actually is.
