InsightJuly 30, 2026

AI agent blast radius: nothing bounds the union of grants

An AI agent's blast radius is the union of every grant you never revoked. Least privilege bounds each grant, not how long it stays open.

Ling Wu
Ling Wu
ai-agentssecurityiamleast-privilegedogfooding

Post anything about letting an agent touch production and the same two questions show up within the hour: how exactly are you letting a model reach a database, a cluster, a deploy pipeline, and when it does something you didn't expect, what stands between it and what you can't undo. Nobody asks whether the model meant well. They ask where the wall is, and whether it's load-bearing.

Buried in OpenAI's account of how one of its evaluation models ended up inside Hugging Face's production database is an answer to a smaller version of that question, given by accident: for the purposes of the benchmark, they had reduced the model's refusals on cyber tasks. That sentence describes a team lowering a control for a specific, defensible reason, and getting a wider boundary than they meant to as a side effect. It's the same thing every operations team does on a Tuesday: a broad IAM grant opened to chase a permissions error, left in place after the deploy it unblocked. Nobody experiences it as a decision at the time. It's routine.

That routine shows up at scale everywhere anyone has measured it. Palo Alto Networks' Unit 42, analyzing 680,000+ identities across 18,000 cloud accounts, found 99 percent of cloud users, roles, services and resources had permissions left unused for 60 days or more, and that was back in 2022. Microsoft's own research on cloud permissions puts average usage at about 1 percent of what's granted, with more than 60 percent of identities untouched for 90 days or longer.

So the interesting question about an agent's blast radius isn't whether the model should have refused. It's what happens to the door behind it once the refusal is gone, and who was ever going to close it.

Every grant was minimized. That's not the disagreement.

Here's the objection I'd make if I read this post from the outside: every permission we hand an agent is scoped as tightly as the task allows. I'd make it because it's true, including of us. Of the permission grants our own agents have proposed and had approved on real infrastructure, roughly six in ten are read-only, and most of the rest are write access scoped to a single service. That's not where anything goes wrong.

Where it goes wrong is in what least privilege actually promises, which is smaller than it sounds. It's a discipline about space: how small one grant can be, not how long it gets to stay open. An engineer who scopes a role down to a single bucket and a single action has done the job right, completely, in the moment they did it. The trouble starts after: once the incident closes, that grant stops being minimal and becomes surplus, and nothing about how carefully it was drawn makes the transition visible to anyone. A grant sized perfectly for a two-hour investigation is a hundred percent excess by the time the postmortem is filed, and the scoping decision that made it look responsible never checks on itself again.

That gap between how tight a grant is and how long it survives persists because closing it is structurally harder than opening it. None of the major clouds expire a grant by default. On AWS, the attachment itself has no expiry: time conditions exist, but they live inside the policy document, and the AWS-managed policies most grants attach can't carry them. GCP lets you write a time-bound condition onto an IAM binding, but the binding stays in the policy after the condition lapses. Azure's Privileged Identity Management does force a duration when a role is activated, the strictest default of the three, and even there eligible assignments can be made permanent, with re-activation repeatable indefinitely. HashiCorp Vault is the one system I know of that revokes on a lease by default, and its own guidance shows engineers setting a max TTL of 120 days, telling even from the tool built to automate this.

Given that asymmetry, a rational engineer facing an unknown recovery time sets the grant generously rather than tightly: an early expiry interrupts real work, a permanent one costs nothing anyone measures. Tamnoon's researchers watched this from the inside: teams requesting broad permissions like s3:* "with a promise to tighten them later," a promise that "rarely comes." Access is granted in seconds; removal depends on someone remembering, years later. And the smaller a grant looks, the easier it is to leave alone. "It's just a viewer role" is a sentence nobody has ever had to defend in a postmortem. The discipline that makes a grant defensible is the same discipline that makes leaving it in place feel harmless. Neither instinct is a mistake. They just don't cancel out.

What accumulates from that isn't any single grant. It's the union of every grant ever issued and never revoked, all sitting open at once. Our own record of permission types granted through this flow includes plenty of ordinary read-only scopes, and it also includes full IAM administration, project-level IAM admin, service account admin, and Contributor-level access on Azure. Any one of those, opened for an afternoon and left standing, redraws the entire map of what's reachable, regardless of how disciplined the grants next to it were.

Which is the whole argument, compressed: least privilege bounds each grant. Nothing bounds the union.

We're a team of fewer than ten engineers running Zeabur, a cloud platform spread across more than eight clouds, twenty-odd Kubernetes clusters, and something over ten thousand hosts. Agents work on that infrastructure daily, investigating real incidents and proposing changes we approve. Nothing below is about making any single grant smaller. It's about the four places where the union above stops being an accounting problem and becomes a door something can actually walk through.

The Hugging Face incident is useful here as a shape, not as news. Its public account describes four places where the agent got one hop further, and at each one the thing that let it through was a credential or a permission decision. The table below maps each to the checkpoint we run instead. I'm not claiming our design would have stopped that attack, a different threat model from an agent doing operations work, only that these are the four places a standing grant turns into a door.

Link in the chainWhat gave way at Hugging FaceOur checkpoint
CredentialsLong-lived keys on compromised machinesShort-lived STS session, no long-lived customer credentials stored
Lateral movementOne foothold reached the whole networkTeam-scoped trust, per-session CloudTrail attribution
EscalationA model setting was the only thing between request and accessDiff proposal, human approval, separate execution role
ForensicsRefusals meant to help blocked the investigators tooHash-chained journal, fail-closed for mutations

Credentials that outlive the session

In the public account, the agent harvested cloud and cluster credentials sitting on the machines it reached: long-lived, reusable, good until somebody noticed.

So we hold nothing long-lived. When you connect a cloud account, your cloud trusts a role you create, and the only thing stored on our side is that role's ARN. Authentication happens through OIDC web identity: we mint a token that lives about five minutes, and the session it opens lasts about an hour, expiring on its own whether anyone was watching or not. There's no customer key in our database to steal.

To be precise about the limit of that claim, since it's a place vendors get sloppy: those temporary credentials do reach the agent's runtime, because that's how it makes API calls. What's true is narrower: no long-lived keys exist anywhere in the system, and anything that leaks dies with its session.

One foothold, everywhere

The next hop in the public account was lateral: one compromised sandbox, privilege escalation, then the rest of an internal network.

Two answers here, doing different jobs, and they shouldn't be confused for each other. The boundary is the trust relationship: the OIDC subject your role trusts is your team's identity, so a foothold can't borrow another team's trust, and we can't be tricked into spending one customer's trust on another's request. The attribution is separate: each agent session assumes the role under its own session name, so when you open CloudTrail in your own account, every call carries the specific session that made it, written by your cloud provider, still there if you stop being a customer. The first decides what can be reached. The second decides what can be proven, and proving stops nothing on its own, which is why it belongs in this list as the record rather than the wall.

A single switch that opens everything

The third link is where the original line about refusals actually bites. If the go-ahead for a consequential action lives inside a model's judgment, the go-ahead is a setting, and turning it down makes the action available.

So the go-ahead doesn't live there. An agent that hits a permission wall can't widen its own access. What it can do is file a proposal: the exact permission diff it wants, the resource it wants it on, and the reason it's blocked. A team administrator reviews that diff and approves or rejects it, and if approved, a separate permission-admin role the agent never assumes applies the change. Three things have to be true for access to grow, and the agent controls none of them. This is the honest answer to "what if someone injects a prompt": a prompt can ask for anything, and the request still has to survive a human holding an admin role in your own cloud.

A seeded demo of exactly that: the agent runs under a read-only role, hits a wall, and asks for one that can do the job. A human grants it from the credentials menu; the agent never widens its own access.

Customer teams have been running this flow on AWS, on GCP, and on Azure over the past few weeks: the agent hits a wall, proposes something specific, writes down why, and a person on the customer's own account decides within minutes. In at least one case the requester and the approving administrator were two different people, which is the entire point of the step.

The failures are almost more reassuring than the successes. Several proposals failed at execution, and every one failed for the same reason: the customer's own cloud said no, a 403 on creating a custom role, a service account binding that no longer existed. The outer boundary is enforced by your cloud, not by our intentions.

Forensics you can trust

The last link is the one Hugging Face wrote about with visible frustration: when they went to investigate, they couldn't easily tell the responder from the attacker, and their own disclosure notes the forensic work was "blocked by the guardrails of the hosted models we first tried."

That word needs a footnote, because two industries use it for opposite things. A model vendor's guardrail usually means a classifier inside the model that declines to answer. A security vendor's usually means an interception point in the infrastructure. Only the second kind survives a model that's been reconfigured, and that's the kind this section is about.

Every tool call, authorization decision, and approval appends to a hash chain, and each entry commits to the one before it, so a later edit doesn't reconcile quietly. For anything that mutates infrastructure, the journal write happens before the tool runs, and if the write fails, the call doesn't proceed. Read-only tools don't block on this, so the accurate claim is narrower than "everything is gated": changes to your infrastructure can't happen unaccounted for. Sealed segments land in write-once object storage.

The word we use for the result is tamper-evident, not tamper-proof, and the difference is real. The retention lock runs in a governance mode, which means someone holding the specific bypass privilege in that account can override it. What the chain gives you is that they can't do it quietly.

The wall we deleted

Until the middle of this month, our command layer had what we called a catastrophic floor: a deterministic rule set, written in code, that stopped irreversible operations before anything else got a say. We deleted it, five hundred and fifty-four lines in one pull request, because dogfooding kept showing it misfiring in both directions: it read a script cleaning up its own temporary directory as a dangerous recursive delete, and it never saw a destructive HTTP DELETE against a cloud API at all. A rule set that stops safe work and misses real deletions isn't a floor. It's a floor-shaped thing that makes you feel governed, which is worse than nothing, because you plan around it.

What replaced it is a judge model, instructed to require your authorization for destructive operations unless a standing rule you wrote explicitly covers that class. Read-only commands keep a deterministic fast path, and if the judge is unavailable, the command stops and waits for a human; a write never runs because a classifier was down.

But whether a destructive command interrupts you is now a model's decision, and a model's decision is a probability, not a guarantee, which is exactly the shape of thing this post says you shouldn't treat as a wall. So the honest inventory: the walls in this system are the three from the section above, enforced by your cloud, your administrators, and a hash chain. The command gate is a well-designed pause. And neither wall nor pause touches the union problem this post started with, because no command gate shrinks a standing grant.

Where least privilege doesn't reach

The gate chooses; it doesn't shrink

The clearest thing in our records runs opposite to the story a vendor would choose to tell.

An agent working in a customer's AWS account needed to list ECS services and couldn't, because the role had no ECS permissions at all. It proposed the narrowest read-only policy that would unblock exactly that call. The administrator rejected it. Six minutes later, a second proposal went through, attaching the full ECS access policy instead, with the reason recorded as the customer explicitly asking for the broader scope and accepting it.

That's the honest version of what an approval gate actually is. The gate didn't keep the permission small. A human looked at the smallest thing that would work, decided it wasn't worth the round trips, and took more. What the gate produced wasn't a tighter grant. It was a decision with a name and a timestamp on it. Least privilege is the default we ship. It isn't something we can enforce on you, and any vendor telling you their approval flow guarantees minimal permissions is describing a customer who has never been in a hurry.

The same honesty applies to how often the gate stops anything at all. Teams run through plan approvals steadily, week after week, on real infrastructure, and rejections are rare. The more defensible reading isn't that agents propose flawless plans, it's that the gate is doing a different job than interception: it sets the rhythm, it puts a name on the change, it makes the record. Those are worth having. They are not the same as a net.

The ledger only runs one way

There's a sharper admission underneath, tied straight back to the union problem above. A grant we approve doesn't carry an expiration. Shrinking it back down isn't something that happens on its own once the incident closes; it takes a second proposal, routed through the same approval step, asking an administrator to take access away, a request nobody feels urgency about filing. We log the approval that made a grant bigger with the same rigor as everything else here. Shrinking one goes through the same proposal step and would get the same record; that record almost never gets written. The ledger runs overwhelmingly in one direction. If you were hoping this post would end with us having solved that, we haven't. What we've built is a record of every time the union got bigger, with a name on each entry. Whether it ever gets smaller is still, today, on you.

Three more places I'd rather tell you than have you discover on your own. The command gate can be disarmed for a single conversation, and it expires, doesn't carry to the next one, with the window recorded as its own entry in the journal so you can draw a box around exactly when the protection was off. While it's off, it's off. And the problem nobody in this industry has solved, us included: an agent with individually reasonable permissions can compose them into an outcome nobody scoped for. Every framework I read while writing this, from cloud vendors to analyst firms, stops at governing the identity. None handles the composition. I don't have an answer either, and I'd be suspicious of anyone who claims one this year.

Read-only is not a small grant:

A read-only grant reads every secret it can reach, in every store that role can see. If that isn't acceptable in your environment, read-only isn't the reassurance the word makes it sound like. It bounds what the agent can change, not what it can see.

Which grant is still open

If you've already made this decision somewhere in your own systems, you know it doesn't feel like security architecture at the time. It feels like a Tuesday and a deadline. Somebody needed to see why the deploy was failing, the grant got wider for an afternoon, the afternoon ended, and the grant is very likely still there, sitting in the union nobody's tracking.

That's the decision this post is actually about. Not whether an agent can be trusted, which nobody can answer cleanly, but which link in the chain carries your checkpoint, and whether that checkpoint holds when a model has been reconfigured, a refusal has been turned down, or nobody remembered to close the door.

We put ours on access, on escalation, and on the record, and we took one thing out of the command layer because it was pretending to be a wall when it wasn't one. If you've built this differently and put the checkpoint somewhere else, I'd like to hear where, and especially if you think we've got this wrong.

ai-agentssecurityiamleast-privilegedogfooding
Ling Wu

Growth at Zeabur, the team building Nuphos.