← Attack Database

Confused Deputy

Agent behaviour verified

Confused Deputy

Summary

The confused deputy is a classic access-control problem (Hardy, 1988) reborn for the age of MCP: a privileged program is tricked into using its authority on behalf of the wrong caller. In the agent context, an MCP server that holds broad credentials — a service-account PAT, an admin API key, a multi-tenant database connection — acts on requests without properly carrying user context from the agent through to the downstream system. One user’s agent ends up reading another user’s data; or an attacker’s agent ends up acting with the MCP server’s full privileges. The MCP specification’s June 2025 update explicitly added protections (RFC 8707 resource indicators, audience-bound tokens) because real deployments had already been exploited.

How it works

There are two flavours seen in production:

Flavour A — tenant confusion inside a shared MCP server. The MCP server holds one credential (or a pool of credentials) that can see multiple tenants’ data. User A’s agent requests their own data; the server fetches it and caches the result. User B’s agent makes an adjacent request; the server returns a cached response that belongs to User A. Tenant-isolation checks are incomplete or applied after caching.

Flavour B — OAuth token confusion across MCP servers. A user grants an OAuth access token to MCP Server X. Server X forwards the token (or a token with the same audience) to downstream Service Y. Server Y accepts the token because it wasn’t checking the aud claim. A malicious or compromised MCP server can now reuse tokens it received to impersonate the user against other services the token was never meant for. The classical mitigation — RFC 8707 resource indicators that bind a token to exactly one resource — is what MCP’s June 2025 spec update made mandatory.

A third variant — confused-deputy-via-dynamic-client-registration — lets an attacker register a client with an MCP proxy server, trick a user into authorising it (using the proxy’s static client ID that the user has seen before), and redirect the authorisation code to the attacker.

Real-world example

Asana MCP cross-tenant data exposure, June 2025. Asana launched its MCP server in May 2025. On 4 June 2025, Asana’s security team discovered a logic flaw in tenant isolation: the MCP server cached responses without re-verifying tenant context. Between 5 June and 17 June 2025, approximately 1,000 customer organisations could see project names, task descriptions, and metadata belonging to other tenants. Asana took the MCP feature offline for nearly two weeks, fixed the check, and reset all MCP connections forcing customers to re-authorise. Disclosed publicly 18 June 2025. (theregister.com, 18-06-2025; bleepingcomputer.com; upguard.com, accessed 19-04-2026.)

MCP authorization specification, 18 June 2025. The MCP specification shipped mandatory RFC 8707 support specifically to prevent confused-deputy token misuse. Clients MUST include a resource parameter identifying the target MCP server in authorisation and token requests; MCP servers MUST reject tokens whose audience doesn’t match. The specification text identifies confused deputy, token passthrough, and session hijacking as named threats. (modelcontextprotocol.io authorization spec; auth0.com breakdown, 2025, accessed 19-04-2026.)

Dynamic-client-registration abuse. A documented attack class, though no single named victim disclosure is on record as of April 2026. Descope and Stytch have published the threat model: a malicious client registers dynamically against a proxy MCP server, inherits user trust in the proxy’s static client ID, and captures the authorisation code on callback. (descope.com on MCP vulnerabilities; stytch.com DCR analysis, accessed 19-04-2026.)

Impact

  • Cross-tenant data exposure (the Asana failure mode).
  • Privilege escalation: a low-privilege user’s agent gains access to actions only the MCP server’s service account can perform.
  • Token theft and replay: access tokens captured by a malicious MCP server used against unrelated downstream services.
  • Regulatory exposure: GDPR, HIPAA, SOC 2 tenant-isolation controls violated.
  • Full account takeover in the DCR variant, because the attacker ends up with the victim’s OAuth access token.

Detection

  • MCP server logs where the acting user identity doesn’t match the user identity on the upstream request.
  • Token audiences (aud claim) that don’t match the resource being accessed.
  • Cache hits that serve one user’s request from another user’s data (detected via query + user-ID fingerprinting).
  • OAuth authorisation requests with mismatched or missing resource parameters.
  • DCR registrations from unexpected clients, especially ones with redirect URIs on new domains.

Prevention

The MCP specification’s audience-binding and resource-indicator requirements are the definitive fix for flavour B. Transport-layer policy enforcement adds defence-in-depth for flavour A (tenant confusion) by making the caller’s identity a first-class part of every policy decision.

Example Intercept policy enforcing per-user scope:

version: "1"
description: "Enforce user scope on a multi-tenant MCP server"
default: "allow"
tools:
  get_project:
    rules:
      - name: "project must belong to caller's tenant"
        conditions:
          - path: "args.tenant_id"
            op: "eq"
            value: "ctx.caller.tenant_id"
        on_deny: "Tenant mismatch — possible confused-deputy attempt"

  list_tasks:
    rules:
      - name: "reject calls without tenant context"
        conditions:
          - path: "args.tenant_id"
            op: "regex"
            value: "^[a-z0-9-]{6,64}$"
        on_deny: "Missing or malformed tenant_id"

      - name: "tenant must match caller"
        conditions:
          - path: "args.tenant_id"
            op: "eq"
            value: "ctx.caller.tenant_id"
        on_deny: "Tenant mismatch"

  "*":
    rules:
      - name: "reject anonymous calls"
        conditions:
          - path: "ctx.caller.user_id"
            op: "regex"
            value: "^.+$"
        on_deny: "No caller identity — MCP server must not act on anonymous requests"

Note: the ctx.caller.* paths and the eq comparison between two policy-context values are speculative relative to the shipped Intercept test policies, which exercise args.* and state.* paths. Treat as illustrative of intent; confirm against the Intercept engine before relying on.

Combine with:

  • RFC 8707 resource indicators in every OAuth flow (mandatory per MCP spec 2025-06-18).
  • Audience validation on every inbound token — reject tokens with missing or mismatched aud.
  • Per-tenant credentials at the MCP server; never a single service account spanning tenants.
  • User-consent screens for every dynamically-registered OAuth client, not just the first one.

Sources

  • Prompt Injection via Tool Results
  • Destructive Action Autonomy
  • Indirect Prompt Injection

Protect your agent in 30 seconds

Scans your MCP config and generates enforcement policies for every server.

npx -y @policylayer/intercept init
github.com/policylayer/intercept →
// GET IN TOUCH

Have a question or want to learn more? Send us a message.

Message sent.

We'll get back to you soon.