Practical LLM Security · Multi-Agent Security · Part 3 of 5
Diagnose how locally correct agents compose into unauthorized actions, using five concrete delegation traces and a permission-matrix test harness.
When every agent is right and the system is wrong
A confused deputy is a program with authority that is tricked into using it for someone who lacks that authority. The site's confused deputy guide explains authority versus designation and the intersection rule for a single agent. Multi-agent systems add a harder problem: each agent can apply a reasonable local check while the chain as a whole performs an action that no participant intended to allow.
This chapter is diagnostic. It presents five traces from a fictional Northstar deployment with three agents: a concierge facing customers, a research agent that reads documents and reviews, and an operations agent with tools for refunds, exports, and account changes. Each trace names the deputy, the designation that was smuggled, and the control that stops it.
Trace 1 and 2: ambient authority and designation through summaries
Trace 1. The operations agent calls the refund API with its own service account, which may refund any order. The concierge asks it to refund order o-91, which belongs to another customer, because a crafted chat message mentioned that number. The operations agent checks that the request came from the concierge, a trusted internal agent, and proceeds. The deputy is the operations agent; its authority is ambient, meaning it does not depend on who asked. The fix is to make the resource server authorize the end user's relationship to o-91, using a delegated credential as described in the delegated identity chapter.
Trace 2. The research agent summarizes a support thread and writes "customer requests export of account a-55 data". The concierge passes the summary to the operations agent, which exports a-55. The customer's real account is a-12. The designation, which account, came from text produced by an agent that read untrusted content. Each hop treated the previous hop's text as a statement of fact. The fix is that designations used for actions must come from authenticated state, such as the session's account binding, or be confirmed by the user, never parsed from intermediate prose.
Trace 3, 4 and 5: forwarded tokens, shared memory, and approval reuse
Trace 3. The concierge forwards the customer's token to the research agent, which also calls a document service. The document service accepts any token signed by the corporate issuer, so the research agent can read documents the customer could read, plus internal notes, because the document service grants internal notes to any token that contains a staff group. That group appeared in the token because the same issuer serves staff and customers and the mapping was misconfigured. The failure is audience: the token was not issued for the document service. Validate audience and use exchange per hop.
Trace 4. Agents share a memory store. The research agent writes "operations approved bulk exports for this tenant" after reading a spoofed email. Later the operations agent consults memory as policy. The shared store became a deputy that converted untrusted text into authority. Authority must come from a policy service with authenticated writes, not from memory.
Trace 5. A customer approves "refund 40 units for order o-77". The approval token is stored in task state. A later step, influenced by a review, refunds order o-78 and reuses the stored approval. The approval was not bound to the exact action. Bind approvals to a digest of operation, resource, amount, and destination, as the ADK confirmation chapter describes.
Architecture: designation provenance and end-to-end authorization
| Trace | Deputy | Smuggled designation | Stopping control |
|---|---|---|---|
| 1 Ambient authority | Operations agent | Order number in chat | Resource server checks the end user |
| 2 Summary designation | Operations agent | Account id in summary | Designations from session state or confirmation |
| 3 Forwarded token | Document service | Token for another audience | Audience validation and per-hop exchange |
| 4 Shared memory | Memory store | Claimed approval | Policy service with authenticated writes |
| 5 Approval reuse | Task state | Different order id | Approval bound to action digest |
The traces share a structure. A value that decides which resource is affected, the designation, travels through a component that cannot vouch for it, and the component that finally acts has authority that does not depend on the original requester. Two controls address the structure rather than each symptom. First, record designation provenance: every argument that selects a resource carries a source label such as session, user_confirmed, tool_result, or model_text. Action tools reject resource-selecting arguments whose provenance is model_text or untrusted tool output unless the user confirms them. Second, authorize at the resource with the end user's identity, so the final service asks whether this user may affect this resource, not merely whether the caller is a known agent.
Implementation: a provenance-checked action argument
Illustrative python; adapt the persistence and identity adapters to your service.
from dataclasses import dataclass
TRUSTED_SOURCES = {"session", "user_confirmed"}
@dataclass(frozen=True)
class Designation:
resource_id: str
source: str
def authorize_action(principal, action, target: Designation, policy, approvals):
if target.source not in TRUSTED_SOURCES:
return "needs_confirmation"
if not policy.owns(principal.subject, target.resource_id):
return "denied_not_owner"
if action.requires_approval:
digest = action.digest(target.resource_id)
if not approvals.consume(principal.subject, digest):
return "denied_no_matching_approval"
return "allowed"In ADK, this check fits naturally in a before_tool_callback, which receives the tool, its args, and the tool context, and can skip execution by returning a dictionary. A plugin version of the same hook receives the arguments as tool_args, so do not copy agent callback code into a plugin without adjusting the signature. The callback is a convenience layer; the resource service must repeat the ownership and approval checks, because a direct call that bypasses the agent framework would otherwise bypass them too.
A permission-matrix test harness
Unit tests that cover the happy path rarely reveal confused deputies, because the bug lives in combinations. Generate a matrix instead. Axes: requesting principal, designated resource owner, designation source, path through the agents, and whether an approval exists for the exact action. For each cell, compute the expected outcome from a simple reference policy and run the real chain with synthetic data and fake tools that count effects. Any cell where the effect counter increments and the reference policy says deny is a confused deputy.
For three principals, three resources, four designation sources, three paths, and two approval states, the matrix has 216 cells, small enough to run on every change. Include faults such as a timeout from the policy service, which must produce a denial rather than an allow.
Tradeoffs and worked case
Requiring confirmation for model-originated designations adds user friction. Reduce it by binding the common case at session start, such as the customer's own account, so most actions designate resources from session state automatically. End-user authorization at every resource requires APIs that understand delegated identity; where legacy services cannot, put a gateway in front of them. Provenance labels must be set by code, not by the model, or they become another field to spoof.
Apply the matrix to the original Northstar chain on paper first. Every cell whose designation source is model_text and whose resource belongs to another customer should be a denial; in the original design, each of those cells reaches the operations agent with ambient authority, so each is a predicted confused deputy. Cells that combine a forwarded token with the document service expose trace 3, and cells with a stored approval and a different order expose trace 5. After adding provenance-checked designations, audience validation, and action-bound approvals, the reference policy and the implementation should agree on every cell, and every allow cell must still succeed, or the fix has simply broken the product. Your own executed matrix is the evidence that matters; this chapter does not report measured results. The delegation boundaries lab lets you reproduce several traces interactively with deterministic fixtures.
Practice and continue
Open the Delegation boundaries: trace privileges across three agents lab to change the assumptions with deterministic synthetic data and inspect each decision. This chapter belongs to the Multi-Agent Security group of Practical LLM Security, which builds on the 25-chapter abuse and fraud prevention series.
Sources and implementation scope
Primary references checked October 16, 2026. Framework details follow the linked documentation and, for ADK, the google-adk 2.12.0 package source; pin and test the version your application uses. The Northstar scenarios, policies, figures, and code are original illustrative designs, not measured production results or complete integrations.