

The MCP confused deputy problem
TLDR: The MCP confused deputy problem is not necessarily a stolen credential or a broken login. It is a trusted intermediary using its legitimate authority for a request the initiating user was never entitled to make.
It was late on Saturday and I was supposed to be preparing for CRTO, but I still wanted to complete one MCP security lab. I worked through the first scenario in MCP Breach-to-Fix Labs, which focuses on authorization and tenant isolation.
The result was simple and uncomfortable: I used Alpha's valid API key to retrieve Bravo's confidential project. The MCP server authenticated the key correctly. It just never checked whether Alpha's tenant owned the project being requested.
What is a confused deputy?
AWS describes the classic confused deputy problem as a less-privileged entity coercing a more-privileged entity into performing an action. The deputy is trusted and has legitimate credentials. The failure is that it uses those credentials without checking whether the original requester was entitled to the specific resource.
An AI agent is an intermediary by design. It exists because it can reach systems that a conversation alone cannot. A support agent with read access across a CRM can answer customer questions, which is useful. That same broad identity can also read another customer's record if the downstream check trusts the agent too much.
Three properties make this especially difficult:
- The instructions are natural language, so the boundary between a genuine request and an injected command is blurry.
- The agent is trained to be cooperative and complete tasks.
- One agent can act across multiple systems, allowing one confused decision to move data between security domains.
The injected text does not need to break authentication. It only needs to influence which resource or tool argument the agent chooses.
Three different questions
It helps to separate three questions:
- Authentication: Who are you?
- Authorization: What are you allowed to do?
- Delegation and intent: Are you allowed to do this specific thing on behalf of this particular user?
Authentication and authorization can both succeed while the third question is never checked.
Imagine that Ryan is authenticated to the AI application, and the AI agent is authenticated to a CRM with a valid bearer token. The token has permission to read orders. Ryan owns Order #123, but a prompt injection or malicious request causes the agent to call get_order(999), where the order belongs to Alice.
The CRM may see:
Caller: AI agent
Token: valid
Permission: read_orders
Requested resource: Order #999
If the CRM only asks whether the agent can read orders, it returns the record. The important question was different:
Can the agent read Order #999 on behalf of Ryan?
Nothing failed cryptographically. The system authenticated the agent and checked its broad permission. It failed to bind the requested resource to the initiating user's authority.
Where MCP fits
In a conventional request, the application often knows which user is making the call and performs authorization before reaching the data layer. With an MCP architecture, the model selects a tool and supplies arguments through an intermediary server. The MCP server may then call an upstream API using its own identity.
The dangerous design looks like this:
Ryan: limited authority
|
v
AI agent: broad authority
|
v
CRM: trusts the agent without checking Ryan's resource ownership
The secure design carries the initiating identity or properly scoped delegated authority through the chain:
Ryan authenticated as Ryan
|
v
MCP server receives scoped user context
|
v
get_project(CRM-2001)
|
v
server checks: does CRM-2001 belong to Ryan's tenant?
|
v
403 Forbidden if it does not
The MCP authorization guidance covers a related proxy failure too: accepting a token intended for another resource or passing a client token through to a downstream API can create a confused deputy. The token must be intended for the MCP server, and the server must make its own authorized upstream request.
The lab attack
The lab exposes a fetch_project tool connected to a fake CRM. It accepts a project ID and an API key:
{
"project_id": "CRM-1001",
"api_key": "alpha-api-key"
}
The server checks whether the API key is valid, but it does not check whether that key's tenant owns the requested project.

I first called the tool with Alpha's own project, CRM-1001. The request succeeded, which established the normal behavior and confirmed that the tool was reachable.

The local CRM fixture contained two projects: Alpha's CRM-1001 and Bravo's confidential CRM-2001. The second project was deliberately there to test cross-tenant authorization.

I then changed only the project identifier and requested CRM-2001 using Alpha's key. The request succeeded and returned Bravo's confidential project, including the lab flag.

That is the moment the issue became clear. The model did not need to bypass login. The API key did not need to be stolen. The server trusted the deputy's broad authority more than it checked the relationship between the user, tenant, and requested object.
This resembles an insecure direct object reference or broken object-level authorization flaw in a web API. The difference is the route: the model and MCP tool make it easier for a requester, an injected instruction, or an automation workflow to select the object, while the server remains the component that should enforce ownership.
Scope, check, confirm
The defense can be remembered as three words.
Scope
Do not give the agent a permanent identity with access to every tenant. Prefer short-lived, task-scoped, resource-scoped credentials and narrow tool permissions. If a model is tricked into requesting Bravo's project, the credential should not be capable of retrieving it.
Check
Authorize every concrete tool call at the backend. Do not ask the LLM whether a request is allowed. Check the authenticated subject, tenant, resource owner, operation, and policy before executing the tool.
The OWASP MCP Security Cheat Sheet recommends binding sessions to user-specific context and checking on each request that the session or token belongs to the current requester. Microsoft's MCP security guidance makes the same architectural point: separate the server's execution identity from the caller's authorization and enforce per-caller checks.
Confirm
Require fresh approval for high-impact actions such as deleting data, changing permissions, sending an external message, transferring money, or deploying code. The approval should describe the exact action, resource, destination, and amount, not merely approve a broad category of capability.
Approval is a second boundary, not a replacement for authorization. The server must still reject a user who is not entitled to the resource.
Takeaways
- A valid token does not prove that the requester is entitled to the specific resource.
- MCP servers can become confused deputies when they use broad authority without enforcing the initiating user's scope.
- The model should never be the authorization boundary.
- Cross-tenant project access is structurally similar to BOLA/IDOR, even when the request arrives through an AI tool.
- Use the sequence: scope the authority, check every call, confirm dangerous actions.
- The secure retest is the important part: the same request for Bravo's project should return
403 Forbidden, while Alpha's legitimate request should continue to work.
Day 6 made the confused deputy problem much clearer than a definition alone. The AI did not need to be hacked, and the key did not need to be stolen. The system simply trusted the deputy more than it trusted the relationship between the user and the resource.
Filed under ai, mcp, authorization, confused-deputy, multi-tenant-security. If any of this is wrong, or you have hit the same thing, tell me.
Published 20 September 2026.