DEV Community

Shieldly
Shieldly

Posted on • Originally published at shieldly.io

AWS STS ExternalId and the Confused Deputy Problem: A Practical Guide

Originally published at shieldly.io/blog.

Cross-account IAM roles are the standard mechanism for granting third-party services access to your AWS account. The problem is that naming an entire AWS account as the trusted principal is not specific enough. Any identity authenticating from that account can call sts:AssumeRole on your role. This is the confused deputy problem, and ExternalId is the condition that closes it.

What the Confused Deputy Problem Is

The confused deputy is a class of vulnerability where a privileged program is tricked by a less-privileged caller into performing actions on the caller's behalf.

In AWS: a SaaS provider has a trusted AWS account. Customers create cross-account roles naming the SaaS provider's account as the trusted principal:

{
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::VENDOR-ACCOUNT:root"
  },
  "Action": "sts:AssumeRole"
}
Enter fullscreen mode Exit fullscreen mode

Any identity in the vendor's account can call sts:AssumeRole on any customer's role — because from AWS's perspective, the call originates from the correct trusted account. Customer A can potentially assume Customer B's role if they know B's role ARN. The SaaS provider is the confused deputy.

How ExternalId Fixes It

When a vendor requires ExternalId as a condition on sts:AssumeRole, they generate a unique secret value per customer and store it. When their service assumes your role, it passes that value:

{
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::VENDOR-ACCOUNT:root"
  },
  "Action": "sts:AssumeRole",
  "Condition": {
    "StringEquals": {
      "sts:ExternalId": "a7f3b2c9-unique-per-customer"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

A different customer cannot assume your role because they do not know your ExternalId — even if they use the same vendor account.

Implementing It Correctly

The ExternalId must be:

  • Unique per customer-vendor relationship — not shared across customers
  • Unguessable — a UUID or similar high-entropy value, not a predictable string like a customer name or account ID
  • Generated by the vendor — not chosen by the customer (a customer-chosen ID is potentially guessable by other customers of the same vendor)

Also verify that you cannot pass an arbitrary ExternalId to the vendor's service. If the vendor accepts any value you provide, the control is ineffective.

Red Flags in Third-Party Integration Docs

Watch for these in vendor setup instructions:

  • No mention of ExternalId
  • Instructions to trust arn:aws:iam::VENDOR-ACCOUNT:root with no condition
  • A fixed, static ExternalId shared across all customers (defeats the purpose)

Catch confused deputy risks automatically — paste a cross-account trust policy into Shieldly's free AI-Powered analysis. No signup, no credit card.

Launch offer: code 90Off2M — 90% off first 2 months. Builder from $1.90/mo. shieldly.io/pricing

Top comments (0)