Home

/

Prompt Injection: Blast Radius

/

Capability: Authority the Agent Cannot Widen

Capability: Authority the Agent Cannot Widen

Chapter 8
Part II
6
min read

Prompt injection is not a new class of vulnerability. It is the confused deputy problem, which Norm Hardy named in 1988 in a paper subtitled or why capabilities might have been invented. His example was a compiler that held access to a billing file for accounting purposes. A caller who named their output file identically to the billing file could get the compiler to overwrite it on their behalf. The caller had no permission to touch that file. The compiler did, and was confused about who it was acting for.

The shape is identical. A component holds authority. Someone who lacks that authority persuades the component to exercise it on their behalf. The component is not compromised in any technical sense, it is confused about who it is acting for.

Your agent is a deputy holding a database connection, a mail transport and a payments credential. An attacker with none of those persuades it to use all three. Nothing was hacked. Something was asked.

The historical answer is capability-based security, and the reason it applies unusually well here is that agents make the classic mitigations useless.

Why roles are not enough

Role-based access control answers "who is this?" That works when the answer is stable.

An agent's answer is stable and wrong. It is the same principal on every turn, holding the union of every permission any of its tasks might need, for the lifetime of the process. Chapter 9's worked example turned on exactly this: the support agent legitimately holds refund authority, so a role check passes, and the injected refund goes through.

The question that separates a legitimate refund from an injected one is not who but what was this authority granted for. Roles cannot express that. Capabilities can.

A capability is a token that says: this specific operation, on this specific resource, until this specific time, granted for this specific task.

public sealed record Capability(
    string Operation,
    string Resource,
    DateTimeOffset Expires,
    string TaskId);

public sealed record CapabilitySet(IReadOnlyList<Capability> Items)
{
    public bool Covers(Capability required) =>
        Items.Any(c => c.Operation == required.Operation
                    && c.Resource == required.Resource
                    && c.Expires > DateTimeOffset.UtcNow);
}

Four fields. The first two narrow what can be done, the third bounds how long, and the fourth is what makes an audit trail readable six months later.

Derived from the user, not the service

The most consequential design decision in this chapter is where authority originates.

Most agents run as a service account, which is the natural thing to build. The agent has its own credentials, its own database user, its own API key. Those credentials are provisioned once, scoped to everything the agent might ever need, and never change.

A service account is a standing grant of maximum authority to the component most likely to be talked into misusing it.

The alternative is to derive each capability from the authenticated user's authority at the moment the task begins, and to grant only what the task requires.

public CapabilitySet IssueFor(ClaimsPrincipal user, TaskIntent intent) =>
    new(intent.RequiredOperations
        .Where(op => _authz.UserMayPerform(user, op))
        .Select(op => new Capability(
            op.Name, op.Resource,
            DateTimeOffset.UtcNow.Add(intent.Budget),
            intent.TaskId))
        .ToList());

Two properties fall out. The agent can never exceed the user on whose behalf it acts, which removes privilege escalation as a category. And the grant expires, so a compromised agent that sits waiting has nothing to wait for.

The uncomfortable question this raises is what TaskIntent is and who decides it, because if the model decides what the task requires, the model can widen its own authority and the chapter has achieved nothing.

Intent comes from the user's action, not the model's interpretation of it. A support agent clicking Investigate ticket triggers a task type declared in your code, with a fixed list of operations, written by a person. The model works inside that envelope and has no mechanism for enlarging it.

This constrains product design and that is the trade. Open-ended agents that decide their own next move cannot be scoped this way, which is a real argument for building agents that do one declared kind of thing.

The same idea, at enterprise scale

The identity industry arrived at the same place by a different route and calls it non-human identity, or agent identity.

The observation driving that work is that organisations have far more machine principals than human ones, they authenticate with long-lived static secrets, and nobody offboards them. Agents make it worse by creating principals that should exist for the duration of one task.

Most identity infrastructure does not do this well. Provisioning is built around onboarding, which assumes a principal that persists. An agent wanting a fresh scoped identity for a forty-second task fits badly, and teams discover this at exactly the point they try to do the right thing.

Two practical routes. Token exchange, where the agent trades the user's token for a narrower, short-lived one scoped to the task. Or keep the issuer in your own application and treat capabilities as an application-level concern, which is what the code above does.

The second is what most teams should do first. It is a few hundred lines, it works today, and it does not require a conversation with whoever owns identity. Start there, and move it into the platform when someone asks for it across three services.

Resource scope is where the reduction comes from

Of the four fields, Resource is the one teams under-use, and it is where most of the shrinkage actually happens.

Operation is easy. Everyone gets to a list of verbs: read, send, refund, delete. The instinct then is to scope the resource broadly, because narrowing it requires knowing which specific thing the task concerns, and at grant time that is not always obvious.

It is worth the work. An agent investigating ticket 4471 needs to read that customer's orders, not the orders table. It needs to refund that order, not any order. Those are two different blast radii and the difference is one string.

new Capability("refund", $"order:{ticket.OrderId}", expiry, taskId)

Where the resource genuinely is not known until the agent has looked, issue in two stages: a read capability scoped to the customer, then a write capability minted after the specific order is identified, scoped to it. That second issuance is a natural place for the gate to intervene and a natural place for chapter 14's human to stand.

An agent holding order:* has a capability system and the blast radius of a service account.

the-three-year-bug
why-the-industry-shipped-anyway
injection-is-not-jailbreaking
why-the-confusion-persists
the-lethal-trifecta
running-the-audit
why-filtering-fails
measured-here-on-a-named-model
why-this-is-structural
what-solved-would-look-like
the-harness
provenance-every-value-knows-where-it-came-from
on-the-reference-agent
quarantine-the-planner-never-reads-the-mail
what-two-models-cost-in-practice
capability-authority-the-agent-cannot-widen
expiry-is-a-feature
the-gate-the-model-proposes-code-disposes
the-policy
failing-closed
egress-closing-the-exfiltration-leg
how-much-can-actually-leak
sandboxing-containing-the-code-the-agent-writes
the-sandbox-held-and-it-did-not-help
poisoned-memory-poisoned-retrieval
cleaning-up-afterwards
the-tool-supply-chain
mcp-and-the-rest
human-in-the-loop-that-isnt-theatre
when-there-is-nobody-there
testing-for-injection
measuring-coverage-not-pass-rate
red-teaming-agents
a-finding-worked-through
when-it-happens-anyway
what-the-logs-cost-you-in-an-incident-you-did-not-have
governance-procurement-and-the-regulator
writing-the-policy
end-to-end
what-it-actually-took
what-stays-broken
why-this-is-probably-structural
the-trifecta-audit-worksheet
action-schema-and-policy-reference
control-mapping
prompt-injection-sources
incidents
appendix-e-what-we-re-ran-ourselves
e4-the-control-that-keeps-e2-and-e3-honest

Download the full PDF for free?

Free download — no account required

Get the PDF
Get the PDF
Related Chapters
Free Download
Get the full PDF
All pages, including all code examples, diagrams, and the appendix reference card.
No spam. Unsubscribe at any time.
Your email won't be shared.
Oops! There's a problem with your request. We're working on fixing it. Please try again later.