Just-in-time access shrinks an agent's window, not its reach
Short-lived credentials limit how long an AI agent holds access, not what it can do. Put hard limits in code first, then shorten the tokens.
Picture a support agent that can refund customers up to R500. An attacker opens a ticket with instructions buried in it, the agent reads them, and it refunds R50,000.
The vendor pitch for this failure is just-in-time access: no standing privileges, a fresh token for every call, expiring in seconds. Now run the same ticket again. The agent asks for a token scoped to “issue refunds”, gets one that lives for 30 seconds, and issues the R50,000 refund in the first two seconds.
Just-in-time credentials shrink how long an agent holds access, not what it can do with it. For an agent, the control that matters is a hard limit enforced outside the model: the refund API that rejects anything over R500. JIT versus standing access comes second. It’s worth doing, but after the limit, not instead of it.
This is for the people wiring agents into business systems and the people signing off on them, at firms where the security team might be one person. It picks up where the least-autonomy post left off: once you’ve decided a step needs an agent, what actually fences it in.
I’ve seen the pattern with a vendor’s SaaS agent that asked for admin-level scopes to do a job that needed read access.
Prompt injection doesn’t steal the credential. It borrows the agent, which already holds the credential legitimately, at the moment it’s using it. However short the token’s life, the attacker’s instruction runs inside it. Only a scope limit changes the outcome.
| Control | Limits | Stops the injected R50,000 refund? |
|---|---|---|
| Token expires in seconds | Time | No. The refund happens inside the window |
| No standing privileges, token issued per task | Time | No. The task is “issue a refund” |
| Token scoped to the refund tool only | Which tool | No. It’s the right tool |
| Refund API rejects amounts over R500 | Value | Yes |
| Refunds only to the card that paid | Recipient | Partly. The money can only go back to the ticket’s author, who may be the attacker |
| At most five refunds an hour per agent | Rate | Caps the total loss |
OWASP’s LLM06 Excessive Agency entry says it directly: implement authorisation in downstream systems rather than relying on an LLM to decide whether an action is allowed. The R500 cap in the prompt is a request. The R500 cap in the refund API is a control.
The next layer of the pitch is “intent-based” authorisation: a policy engine that checks whether the agent’s action matches what it meant to do. It sounds like the answer to injection. It isn’t, because there is no intent to check.
A policy engine sees a request: the caller, the endpoint, the parameters. When a product says it evaluates intent, the intent is text the model wrote, usually a field like reason: "customer requested refund for damaged goods". The model wrote that after reading the attacker’s ticket.
What a policy engine can check reliably is the request itself:
- Parameters. The amount is under R500. The date range is under 90 days. The record count is under 50.
- Recipients. The email goes to an address already on the client’s file. The payment goes to an account on the allowlist.
- Rates. No more than a set number of these actions per hour, per agent and per customer.
- Pairing. The refund matches an order that exists, for no more than its value.
All four are deterministic. The same request gives the same answer every time, and an attacker can’t talk them round.
Another common design splits the agent into roles: a planner, an inspector that reviews the plan, and an executor that holds the credentials. It helps only when the split removes something the attacker needs.
If the inspector is another language model, it reads the same ticket the planner read, instructions and all. Models follow instructions in content, and they can’t reliably tell yours from an attacker’s. An injection that fooled the planner can carry a second paragraph addressed to the inspector. Two models in series aren’t two controls. They’re one control that runs twice. The inspector has to be code.
The split that works is the one that takes a leg off the lethal trifecta. Simon Willison described it in 2023 as the dual LLM pattern: a quarantined model reads the untrusted content and has no tools, and a privileged model holds the tools and never sees that content directly. The component that reads the customer’s ticket should hold no credentials at all. It passes structured fields (an order number, a category, an amount) to code, and code decides what happens next.
I’ve built an agent this way, with the reader model holding no tools at all.
Most business agents act for someone: the customer on the chat, the adviser who asked for a summary, the claims clerk working a queue. The service-account habit is to give the agent one broad token that can reach every customer’s records, and then trust the model to only look at the right one.
Turn that round. The agent gets a token derived from the user it’s acting for, and never more than that user could do. Usually it gets less, because the agent needs read access to one client’s file, not the adviser’s full book.
OAuth has a standard for this. RFC 8693, OAuth 2.0 Token Exchange (January 2020) defines how a service swaps one token for another, including delegation. With delegation the agent keeps its own identity, separate from the user’s, and the token’s act claim records that the agent is acting for the user. An injected instruction to read another client’s file then fails at the API, because the token doesn’t reach it. The log shows both names: which agent did it, and for whom.
The best argument for JIT is that agents leak credentials. A token ends up in an environment variable that gets printed to a log, a debug trace, a support bundle, or a compromised MCP server the agent talks to. A token that expired four minutes after it was issued is worth very little to whoever finds it. A standing key that works until someone rotates it is worth a great deal.
So do it. JIT addresses a real threat: the stolen credential. Prompt injection is a different threat, and the agent doesn’t need to lose its credential for the attacker to use it. Order the work by which threat you’re more likely to meet this year.
Everything above assumes you run the agent’s code. A five-person financial services provider running one agent inside a vendor’s SaaS has no policy engine, no token broker and no developer to build either. The proportionate version uses what the vendor already enforces:
- The narrowest OAuth scopes the integration offers: read-only where the agent only reads, one mailbox instead of all of them.
- Per-tool limits on the vendor’s side: spend caps, recipient allowlists, a daily action ceiling.
- A separate account for the agent, never a person’s login shared with it.
- Logs that record which user the agent acted for, and someone who reads them.
The separate account has made the biggest difference in my experience. The logs show what the agent did, apart from what its owner did. A vendor that can’t offer any of these is telling you something about how its product will fail.
An agent’s credential is a service account, whatever the vendor calls it. POPIA section 19 already asks you to take appropriate, reasonable technical and organisational measures against unlawful access to personal information, and a broad, shared token is hard to defend under that wording. The no-AI-Act post maps section 19 to the Joint Standards and PCI DSS.Sidenote: My reading of POPIA as a practitioner, not legal advice. Your compliance officer decides how it applies to your firm.
The R50,000 refund in the opening needed a token for two seconds. A R500 cap in the refund API would have stopped it at the first request.
More on GenAI
Who picks the model when a Claude Code workflow fans out
Workflow agents can quietly run on your priciest model. The four rungs that pick each agent's model, a probe that proves it, and how to own Claude's guide.
Give your AI workflows the least autonomy that works
Autonomy is a permission you grant a model, not a feature it has. Five levels for AI workflows, and how to pick the lowest one each step can live with.