The first ten lines of a command policy for AI coding agents

In this Help Net Security interview, Vinay Thakker, CTO of KloudStax, explains how to set identity, credentials, and network limits for AI agents. He argues that agents should run under their own identity, with the person’s identity attached when acting on someone’s behalf. He covers where credential proxies go wrong, what the first ten lines of a command policy for a coding agent should say, and how to stop data from leaving through package installs or URL parameters. He also describes how red teams should test agent deployments.

AI agent access control

Should an agent get its own identity, borrow the identity of the human who launched it, or something in between? Walk me through how you’d decide for two cases: an agent that triages support tickets and one that pushes infrastructure changes.

Neither extreme. The deciding question is whether the agent is doing a job for the business or a task for one person.

If it’s doing a job for the business, it gets its own identity, with permissions scoped to that job. If it’s doing a task for one person, it still gets its own identity, but each request also carries the identity of the person who asked. The agent can then only do what both are allowed to do, and the logs show both names. Borrowing the person’s login outright is the option I’d rule out, because the agent’s actions become indistinguishable from the person’s, and it inherits everything that person can reach.

A ticket-triage agent is doing a job for the business. It works across every customer’s tickets and doesn’t belong to any one rep, so it runs as itself, with read access to the queue and write access only to routing and labels.

An infrastructure agent is usually doing a task for an engineer, so it carries that engineer’s identity alongside its own. But proposing a change and applying it should be separate permissions. The agent can open the change and run the plan. Applying it goes through the same review and pipeline a human’s change would.

Should agents hold secrets directly, or should they go through a vault or proxy that injects credentials at call time? Where have you seen the proxy approach go wrong?

Never hand an agent a long-lived secret. Past that, the question bundles two decisions worth separating: how long a credential lives, and whether the agent ever holds it.

Short-lived credentials limit how long a leaked one stays useful. A proxy that attaches the credential at call time limits whether it can leak at all, since the agent never sees it. The two work together, and the strongest setups do both: the proxy verifies the agent’s identity, obtains a short-lived credential scoped to that call, and attaches it on the way out.

For services that only accept static API keys, a proxy is the only way to keep the key out of the agent. If an agent does hold a short-lived token directly, check what sits next to it. A refresh token in the same environment is a long-lived secret by another name. Better still, tie the token to the machine or container that requested it, so a copy that leaks is useless anywhere else.

Neither control stops a hijacked agent from misusing access it legitimately has, and that’s where proxies go wrong. A proxy that attaches credentials to any request the agent makes is a confused deputy waiting for instructions. One that forwards the caller’s token downstream turns every hop into a place to steal it. One shared credential behind the proxy makes every agent look identical in the logs. And a vault on the same host as the agent hasn’t protected the secret, only moved it. A proxy doesn’t remove an agent’s authority. It concentrates it, so it needs per-agent identity, least privilege and a destination allowlist like anything else.

If you had to write the first ten lines of a command policy for a coding agent with terminal access, what would they say?

A short list of hard denies is a reasonable first step for a team just getting started. For an agent with a terminal, though, I’d write the policy as an allowlist, because a coding agent will always find the command you didn’t think to forbid. I’d also make sure every line is enforced by the harness rather than by asking the model to behave. Roughly:

1. The agent works inside one checked-out repository in a disposable environment and can’t read or write anything outside it.
2. It runs only approved commands: the project’s build, test, lint and format tools, read-only git, and file edits inside the repo. Anything else asks first.
3. No credentials live in its environment. Anything that needs one goes through a broker the agent can’t read.
4. It can’t open files that carry secrets, such as .env files, SSH keys or cloud credential files, even if one turns up in the workspace.
5. Network access is off except for the internal package mirror and the specific hosts this project needs.
6. Packages come only from the internal artifact repository, and adding a new dependency needs a human.
7. It can commit to its own branch and open a pull request. It can’t push to protected branches, force-push or merge.
8. Anything that changes shared infrastructure or data, such as an apply, a deploy, a migration or a delete, stops and asks.
9. It can’t escalate privileges or change its own policy, hooks or configuration.
10. Every command is logged with the agent’s identity and the task that triggered it, and a spend or runtime cap ends the session when it’s hit.

Several of these lines shouldn’t depend on the command policy at all. Branch protection belongs in version control, network rules in the firewall and credential access in the identity system, so they hold even if the agent finds a way around its own policy. None of this is new.

It’s the discipline we already apply to CI pipelines, applied to something that writes its own commands.

What should an agent be allowed to reach on the network by default? How do you stop data from leaving through an innocent-looking request, like a package install or a URL parameter?

I’d start every agent with no network access and work forward from what it holds. The test I use is whether a single agent combines private data, content from people you don’t control, and a way to send data out. With all three, one poisoned email or ticket is enough to move data out of the company. A research agent that reads the open web but can’t see anything internal can have fairly broad reach. A support agent reading customer email next to customer records shouldn’t have an open outbound path at all, and its replies should go through a channel someone controls.

When an agent does need the network, I’d deny by default, list exact hosts rather than wildcards, and enforce it somewhere the agent can’t edit. Package installs are where most allowlists quietly fall apart, because the public registries end up on the list. We’ve had internal artifact repositories for years for exactly this reason. Pointing agents at yours instead of the public registry closes most of that gap and gives you one place to hold back new releases or packages nobody has reviewed.

The URL parameter is the harder case, and I don’t think the network solves it. A plain GET to an approved host can carry data in its query string, and approving a host means trusting the platform, not everything people can write on it. Inspection is worth having for visibility, but I wouldn’t rely on it to catch injected instructions, since an attacker only needs the one variant the filter misses. What I trust more is design: once an agent has read untrusted content, it shouldn’t be able to send data out or take a consequential action without someone approving it.

How should a red team test an agent deployment? What does a good tabletop exercise for a hijacked agent look like?

I’d have a red team test the system around the model rather than the model itself. Assume an injection will eventually work, and measure what a hijacked agent can do from there: how far it got, which controls held, and how long it took anyone to notice. The known paths, such as reading its own credentials, reaching a host off the allowlist or editing its own configuration, should run as automated tests every time someone adds a tool or connector, because that’s when the exposure changes. That leaves the human red team free to hunt for the paths nobody has written a test for yet.

For the tabletop, I’d be careful about which incident you rehearse. The costliest agent incident I’ve seen this year was agents on one engineer’s account spending roughly $500,000 on cloud infrastructure in about 36 hours. That one was loud, and the bill eventually gave it away. A hijacked agent is built to stay quiet, reading the data it always reads and calling the systems it’s allowed to call, so rehearsing the loud case teaches the wrong instincts.

I’d run it in stages instead. A support agent processes a ticket carrying hidden instructions, and over the following week other customers’ records leave a few at a time inside its replies to the attacker. Reveal one clue at a time: a customer complaint, then an odd pattern in the agent’s logs, then the poisoned ticket itself. At each stage, ask the room what they’d do with only what they know. Can they tie the activity to one agent? Can they stop it without taking down support? Who decides, and how fast?

Then ask the question most teams can’t answer: after you end the session, how do you know it’s gone? If the attacker had the agent write an instruction into its memory, a shared configuration file or a reply template, the next session picks it up and starts again. Recovery means cleaning everything the agent could write to, not just revoking its credentials.

Download: The IT and security field guide to AI adoption

Don't miss