trueno blog
AWS security

AWS External ID Explained: The Confused Deputy Problem in Plain English

If you have ever connected a third-party platform to your AWS account, you have probably seen a setup step that asks you to add an External ID to an IAM role trust policy.

Most people copy it, paste it, and move on.

That is understandable. The AWS setup works, the connection verifies, and the dashboard starts loading data.

But External ID is not a random setup value. It exists for a specific security reason, and it matters most when you are granting access to a SaaS product that serves many AWS customers.

If a cloud security, cost, or posture management tool asks you to create a cross-account IAM role, you should understand what the External ID does before you connect production AWS accounts.

The basic setup: cross-account IAM role access

The safer way to connect a third-party AWS tool is usually through a cross-account IAM role.

Instead of creating an IAM user, generating access keys, and pasting those keys into a vendor product, you create an IAM role inside your AWS account. That role has a trust policy that allows a specific AWS principal from the vendor to assume it.

When the tool needs to scan your account, it calls AWS STS AssumeRole.

If AWS accepts the request, STS returns temporary credentials. Those credentials expire automatically.

This is much better than handing out long-lived access keys because the customer keeps control of the role, the permissions, and the trust relationship.

If you want to revoke access, you update or delete the role. You do not need to hunt down an access key that may have been copied into logs, scripts, CI variables, or a vendor database.

For AWS security tools, this matters. A product that is supposed to reduce cloud risk should not ask you to create a permanent secret as the first step.

Where External ID fits

AssumeRole is the access mechanism.

External ID is an additional condition that helps make sure the AssumeRole request is meant for the right customer.

A typical third-party SaaS provider serves many customers. Many of those customers create IAM roles that trust the same AWS principal owned by that provider.

From AWSs point of view, the vendor principal may be allowed to assume many customer roles. That is expected.

But the trust policy still needs a way to say:

This vendor principal can assume this role only when it provides the External ID that belongs to this customer.

That is the job of External ID.

It is a value included in the AssumeRole request. The IAM role trust policy can require that exact value through a condition. If the value does not match, AWS denies the request.

The confused deputy problem

External ID exists to help prevent the confused deputy problem.

The idea is not specific to AWS. It is an old security problem where a trusted system is tricked into using its authority on behalf of the wrong party.

In AWS cross-account access, the pattern looks like this:

Customer A uses a SaaS platform.

Customer B also uses the same SaaS platform.

Both customers create IAM roles that trust the SaaS providers AWS principal.

Now imagine Customer A tries to cause the SaaS provider to assume Customer Bs role. The provider is trusted by Customer B, so AWS needs another way to know whether the request is actually meant for Customer B.

External ID gives AWS that additional check.

Customer Bs role trust policy requires Customer Bs External ID. If the SaaS provider sends the wrong External ID, the AssumeRole request fails.

This does not solve every security problem. It solves a specific cross-account authorization problem. But that problem is important enough that any serious AWS SaaS integration should handle it cleanly.

A simple trust policy example

A simplified trust policy might look like this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "example-external-id"
        }
      }
    }
  ]
}

There are three important pieces here.

First, the Principal identifies the AWS account or role that is allowed to request access.

Second, the Action allows sts:AssumeRole.

Third, the Condition requires the expected sts:ExternalId.

Without the condition, the trusted principal alone may be enough to assume the role. With the condition, the trusted principal also has to provide the correct customer-specific value.

That is the practical security improvement.

External ID is not a password

It is tempting to think of External ID as a password.

That is not quite right.

A password is usually selected by a user and used to authenticate that user. External ID is a value used inside an AWS trust relationship to help a third-party service prove that an AssumeRole request belongs to the correct customer context.

The customer should not need to invent it.

A well-designed SaaS platform should generate it, bind it to the correct customer or workspace, and show it during setup. The customer then places it into the IAM role trust policy.

The customer still controls the AWS role. The SaaS controls the mapping between customer workspace and External ID.

That division is useful.

The customer should not be able to accidentally choose a weak or duplicate value. The service should not rely on user-selected text as the trust boundary.

External ID should be generated by the SaaS

When evaluating an AWS SaaS product, pay attention to how it handles External ID.

A stronger pattern looks like this:

  • The SaaS generates the External ID server-side.
  • The value is unique to the customer workspace or account connection.
  • The browser cannot override it.
  • The IAM role setup instructions include the generated value.
  • The product verifies the role by calling STS with that value.
  • The product stores only what it needs to identify and verify the connection.

This removes a surprising amount of ambiguity.

It also avoids a common setup smell: asking the customer to type any External ID they want.

If the customer can choose any value, the product now has to trust that customers will choose something unique and hard to guess. That is not a good assumption for a security-sensitive integration.

External ID does not replace least privilege

External ID is necessary, but it is not sufficient.

A role can have a perfect External ID condition and still be overprivileged.

That is why External ID should sit beside least privilege, not replace it.

For a cloud security scanner, the IAM permissions should usually be read-only. The scanner may need broad visibility, but broad visibility is not the same as broad control.

A scanner might need to call APIs that list resources, describe configurations, inspect IAM structure, read security service status, or query cost metadata.

That does not mean it needs permission to modify infrastructure.

A safer policy shape is built around actions like:

List*
Describe*
Get*

For configuration metadata, that is usually enough to answer the important posture questions:

  • Which resources exist?
  • Which ports are exposed?
  • Which buckets are public?
  • Which roles look risky?
  • Which access keys are stale?
  • Which services lack logging, backup, or encryption?
  • Which resources look idle or wasteful?

External ID protects the cross-account trust path. Least privilege limits what can happen after access is granted.

You need both.

External ID does not mean the scanner should read your data

Another common mistake is treating read-only as one big category.

It is not.

There is a major difference between reading configuration metadata and reading customer data.

A cloud posture tool may need to know that an S3 bucket is public. It does not need to read the objects inside that bucket.

It may need to know that a DynamoDB table exists and whether encryption is configured. It does not need to read table items.

It may need to know that a Lambda function uses an outdated runtime. It does not need to inspect application payloads.

For a metadata-only scanner, the IAM policy should avoid data-plane reads such as:

s3:GetObject
dynamodb:GetItem
dynamodb:Query
dynamodb:Scan
logs:GetLogEvents
states:GetExecutionHistory
athena:GetQueryResults

A vendor may have a valid reason for some of these in a specific product category. But for a normal AWS security and cost posture scanner, data-plane access should not be the default.

If a tool asks for it, ask why.

What can go wrong without External ID

Without External ID, the trust policy may depend only on the vendors AWS principal.

That means the role trusts the vendor, but it may not strongly bind the AssumeRole request to the correct customer context.

In small internal setups, that may not seem like a big deal. In a third-party SaaS environment, it is different.

A vendor might have thousands of customers. Many customers may trust the same vendor AWS account. The vendors application layer must map customers to role ARNs, account IDs, and scans correctly.

External ID gives AWS a policy-level guardrail. Even if the wrong role ARN is used in the wrong customer context, the trust policy can reject the request because the External ID does not match.

That is valuable because it moves part of the protection into AWS IAM, not just the vendor application.

Application checks are still necessary. But when the AWS trust policy can enforce the customer binding too, the overall design is stronger.

What good onboarding should show

A good AWS onboarding flow should not hide the trust model.

It should make it obvious.

Before connecting a production account, you should be able to see:

  • The IAM role ARN you are creating.
  • The trusted AWS principal.
  • The External ID value.
  • The policy permissions.
  • Whether the policy is read-only.
  • Whether the scanner reads metadata only.
  • How to revoke access.
  • What AWS services are covered.
  • Whether the product supports CloudFormation or Terraform setup.

That level of clarity matters because connecting a cloud account is not the same as signing into a normal web app. You are creating a trust relationship between your infrastructure and a third-party system.

The setup screen should respect that.

How trueno handles External ID

trueno uses the AWS cross-account role model for connected AWS accounts.

The customer creates a read-only IAM role in their AWS account. trueno assumes that role server-side through AWS STS. The role trust policy is gated with an External ID tied to the customer workspace, and the client cannot choose or override that value.

The scanner is designed around configuration metadata. It uses AWS metadata to find public exposure, identity risk, compliance gaps, operational drift, and cost waste. It does not require long-lived AWS access keys for routine scanning, and it does not require broad infrastructure write permissions.

That is the important part.

The product can inspect enough of the AWS account to produce useful findings, but the access model keeps the customer in control:

  • Temporary credentials.
  • Read-only role.
  • External ID protection.
  • Metadata-only scanning.
  • One IAM role that can be revoked from AWS.

This is the kind of access model a cloud security product should start with.

How to review a vendors AWS access model

Before you connect any AWS SaaS product, ask these questions:

  1. Do you use STS AssumeRole?
  2. Do you require long-lived AWS access keys?
  3. Is an External ID required in the trust policy?
  4. Who generates the External ID?
  5. Can the browser or customer override it?
  6. Is the IAM role read-only?
  7. Which exact AWS actions are included?
  8. Do you call any data-plane APIs?
  9. Can access be revoked by deleting one IAM role?
  10. What happens if AssumeRole fails?
  11. Can I inspect the policy before deployment?
  12. Do you support CloudFormation or Terraform setup?

You do not need a perfect answer to every question, but you do need clear answers.

Vague answers are a warning sign.

A practical rule for buyers

When a security tool asks for AWS access, evaluate the access model before the dashboard.

A beautiful dashboard does not compensate for a risky connection model.

A strong AWS SaaS integration should have these properties:

  • Temporary credentials through STS
  • External ID protection
  • Customer-controlled IAM role
  • Read-only permissions by default
  • Metadata-only scanning where possible
  • No long-lived AWS access keys
  • Clear revocation path
  • Clear documentation of required permissions

That is the baseline.

Everything else comes after.

Final thought

External ID is a small field in an IAM trust policy, but it carries a lot of weight.

It helps prevent a trusted service from becoming a confused deputy. It binds cross-account access to the right customer context. It makes the trust relationship more explicit.

For any AWS SaaS product, that matters.

If the product is supposed to help you manage cloud security, cost, and operational risk, its own access model should be secure by design.

Start there.

Next steps

The trust model matters more than the dashboard.

If the access model matters to you, these pages go deeper on how trueno connects, what it reads, and how to get started.

The trueno security model

Read-only IAM, short-lived STS sessions, external ID protection, and workspace isolation, explained in full.
Read the security model

What trueno scans

The AWS services trueno reads, and the configuration metadata it uses to find exposure, identity risk, and cost waste.
See AWS coverage

Plans and pricing

Free to start on one AWS account. Paid tiers add more accounts, automation, alerts, and API access.
View pricing

Connect a read-only role

Set up the role in a few minutes and run your first read-only scan. No agents, no stored access keys.
Start free