Agent Authorization
An agent acts on behalf of a user and must be limited to that user's permissions — not to the service account it happens to run under.
The requirement, the obvious build, and why it breaks
Every lesson starts where the work starts: someone asked for something, and the first implementation that comes to mind has a problem.
Whose permissions does an agent act with, and how do you make sure it cannot do more than the person who asked?
The assistant should be able to read a customer's orders and issue refunds — but only for the customer it is helping, and only if the support agent using it is allowed to issue refunds at all.
The agent runs as a service account with access to the whole database, and the prompt explains which records it should touch. It is our own code calling our own database.
The service account can read every tenant's data, so any injection, mistake or malformed filter is a cross-tenant data breach rather than an error (Tenant Isolation).
- The service account can read every tenant's data, so any injection, mistake or malformed filter is a cross-tenant data breach rather than an error (Tenant Isolation).
- A support user with read-only permissions gains write ability the moment they phrase a request the model interprets as a refund — the agent has escalated their privileges.
- The prompt cannot deny anything. It biases behaviour; it does not gate execution (The Model Is Not the Authorization Layer in the Security domain).
- Audit trails record the service account, so "who refunded this order" has no answer beyond "the assistant".
- Content the model reads — a support ticket, a product description, a PDF — can redirect it, and the service account's permissions decide how bad that is (Indirect Prompt Injection in the Agentic AI domain).
- Permission changes do not take effect: revoking a user's access does not revoke the agent's, because the agent never used their access.
What is actually happening
- This is the confused deputy problem in its textbook form: a privileged component performs actions on behalf of a less privileged one, using its own authority instead of the requester's.
- There are three identities in play and they are routinely collapsed into one: the end user on whose behalf the work is done, the agent runtime as a service principal, and the tool's own credential to a database or third party.
- Correct behaviour is an intersection, not a union: an action is permitted only if the user may do it *and* the agent is allowed to do it on their behalf *and* the specific object is in scope (Attribute-Based Access Control).
- Delegation must be explicit and carried. In practice that means the authenticated user's identity travels with the run and is used at every tool boundary — an on-behalf-of token, a scoped credential, or an in-process context that handlers must consult (Request Context Propagation).
- A tool credential with broad database rights is not made safe by application-level filters; the filter is one bug away from being absent. Scope the credential itself where the engine supports it (Database Privileges and Blast Radius in the Security domain).
- Some capabilities should require more than the user's own permission, not less: an action a user could perform manually with deliberation may still warrant approval when a model initiates it (Approval Gates and Risk Classes in the Agentic AI domain).
Three identities, one action
Agent authorization goes wrong at the moment three distinct identities get collapsed into one. The user asked. The agent runtime executed. The tool used a credential. Each one has its own permissions, and the action is only legitimate when all three permit it.
Drawing it makes the confused deputy visible: the dangerous path is the one where the tool's credential is broader than the user's permissions and nothing narrows it in between.
- User authority — what the human may do. The ceiling for everything.
- Agent authority — which tools this run may use at all, as an explicit allowlist.
- Tool credential — what the handler can technically reach. Scope it, because it is the blast radius.
- The permitted set is the intersection, and it must be enforced where the action happens.
Where the enforcement actually goes
Every option below is used in real systems, and they differ mainly in what happens when someone forgets a line of code. That is the property worth optimising: not how elegant the mechanism is, but how it fails.
What propagates identity, and what happens if a check is missed?
when Most services. Identity in the run context; each handler loads the object and checks ownership.
cost Correct only if every handler does it. Fails open on omission — use shared middleware to make omission hard (The Middleware Pipeline).
when The engine supports per-request roles or policies and the data model is tenant-shaped.
cost Fails closed on omission, which is the point. Costs connection-management complexity and engine lock-in (Connection Pools).
when Tools are separate services; the runtime exchanges the user's token for a scoped, short-lived one.
cost An identity provider on the request path, plus token lifetime and refresh handling (OAuth and OIDC From the Backend Side).
when The tool acts on an external account the user owns — a mailbox, a repository, a calendar.
cost Credential storage, rotation and revocation per user (Secrets Are Not Configuration).
when Irreversible or high-value: payouts, deletions, external communications.
cost A human in the loop and real latency; the only control that works when everything else is probabilistic.
when Never on a path that touches other users' data.
cost Any injection or mistake becomes a cross-tenant breach; the audit trail names a robot.
Failures worth recognising by name
These are the specific ways agent authorization breaks in production. Each has a symptom you can look for and a response that does not depend on detecting prompt injection.
| Trigger | Symptom | Cause | Response |
|---|---|---|---|
| Tool runs under a service account with full data access | Agent can answer questions about records the user cannot see | Confused deputy: agent authority used instead of user authority | Carry user identity into every handler; scope the credential (Object-Level Authorization) |
| Ownership check placed in the agent runtime | A job or second agent calling the same function bypasses it | Enforcement above the handler rather than inside it | Move the check into the handler; keep the runtime check as defence in depth |
| Read tools user-scoped, write tools not | A read-only user can trigger a state change through the assistant | Privilege escalation via the agent's own authority | Derive the tool allowlist from the user's role (Role-Based Access Control) |
| Untrusted content instructs the model | A tool is called with arguments no user typed | Indirect prompt injection reaching a privileged tool | Assume it succeeds; limit what the tool can reach and gate irreversible actions |
| Tenant filter applied only in application code | One missed clause returns another tenant's rows | Fails open by construction | Enforce below the application where the store allows it (Tenant Isolation) |
| Permission re-checked only at plan time | An action executes after the user's access was revoked | Time-of-check to time-of-use gap across a long-running plan | Authorize at execution, per action, not per plan |
| Approval shows a model-written summary | A human approves something other than what runs | The gate validates prose, not the concrete operation | Render the exact tool name, arguments and target object being approved |
| Audit records the service account | "Who refunded this?" cannot be answered | Delegation chain not recorded | Record human, run and tool identity together (Agent Audit Logs) |
How to build it
Most important first.
- Carry the end user's identity through the entire run and derive every authorization decision from it. The run context, never a tool argument (Where the Check Belongs).
- Enforce at the tool handler. Enforcement in the runtime alone breaks the moment a tool is invoked from a job, a test or another agent (Object-Level Authorization).
- Scope the credential, not just the query. Where the store supports per-user or per-tenant roles, use them so a missing filter fails closed (Tenant Isolation).
- Give the agent a permission set that is a subset of the user's, expressed as an explicit allowlist of tools rather than inherited implicitly (Role-Based Access Control).
- Gate irreversible actions on human approval, with the approving human recorded as a separate identity from the requesting one.
- Fail closed on ambiguity. If the agent cannot establish which user or tenant it is acting for, it must refuse rather than proceed with the widest scope available.
- Re-check permissions at execution time, not at plan time. A plan formed thirty seconds ago may reference an object whose access has since changed.
- Record the delegation chain in the audit entry: which human, which agent run, which tool, which credential (Agent Audit Logs).
What can go wrong
- Authorization enforced in the agent runtime and skipped by a background job that calls the same tool function directly.
- An on-behalf-of token that outlives the request and gets reused for later, unrelated work.
- Read tools scoped to the user and write tools left on the service account, because the write path was added later.
- A tenant filter applied in application code with a service credential behind it — one forgotten
WHEREclause from a cross-tenant leak. - Permission checks performed against the plan rather than the executed action, so a plan that was authorized executes something slightly different.
- An approval gate that shows the human a summary written by the model rather than the concrete action about to be taken.
- Cached tool results keyed without the user identity, serving one user's data to another (Cache-Aside).
- Permissions can change between planning and execution, so an authorized plan can execute an action that is no longer permitted — re-check at execution (Backend Races).
- An approval granted for one action can be consumed by a different action if the approval token is not bound tightly to the concrete operation.
- Concurrent agent runs for the same user can each pass an individual limit while jointly exceeding a policy meant to be aggregate.
- This is the highest-severity failure class in agent backends: privilege escalation and cross-tenant access, reachable through ordinary product usage.
- Prompt injection converts directly into unauthorized action at exactly the level of privilege the tools hold. Least privilege is the control that limits the damage, not injection detection (Least Privilege in the Security domain).
- Broad tool credentials mean any injection is a full breach; narrow, per-user credentials mean the same injection is limited to what that user could already do (Defence in Depth).
- Agent runs must be attributable to a human for incident response and compliance; a service-account-only trail is not an audit trail (Audit Logs for Privileged Actions in the Security domain).
- Where the agent can act asynchronously, on a schedule or from a webhook, decide explicitly whose authority it uses when no human is present (Inbound Webhooks).
- "It is our own code calling our own database, so authorization is internal." The initiator of the call is influenced by untrusted text. Internal is not the same as trusted.
- "The system prompt restricts what it can do." Prompts shape likelihood. Authorization decides outcomes (Authentication vs Authorization).
- "The user is authenticated, so the agent is authorized." Authentication says who is asking. It says nothing about which of a hundred tools they may invoke (Authentication vs Authorization).
- "We use least privilege — the service account only has access to our own database." Your own database contains every tenant. Least privilege means less than that.
- "Injection defence solves this." Detection is probabilistic; permissions are not. Assume injection succeeds and make the outcome survivable.
Operating it
- Authorization denials per tool and per user — an increase is either a broken prompt or an attempt to exceed permissions.
- Tool calls broken down by the identity actually used, so a service-account call on a user-facing path is visible immediately.
- Cross-tenant access attempts as a distinct counter, alerted on, because it should be zero.
- Approval gate outcomes: requested, approved, denied, timed out.
- The delegation chain on every audit record, so "who did this" is answerable in one query (Agent Audit Logs).
- With one tool and one team, per-handler checks are manageable. With dozens of tools across teams, a shared authorization middleware and a policy service become the only way to keep it consistent (The Middleware Pipeline).
- Multi-tenant deployments raise the stakes sharply: the same missing check is a bug in a single-tenant system and a breach in a multi-tenant one (Multi-Tenancy).
- Per-user database credentials do not scale to large connection counts; most systems end up with application-level scoping plus a narrow role, and must be honest that this depends on code correctness (Connection Pools).
- User-scoped permissions make the agent less capable than users expect: it cannot see across accounts to answer a question the user could not answer themselves.
- Per-user credentials give the strongest isolation and complicate pooling, caching and connection management.
- Approval gates add friction to exactly the actions people most want automated.
- A central policy service adds a dependency on the request path, with its own latency and failure modes (Calling Something You Do Not Control).
Where this applies
Backend advice is context-sensitive. These labels say what each claim is specific to, and where a different stack or scale would differ.
- GENERALThe confused-deputy structure is independent of model, framework and language.
- DATABASE-SPECIFICHow much can be enforced below the application differs: Postgres offers row-level security so a scoped role fails closed without an application filter; MySQL has no direct equivalent and pushes the check into application code or views; a document store or a third-party API may offer only coarse API keys, in which case scoping is entirely your code's responsibility.
- CLOUD-SPECIFICDelegated identity mechanisms differ by provider — token exchange, assumed roles with session tags, workload identity — and none of them map exactly onto another. Model the intersection you need first, then find the provider mechanism that expresses it.
Where the depth lives
This domain teaches the application-side mechanism and hands the rest off.
- — Distributed Systems — delegated identity across service boundaries, and why an on-behalf-of token needs a lifetime shorter than the work it authorises.