A service can enforce authentication correctly, hold only legitimate credentials, and still perform an action that the requester was never allowed to cause. This happens when a privileged component uses its own authority without preserving enough information about who asked for the action and what that requester may do.

This class of mistake is called the confused deputy problem. The deputy is a component that has legitimate access to a resource. It becomes confused when it cannot distinguish an authorized use of that access from a request that merely causes it to exercise its privilege on someone else’s behalf.

The consequence can be unauthorized reads, writes, or administrative actions even though no credential was stolen. This article develops a practical mental model for recognizing that failure and designing service boundaries that keep authority explicit.

Start with three actors, not two

Authorization diagrams often show a requester and a resource:

requester -> resource

A confused deputy problem appears when an intermediary has more privilege than the requester:

requester -> deputy -> privileged resource

Suppose a report service can read every customer’s billing record because it needs that access to generate reports. A user asks the service to generate a report for a customer identifier supplied in the request.

The important question is not whether the report service may read that billing record. It can.

The important question is whether this requester may cause the report service to read this billing record for this purpose.

If the service checks only its own database permission, it answers the wrong authorization question.

That distinction is the core mental model: possessing authority and being authorized to exercise it on behalf of a particular requester are different facts.

State the threat model

Confused deputy defenses reduce the risk that a less-privileged caller can induce a more-privileged component to perform an action outside the caller’s permitted authority.

The failure does not require the caller to compromise the deputy. The deputy may behave exactly as implemented. The weakness is that its interface loses, ignores, or incorrectly reconstructs the authorization context needed to decide whether the requested action is legitimate.

This control does not protect against every privileged-service compromise. If an attacker gains arbitrary execution inside the deputy or steals credentials that independently authorize the target action, preserving caller context alone is insufficient. Those threats need complementary controls such as credential protection, isolation, monitoring, and least privilege.

Follow authority through the request

Consider a document conversion service. It receives a source document identifier, reads the document from protected storage, converts it, and writes the result.

A risky design looks like this:

POST /convert
{
  "document_id": "doc-4821"
}

conversion service:
  authenticate caller
  load document_id using service credential
  convert document
  return result

Authentication establishes who sent the request, but the service never uses that identity when deciding whether doc-4821 may be read. The storage system sees a credential belonging to the conversion service, so its access check succeeds.

A safer design makes the authorization decision explicit:

conversion service:
  authenticate caller
  determine caller identity and relevant authorization context
  verify caller may convert document_id
  load document using only the authority required for the approved action
  convert document
  return result

The example is intentionally abstract. Production systems differ in how they represent identity and authorization, but the invariant is portable: a privileged operation performed for a caller must be tied to an authorization decision about that caller and that operation.

Do not treat service identity as user authority

Service-to-service authentication answers a useful question: which workload is calling?

It does not automatically answer another question: which end user, tenant, job, or delegated principal is this workload acting for?

This matters in systems where one backend calls another. Imagine this path:

user -> API -> export service -> data store

The export service may correctly authenticate the API as an approved workload. If every request from that API is then treated as equally authorized, user-level restrictions can disappear at the service boundary.

A developer should decide which authority the downstream operation requires. Sometimes service identity alone is appropriate, such as a scheduled maintenance job whose permission is intentionally assigned to that workload. Sometimes the action is delegated from a user and the downstream decision must preserve that fact.

Do not invent user authority from a trusted network location, a service name, or the fact that an upstream component authenticated successfully. Carry or reconstruct only authorization context that the receiving component can validate and is designed to trust.

Bind requests to the resource and operation

Authorization context becomes dangerous when it is too broad or can be detached from the action it was intended to approve.

Suppose an upstream service determines that a user may download one invoice and sends a generic flag downstream:

approved = true
invoice_id = request.invoice_id

If the downstream service trusts approved without knowing what was approved, a programming mistake can pair that approval with a different invoice identifier.

A stronger design binds the decision to meaningful attributes, conceptually:

principal: user-17
action: read-invoice
resource: invoice-204
tenant: tenant-8

The exact representation depends on the architecture. The security property is that authorization for one resource or action should not silently become authorization for another.

For especially sensitive delegated operations, short-lived, narrowly scoped authorization artifacts can reduce how far an approval can be reused. They add protocol and lifecycle complexity, so they are not necessary for every internal call. A direct server-side authorization check may be simpler and stronger when the service can reliably query the authoritative policy source.

Keep trust boundaries visible

A common design mistake is to validate authorization at the public edge and assume every internal component may trust the resulting request forever.

Internal services still cross trust boundaries. Data can be transformed, queued, retried, cached, or combined with values from other sources. Each component that performs a security-sensitive action needs enough trustworthy context to justify that action.

This does not mean repeating every authentication step at every function call. It means placing authorization checks where privileged effects occur and making the inputs to those checks explicit.

For a background job, for example, record the immutable identifiers needed to describe the approved work rather than storing a vague instruction such as “process whatever resource this account currently selects.” When the job runs later, its authorization model should account for whether permissions can change between request time and execution time.

If revocation must take effect immediately, re-check current authorization before the privileged action. If the business rule intentionally grants a durable capability at submission time, model and protect that capability explicitly instead of accidentally obtaining durable access through a queued identifier.

Reduce the deputy’s ambient authority

Correct authorization checks are important, but limiting the deputy’s own privilege reduces the impact of mistakes.

Ambient authority means authority that is available to code without being explicitly tied to the operation that needs it. A service credential that can read every tenant’s data is an example when most requests need access to only one tenant or object.

Where the platform supports it, narrower credentials, tenant-specific roles, scoped access tokens, or delegated capabilities can make the resource layer enforce part of the intended boundary. Then a missed application check is less likely to expose the full privilege of the service.

There is a trade-off. Fine-grained credentials create issuance, caching, rotation, observability, and failure-handling work. For a small system, one service identity plus a clear, centralized authorization check may be easier to reason about than a complex delegation scheme.

Use additional credential boundaries when the potential impact justifies the operational complexity, not because more tokens automatically mean better security.

Avoid authorization context supplied by the caller

A field becomes trustworthy because of how it was established, not because its name sounds authoritative.

These values should not become authorization facts merely because a client sends them:

is_admin=true
tenant_id=tenant-8
acting_as=user-17
approved=true

The application may accept a tenant or resource identifier as a request parameter, but it must independently establish whether the authenticated principal may act on that value.

Likewise, if an upstream service forwards identity or authorization metadata, the downstream service needs a trustworthy way to distinguish metadata produced by the approved upstream component from arbitrary caller input. The appropriate mechanism depends on the system: authenticated service channels, server-controlled headers, signed assertions, or direct policy lookup are possible approaches.

The general rule is simpler than any mechanism: never let untrusted request data promote itself into trusted authorization context.

Test the negative paths

A happy-path test proves that an authorized request works. It does not prove that the deputy refuses to misuse its privilege.

For each privileged operation performed on behalf of another principal, test at least the important mismatches:

  • an authenticated caller requests another tenant’s resource;
  • a caller authorized for one object substitutes a different object identifier;
  • a caller authorized for one action requests a stronger action;
  • forwarded authorization context is missing, malformed, expired, or inconsistent with the resource;
  • current permission is revoked before delayed work executes, when the design requires a fresh check.

These are defensive tests. Their purpose is to verify that the service’s own powerful credential cannot accidentally substitute for caller authorization.

Also verify observability. Security-relevant logs should make it possible to distinguish the initiating principal, the deputy, the target resource, the requested action, and the authorization result without recording secrets or unnecessary sensitive data.

Know what a correct design guarantees

Under the assumption that authentication and the policy source are trustworthy, an explicit-authority design can reduce the chance that a privileged intermediary performs an operation merely because it possesses the technical ability to do so.

It does not guarantee that the policy itself is correct. A rule that grants the wrong users access will still authorize the wrong users. It also does not solve credential theft, compromised workloads, insecure storage, or missing validation of unrelated input.

The practical goal is narrower: make every privileged action answer the right question about authority.

When reviewing a service, ask:

Whose authority allows this specific action on this specific resource?

If the answer is only “the service account can do it,” inspect the design more closely. A deputy often needs broad technical access to do useful work, but each use of that access should still be justified by the principal, operation, resource, and context that caused it.

Conclusion

A confused deputy problem is an authorization failure, not necessarily an authentication failure. It appears when a component with legitimate privilege acts for a less-privileged caller without preserving the distinction between its own authority and the caller’s authority.

Keep that distinction explicit. Authenticate the actors that matter, bind authorization decisions to the intended action and resource, preserve trustworthy delegation context across service boundaries, reject caller-supplied claims that have not been independently established, and reduce ambient privilege where the added complexity is justified.

The most useful review question is simple: who is allowed to cause this privileged effect? Designing the system so it can answer that question precisely makes confused deputy failures much easier to prevent and test.