Skip to main content
← back to blog

The Confused Deputy Problem, Explained

A 1988 security paper predicted why AI agents are vulnerable. The standard fix is incomplete.

security confused-deputy capabilities authorization technical

The confused deputy is the fundamental failure mode of authorization systems. First documented in 1988, it explains why identity-based security breaks down in delegated workflows—and why the standard fix doesn’t go far enough.

The setup is deceptively simple: A deputy has authority the requestor lacks. The requestor’s input causes the deputy to exercise that authority on their behalf.

The failure is structural: The deputy cannot distinguish between its own authority and the authority it should exercise for this specific transaction.

The Original Confused Deputy

The classic example comes from Norm Hardy’s 1988 paper. A Fortran compiler called FORT was installed in a privileged system directory called SYSX. Because of its location, FORT had write access to all files in that directory—including BILL, the system billing file.

FORT accepted a command-line parameter specifying where to write debug output. A clever user provided (SYSX)BILL as the output path.

The compiler obliged. It opened BILL using its own write authority and overwrote the billing records with debug output.

The user never had permission to write BILL. But the compiler did. And the compiler couldn’t distinguish between:

  • “Write debug output to a file I should be able to write” (what happened)
  • “Write debug output to a file the user should be able to write” (what was intended)
flowchart LR
    subgraph SYSX["SYSX (privileged directory)"]
        FORT["FORT (compiler)"]
        BILL["BILL (billing)"]
        STAT["STAT (statistics)"]
    end

    User -->|"compile foo.f,<br/>output=(SYSX)BILL"| FORT
    FORT -.->|"writes to<br/>(using its own authority)"| BILL

    User -.-x|"NO direct access"| BILL

The mismatch: User has no write access to BILL. FORT does (ambient authority from its location). The system cannot distinguish “write to a file the user should access” from “write to a file FORT can access.”

This is the confused deputy. The compiler (deputy) has authority the user lacks. The user’s transaction causes the compiler to exercise that authority. The system has no mechanism to detect the mismatch.

The Standard Solution: Capabilities

The capability security community developed an elegant answer: eliminate ambient authority.

In the original system, FORT had write access to SYSX “because of where it lived.” This is ambient authority—authority derived from identity, role, or location rather than explicit grant.

In a capability system, FORT has no ambient authority at all. Instead, the user must explicitly pass capabilities for every resource the compiler should access:

invoke FORT with:
  - capability: source_file (read)
  - capability: output_file (write)  <- user must possess this

If the user wants debug output written to BILL, they must pass a write capability for BILL. They don’t have one. The attack fails.

flowchart LR
    subgraph UserCaps["User's Capabilities"]
        cap_source["cap: source.f (read)"]
        cap_debug["cap: debug.txt (write)"]
        cap_bill["cap: BILL? ❌ NONE"]
    end

    User -->|"compile + capabilities"| FORT
    FORT -->|"writes to"| debug["debug.txt ✓"]
    FORT -.-x|"no capability"| BILL["BILL ✗"]

FORT can only write to files the user provides capabilities for. User can’t pass a capability they don’t have.

This is a genuine improvement. Capabilities ensure authority comes only from explicit possession, not from identity or location. The compiler can only write to files the user explicitly authorized.

Problem solved?

The Insufficiency of Capabilities

Not quite. Capabilities eliminate authority-from-identity, but they introduce a subtler problem: deputies accumulate capabilities over time.

Consider a government tax discount program. Citizens earning below a threshold qualify for reduced taxes. Two offices handle the workflow:

Public Records Office: Maintains income records. Given an ID key and an Accounting key, an officer retrieves and attests a citizen’s income.

Tax Office: Grants discounts based on attested income documents.

This looks like a capability system. Officers don’t have ambient access to all records—they need the citizen’s Accounting key (a capability) to access that citizen’s specific records.

But the Public Records Office also serves police investigations. For those cases, officers receive a superuser Accounting key—a capability granting access to all records.

Now consider an officer who serves both functions.

John arrives to request his income attestation. He provides his ID key and his Accounting key. The officer now possesses:

  1. John’s Accounting key (just received, for this transaction)
  2. The superuser key (received last week, for a police investigation)

Both are valid capabilities. The officer possesses both legitimately. Nothing in the capability model prevents the officer from:

  1. Using the superuser key to retrieve a different citizen’s income
  2. Attesting that lower income as John’s
  3. John receives a fraudulent tax discount

This is still the confused deputy. The officer is the deputy, possessing multiple capabilities, exercising the wrong one in response to John’s transaction.

flowchart TB
    subgraph Transaction["John's Transaction"]
        John["John arrives with:<br/>• ID key<br/>• Accounting key"]
    end

    subgraph Officer["Officer's Capability Space"]
        key1["1. John's Accounting Key<br/>(just received)"]
        key2["2. Superuser Key<br/>(from police investigation)"]
    end

    John -->|"for this transaction"| Officer

    Officer -->|"LEGITIMATE:<br/>Use John's key"| path1["John's records<br/>Income: $80k"]
    Officer -->|"ATTACK:<br/>Use superuser key"| path2["Other records<br/>Income: $30k"]

    path1 --> result1["Tax Office:<br/>No discount ✓"]
    path2 --> result2["Tax Office:<br/>Fraudulent discount ✗"]

    style path2 stroke:#f66,stroke-width:2px
    style result2 stroke:#f66,stroke-width:2px

Both paths use valid capabilities. The system cannot distinguish them.

Why Didn’t Capabilities Help?

Capabilities eliminated the compiler’s ambient authority. Why didn’t they eliminate the officer’s confused deputy vulnerability?

Because in any real system, deputies accumulate capabilities over their lifetime. The officer legitimately received the superuser key for legitimate purposes. That capability now exists in their “capability space.” When processing John’s transaction, they have access to both.

The capability model guarantees:

  • You can only exercise authority you possess
  • You can only possess authority that was explicitly granted

It does not guarantee:

  • You will exercise the right authority for this transaction
SystemAuthority SourceConfused Deputy?
Identity-basedRole, location, namingYes—compiler has SYSX access from location
Capability-basedExplicit possessionStill possible—officer has multiple capabilities

Capabilities shrink the attack surface. The officer can only access records they have capabilities for, not “all records because they’re an officer.” But within their accumulated capability space, confusion remains possible.

The Missing Piece: Designation

The confused deputy arises because the deputy chooses which authority to exercise. The compiler chose to use its SYSX authority. The officer chose to use the superuser key.

The structural fix removes the choice.

When John initiates his transaction, the system should create a binding:

Transaction T:
  Requester: John
  Designated capability: John's Accounting key
  Permitted operations: retrieve and attest John's income

The officer doesn’t select which capability to use. The transaction designates it. A verification layer—outside the deputy’s control—enforces the binding before any action executes.

If the officer attempts to use the superuser key for John’s transaction, verification fails. Not because the system detects fraud, but because the action doesn’t match the transaction’s designation.

flowchart TB
    John["John initiates transaction"] --> Verifier["Verification Layer<br/>(outside officer control)"]

    Verifier -->|"Creates Transaction T:<br/>• Requester: John<br/>• Capability: John's key<br/>• Scope: John's records"| Officer["Officer<br/>(still has both keys)"]

    Officer --> action1["Action: Use John's key<br/>for John's records"]
    Officer --> action2["Action: Use superuser key<br/>for other records"]

    action1 --> check1["Verifier checks:<br/>Key matches? ✓<br/>Scope matches? ✓"]
    action2 --> check2["Verifier checks:<br/>Key matches? ✗<br/>Scope matches? ✗"]

    check1 --> allowed["✓ ALLOWED"]
    check2 --> rejected["✗ REJECTED"]

    style allowed stroke:#090,stroke-width:2px
    style rejected stroke:#f66,stroke-width:2px

The officer’s capability space is irrelevant. Only the transaction’s designation matters.

This is the difference between two questions:

QuestionWhat It ValidatesConfused Deputy?
”What capabilities do you possess?”The deputyStill possible—deputy has multiple
”Which capability is designated for this transaction?”The transactionNo—binding is external to the deputy

The first question is Proof of Possession. The second is Proof of Continuity.

flowchart TB
    subgraph possession["PROOF OF POSSESSION"]
        direction TB
        q1["'What do you have?'"]
        deputy["Deputy<br/>• cap A<br/>• cap B ← which one?<br/>• cap C"]
        choose["Deputy CHOOSES<br/>which to use"]
        result1["Confused deputy<br/>POSSIBLE ⚠️"]

        q1 --> deputy --> choose --> result1
    end

    subgraph continuity["PROOF OF CONTINUITY"]
        direction TB
        q2["'What were you given for THIS?'"]
        txn["Transaction<br/>• designated: cap A<br/>• for: resource X<br/>• by: requester R"]
        verify["Verifier CHECKS<br/>action matches"]
        result2["Confused deputy<br/>IMPOSSIBLE ✓"]

        q2 --> txn --> verify --> result2
    end

    style result1 stroke:#f90,stroke-width:2px
    style result2 stroke:#090,stroke-width:2px

From Possession to Continuity

Proof of Possession asks: Do you have a valid capability?

The officer does. Multiple, in fact.

Proof of Continuity asks: Are you the designated executor of this specific transaction, operating with the specific capability that was bound when the transaction began?

This question can only be answered by examining the transaction itself, not the deputy’s capability space. The transaction carries:

  • Origin: Who initiated this workflow?
  • Designation: Which capability applies?
  • Constraints: What limits were established?
  • Continuation: Is this deputy the legitimate next step?

A trusted verifier checks these properties before the deputy acts. The deputy’s accumulated capabilities are irrelevant—only the designated capability matters.

Why This Matters for Agents

Human deputies accumulate authority gradually. An officer receives the superuser key, uses it, perhaps retains access. Organizational controls—supervision, audit trails, separation of duties—mitigate the risk.

AI agents operate differently:

Speed. An agent might receive and exercise dozens of capabilities per second. Human oversight cannot intervene in time.

Scale. Thousands of concurrent workflows, each with different capability contexts. Manual audit is impossible.

Delegation. Agent A spawns Agent B spawns Agent C, each receiving capabilities. Every hop is an opportunity for confused deputy failures.

Persistence. Agents may cache or retain capabilities across invocations. The capability space grows continuously.

When a refund agent has been granted capabilities for customer credentials, administrative overrides, database access, and payment processing—across hundreds of different customer workflows—the confused deputy isn’t an edge case. It’s the default architecture.

The solution is the same as for human deputies, but enforcement must be structural rather than organizational:

  1. Bind capabilities to transactions, not agents
  2. Make the binding travel with the workflow as it crosses boundaries
  3. Verify before execution that actions match their designated authorization

Capabilities alone don’t prevent confused deputy. Capabilities plus designation—Proof of Continuity—does.


References