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"
}
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"
}
}
}
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:rootwith no condition - A fixed, static
ExternalIdshared 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)