Capabilities Are the Only Way to Secure Agent Delegation

Safe delegation for AI agents requires authority to be derived, not inferred

Niki A. Niyikiza published on
9 min, 1601 words

Categories: Agentic Security

A few months ago, I set out to explain how to secure agent delegation with IAM. I ended with an uncomfortable conclusion: it might not be possible at all.

In my last post, I showed that static permissions can’t track dynamic intent. We decompose tasks across agents, but authority stays consolidated. The gap is real.

Today I’ll make some enemies. Here’s my claim:

Delegation cannot be secured by refining identity because delegation is not an attribute of who you are. It is an operation on authority itself. Authority must be constructed, passed, and monotonically reduced as data. Capability systems are the only authorization model that treats delegation as a first-class, enforceable transformation rather than an inferred side effect.


Part 1: The Gap

The Valet Key

When you hand your car to a valet, you have two options.

Option A: Check his badge. Hand him your master key. Trust him not to open the trunk or take a detour. Trust becomes part of the security model.

Option B: Hand him a valet key. It starts the engine, limits speed, disables the trunk. You don’t need to trust he follows your instructions. The key is the policy.

Most systems use Option A. They verify the badge, hand over the master key, and hope for the best.

Hope, unfortunately, is not a security primitive.

The problem isn’t verification. The problem is there’s no valet key. Only master keys get handed over, and they remain valid across tasks whose intent was never considered.

Why Context-Aware IAM Cannot Enforce Delegation

A natural objection: “Modern IAM is more sophisticated than a badge check. Use session tags. Add conditions.”

This misunderstands the problem delegation creates.

Problem 1: Conditions Check Attributes, Not Derivation

IAM conditions are powerful. They gate permissions on identity, resource tags, time, location, device posture. When a Scraper requests access, the policy can ask: Is this the right identity? Is this resource tagged for this use? Is this request during business hours?

All of that works. None of it captures delegation.

When a Researcher delegates to a Scraper, what should the policy verify?

  • That this specific authority came from a specific delegation
  • That the Scraper’s scope is bounded by what the Researcher had
  • That the delegation chain is intact and attenuated at each hop

These aren’t attributes. They’re relationships. IAM has no field for “derived from” and no operator for “is subset of parent’s authority.”

You can tag the Scraper’s request with delegated_by=researcher. But the tag is a claim, not a proof. The policy cannot verify that the Researcher actually had the authority being exercised, or that the Scraper hasn’t exceeded it.

IAM asks: Does this identity, in this context, have this permission?

Delegation asks: Was this authority handed down, and did it shrink?

Different questions. IAM answers the first. It has no vocabulary for the second.

IAM evaluates requests against ambient identity. Delegation requires evaluating requests against derived authority. These are not the same operation, and no amount of context makes them equivalent.

Problem 2: Identity and Intent Live on Different Timelines

IAM binds permissions against identities.

Agents make decisions against tasks.

Those timelines do not align.

Agents do not know all required actions upfront. They reason, adapt, and decide mid-execution. A Researcher might read a config file, then decide to “quickly check” a customer database.

IAM cannot revoke authority mid-task because the identity has not changed. The permissions remain ambient.

Any system where authority is granted before intent is known cannot revoke authority when intent changes without reintroducing a control plane into the execution path.

Safe delegation requires authority to appear and disappear with the task itself.

Problem 3: The Confused Deputy

In 1988, Norm Hardy described the Confused Deputy problem: a program with legitimate authority being tricked into misusing it because it cannot distinguish why it has that authority. IAM makes every long-running agent a confused deputy by design. The Scraper has authority because it is a Scraper, not because the Researcher delegated specific permissions for this task. When a prompt injection tells the Scraper to exfiltrate data, the Scraper has no way to know this was not intended.

The authority is ambient. The intent is invisible. The deputy is confused because the system never records why authority was granted, only to whom.

The Pattern

Every failure traces back to the same root.

IAM asks who you are, then infers what you can do. Delegation isn’t about who you are. It’s about what you were handed, by whom, for what purpose. Identity cannot answer those questions because identity was never designed to.

Zero Trust doesn’t change this. It verifies identity at every hop rigorously and continuously. But it’s still identity. It still asks who, not what for.

It’s the wrong abstraction for the job.

The Core Issue

Tyler Close proved this formally in “ACLs Don’t” (2009): ACLs cannot make correct access decisions for interactions involving more than two principals, because required information isn’t retained across message sends.

Delegation is exactly that: a chain of principals, each constraining the next. ACLs forget the chain at every hop.

Context can describe intent. Authority must be derived.

If authority isn’t explicitly passed, it will be implicitly assumed. That assumption is where most security bugs live. Systems that infer authority from identity cannot distinguish between authority granted for this task and authority granted for any task.


Temporal Mismatch: Authority bound to identity vs. intent

Identity is constant. Intent changes. Authority should track intent.


Part 2: The Model

Once delegation must be enforced mechanically rather than socially or declaratively, any model that reloads authority from identity at each hop is structurally invalid. Only subtractive derivation survives.

Anatomy of a Capability Token

{
  "id": "cap_7f3a9c",
  "scope": {
    "tools": ["read_file"],
    "constraints": { "path": "/data/project-x/*.csv" }
  },
  "holder": "<agent_public_key>",
  "issued_at": 1705312200,
  "expires_at": 1705312500,
  "issuer_chain": [
    { "issuer": "<orchestrator_key>", "scope": {"..."}, "signature": "<sig>" },
    { "issuer": "<gateway_key>", "scope": {"..."}, "signature": "<sig>" }
  ]
}

Every field exists to preserve authority provenance across hops.

Scope: Not “this agent can read files” but “this agent can read CSVs under /data/project-x/.” The constraints are in the token, not in a policy server.

Holder: Binds the token to a public key. Possession alone isn’t enough.

Expiry: Minutes, not hours. Task ends, authority ends.

Issuer chain: Each delegation hop signed. The history embedded. Self-contained. No policy lookup required.

The Invariant: Authority Only Shrinks

A derived capability cannot exceed its parent.

Parent:  tools: [read_file, write_file], path: "/data/*"
Child:   tools: [read_file], path: "/data/project-x/*"

Reintroducing write_file would require forging cryptographic signatures. Widening the path would violate the constraint lattice.

DimensionRule
ToolsChild ⊆ Parent
ConstraintsChild narrower or equal
ExpiryChild ≤ Parent
TrustChild ≤ Parent

Attenuation isn’t policy. It is a consequence of cryptographic derivation.

This is what Mark Miller called the principle of least authority made mechanical: you can only give what you have, and you can only give less.

Why This Solves Confused Deputy

With capabilities, there’s no ambient authority. The Scraper doesn’t have permissions “because it’s a Scraper.” It only has what was explicitly handed to it for this request. When a prompt injection tells the Scraper to exfiltrate data, the Scraper checks its capability token. The capability permits read_file on /data/project-x/*.csv. It does not permit send_email or network egress. The request fails. The injection succeeds at the LLM layer. It fails at the authorization layer. The deputy isn’t confused because there is no hidden source of authority to be confused about. The only authority that exists is the authority that was delegated.

IAM vs Capability authorization under prompt injection attack

Same attack. Different authorization model. Different outcome.

Proof of Possession

Bearer tokens fail because possession equals authority. Leak the token, attacker is authorized.

Capabilities bind authority to a holder key. Using one requires proving you hold the corresponding private key.

1. Agent signs: { tool, args, timestamp }
2. Agent sends: capability + signature
3. Verifier checks:
   ✓ Signature matches holder key
   ✓ Capability permits action
   ✓ Constraints satisfied
   ✓ Not expired
   → Allow

A stolen token is useless without the key. A replayed request expires in seconds.

Authority is explicit. Possession is proven.

Offline Verification

Agents operate at millisecond timescales. They cannot wait for policy server round-trips.

Capabilities are self-contained: the token has scope, constraints, expiry, signatures, holder binding. Verification is local. No central engine. No runtime dependency.

Google’s Macaroons (2014) demonstrated that decentralized authority attenuation works at scale. Biscuit added public-key signatures and proved that attenuated, verifiable authorization is possible.

Agents require something stricter: task-local authority that is created, transformed, and destroyed inside the control flow of reasoning itself. That requirement invalidates identity-based reload and central evaluation entirely. These systems prove that attenuation and offline verification are possible. They do not address delegation that emerges dynamically from agent reasoning itself.


Part 3. Why Agents Need This

Once delegation becomes dynamic, these properties stop being nice-to-have. They become mandatory.

Ephemeral. Task-scoped authority. When the task ends, authority vanishes. No cleanup, no revocation.

Attenuating. Each hop narrows. Workers can’t re-delegate broader authority than received.

Bound. Stolen token is useless without the private key. Possession must be proven.

Offline-verifiable. Millisecond timescales. No policy server. Everything travels with the request.

Injection-resistant. The LLM can be fooled. The capability cannot. Attack succeeds at language layer, fails at authorization layer.

Auditable. The token is cryptographic proof of who authorized what. No logs to correlate.


Next

I’ve spent the last few months of weekends building an open-source engine that makes this operational. Releasing soon. Add your email to get the repository when it drops.