Trust Policies With Wildcard Root ARNs Skip ExternalId
A cross-account role in AWS IAM trusts arn:aws:iam::*:root without an sts:ExternalId condition, letting any AWS account call sts:AssumeRole successfully. CloudTrail shows a valid, authenticated call, so GuardDuty and standard IAM reviews rarely flag the session as anomalous until data has already moved.
Operational summary
At a glance
- Symptom
- Any external AWS account calls sts:AssumeRole against the role ARN and receives valid temporary credentials.
- Likely cause
- A cross-account role trust policy sets Principal to arn:aws:iam::*:root with no sts:ExternalId condition on AssumeRole.
- Impact
- Any AWS account on the internet can assume the role and inherit its attached permissions, logged as a normal authenticated call.
- Verification signal
- Re-run the detection test and a controlled negative-path test.
- Safe correction
- Scope the principal to the exact trusted account ARN and require a unique sts:ExternalId condition, verified via IAM Access Analyzer findings.
- Rollback or recovery
- Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.
The Trap
A cross-account IAM role’s trust policy specifies "Principal": {"AWS": "arn:aws:iam::*:root"} for the sts:AssumeRole action, with no Condition block enforcing sts:ExternalId. IAM’s policy engine treats the wildcard root ARN as a syntactically valid principal, so the role deploys without error and passes automated linting tools that only check for the literal string "*" rather than wildcarded ARN patterns.
The Default State
This pattern appears constantly in SaaS onboarding runbooks and internal Terraform modules copied from vendor documentation. Engineers set up cross-account access for a monitoring or backup vendor, intend to restrict the principal later, and never add the ExternalId condition because the initial handshake with the vendor’s account succeeds without it. AWS Organizations SCPs do not block this by default, and IAM Access Analyzer will report the role as “external access allowed” only if it is actively scanning that specific resource type.
The Blast Radius
Because the trust policy authorises the root principal of every AWS account on the internet, not just the intended vendor, any external account that knows or guesses the role ARN can call sts:AssumeRole and receive temporary credentials scoped to whatever permissions policy is attached. This becomes a confused deputy problem: attackers who compromise or register throwaway AWS accounts can enumerate role ARNs from leaked CloudFormation templates, public GitHub repositories, or Terraform state files, then assume the role directly. CloudTrail logs the call as a legitimate, successfully authenticated AssumeRole event from a foreign account, which does not trigger GuardDuty’s anomalous-behaviour findings the way credential theft does. Once inside, the caller inherits whatever S3, KMS, or Secrets Manager access the role’s permissions policy grants, and session duration up to the role’s configured maximum extends the exposure window well past initial discovery.
The Lead Mechanic Fix
Replace the wildcard root principal with the specific trusted account ARN and enforce a unique sts:ExternalId per external party: "Condition": {"StringEquals": {"sts:ExternalId": ""}}. Run aws accessanalyzer list-findings --analyzer-arn against every role with an external principal and remediate any finding where the principal ARN contains a wildcard segment. For vendor integrations, generate the ExternalId server-side per customer, store it outside source control, and rotate it on any suspected leak rather than treating it as a permanent onboarding constant.
Apply the safer control
Before you change production
Confirm the affected scope, export the current configuration, and test the replacement control in a non-production environment first.
Fix commands and configuration
sts:ExternalId"Condition": {"StringEquals": {"sts:ExternalId": ""}}aws accessanalyzer list-findings --analyzer-arnVerify, roll back or escalate
Verify
Re-run the detection test and a controlled negative-path test. Confirm the unsafe behaviour is blocked while approved traffic still succeeds.
Rollback
Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.
Escalate
Escalate when the blast radius is uncertain, the control cannot be tested safely, or remediation requires an outage or security exception.