Agent securityMiddleLesson 144 min read

The confused deputy

Your agent has powers your user does not. An attacker who cannot open a door themselves simply asks the agent to open it for them.

Lesson in motion

In 60 seconds

The confused deputy

Your agent has powers your user does not. An attacker who cannot open a door themselves simply asks the agent to open it for them.

1/7
In simple words
A guard has the keys to every room. A stranger cannot get in — so the stranger asks the guard, very politely, to fetch something from a room the stranger is not allowed in. The guard is not bad. The guard is confused about who they are helping.
A confused deputy is a program with more authority than the person asking it to act, which gets tricked into using that authority on their behalf. The idea is from 1988. Agents have made it fashionable again.
Attackerno access at alla requestYour agentholds admin rightsacts with ITS rightsPrivate systemopens the doorthe attacker never had permission for this paththe system checks the agent's rights, not the requester'sso every check passes, and the log looks perfectly clean
Nothing here is broken. Every permission check passes, because the system is checking the wrong identity.

How it shows up in practice

  1. 1

    Multi-tenant leakage

    One agent serves every customer with one database connection. Customer A asks about "my recent orders" in a way that widens the query, and sees customer B's. The database was asked politely and answered honestly.
  2. 2

    Privilege ladder

    A support agent can reset passwords because that is a support job. A user talks it into resetting an admin's password. The tool worked exactly as specified.
  3. 3

    Internal network reach

    Your agent runs inside the network and can reach internal services. An injected instruction has it fetch http://internal-admin/ and summarise the page. That is server-side request forgery, performed by your own agent.
  4. 4

    Shared cache poisoning

    An agent caches results to be fast. One user's poisoned result gets served to the next user. The attack now spreads by itself.

The fix: carry the user's identity, not the agent's

Wrong
  • One service account with wide rights.
  • The agent decides which user it is acting for.
  • Permission checks happen in the prompt.
  • Logs show "agent did X" with no user attached.
Right
  • Per-request token scoped to the actual end user.
  • The runtime pins the user identity; the model cannot change it.
  • Permission checks happen in the database and API layer.
  • Logs show "agent did X on behalf of user 4471".
Danger
If the model can put a user ID into a tool argument, the model can put in a different user ID. Identity must be attached by your code, outside the model's reach, before the tool ever runs.
Do this
One line to remember: the agent should never be able to do something the requesting user could not do themselves. Anywhere that is not true, you have a confused deputy waiting to happen.

Watch and read more

Lab

A confused-deputy exploit against your own multi-tenant agent, then the fix.

~20 min

The problem

Build an agent serving two customers from one database connection. As customer A, retrieve some of customer B's data without any explicit exploit — just by phrasing a request that widens the query. Then re-implement with a per-request scoped token and prove the same phrasing fails.
Starter codepython
# WRONG: one connection, model chooses the filter
def search_orders(query, customer_id=None):
    sql = "SELECT * FROM orders"
    if customer_id:
        sql += f" WHERE customer_id = '{customer_id}'"
    return db.execute(sql)

# RIGHT: identity pinned by the runtime, invisible to the model
def search_orders_scoped(query, *, caller_id):
    return db.execute(
        "SELECT * FROM orders WHERE customer_id = %s AND description ILIKE %s",
        (caller_id, f"%{query}%"),
    )

You are done when

Hard questions

Try to answer before you reveal. If you can answer these, you understood the lesson.

Q1Why must customer_id be a keyword-only runtime argument rather than a tool parameter the model fills?Reveal
Anything the model can fill, the model can fill differently — and an injected model will. Making identity a runtime-injected argument the model never sees moves authorisation outside its reach entirely. It is the same principle as parameterised queries: remove the ambiguity structurally rather than instructing against it.
Q2Your agent runs inside the VPC and has a fetch tool. Name the attack and the two controls that matter.Reveal
Server-side request forgery, performed by your own agent. An injected instruction has it fetch http://169.254.169.254/ (cloud metadata, often containing role credentials) or an internal admin service, and summarise the result. Controls: block link-local and private ranges at the network layer, not with a URL check in the tool — and require IMDSv2 or remove instance credentials entirely. A regex on the URL is defeated by a redirect or a DNS name that resolves internally.

Please sign in to continue.

Questions people ask

Isn't this just an access-control bug?

Yes — that is exactly what it is, and that is good news. It is an old, well-understood class of bug with well-understood fixes. Agents simply make it far easier to create by accident, because the natural way to build one is with a single powerful service account.

How do I pass user identity through a chain of agents?

Propagate a scoped token with every internal call and check it at every boundary, the same way a well-built microservice architecture does. Never let an internal hop upgrade privileges just because it is internal.

Is SSRF really a concern for agents?

Very much so. An agent with a fetch tool that runs inside your network is a request-forgery engine. Block internal address ranges and cloud metadata endpoints at the network layer, not with a URL check in the tool.

What about agents that legitimately need admin rights?

Then they should not also read untrusted content. Split the system: an admin-capable agent that only ever receives structured, validated input from your own code, and a separate untrusted-content reader with no rights at all.

Lesson test

5 questions. Get 3 right (60%) to pass and complete this lesson.

Sign in with your phone number to take the test and save your progress