S3 StringLike Conditions: The Wildcard Principal Escape
A bucket policy grants Principal "*" and relies on a StringLike condition against aws:PrincipalArn to narrow access, but the glob pattern matches any account number in that ARN segment, and IAM Access Analyzer excludes the bucket from its public findings once that condition key appears, so no scanner in the account ever flags the exposure.
Operational summary
At a glance
- Symptom
- An external account creates or assumes an IAM role matching the glob pattern's role-name suffix, satisfying the condition regardless of account ownership.
- Likely cause
- Principal set to "*" paired with a StringLike condition on aws:PrincipalArn using a wildcard in the account ID segment.
- Impact
- Any AWS account can gain the access the policy intended to restrict, while Access Analyzer and the S3 console report the bucket as non-public.
- Verification signal
- Re-run the detection test and a controlled negative-path test.
- Safe correction
- Replace StringLike account-segment wildcards with StringEquals against explicit account IDs or full literal ARNs.
- Rollback or recovery
- Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.
The Trap
Bucket policies that set "Principal": "*" and then attempt to scope access with a StringLike condition on aws:PrincipalArn, using a pattern such as arn:aws:iam::*:role/CrossAccountRole. The intent is to restrict access to a specific IAM role name shared across an organisation’s accounts. The wildcard segment sits in the account ID position, which StringLike treats as an unbounded glob, not a placeholder for “accounts I trust”.
The Default State
This pattern is lifted almost verbatim from AWS’s own cross-account access blog posts and re-invented by engineers wiring up shared logging or artefact buckets between accounts they control. Nobody revisits the condition operator once the policy validates and the intended account can read the objects. The account ID wildcard is left in place because it “still works” for the account that wrote it.
The Blast Radius
Any external AWS account can create an IAM role with the exact name CrossAccountRole (or whatever suffix the pattern specifies), assume it, and satisfy the StringLike match regardless of which account owns that role. The bucket becomes readable or writable by any AWS customer willing to name a role correctly, which is a trivial reconnaissance step once the bucket name or ARN pattern leaks through logs, error messages, or a misconfigured CORS response. Worse, IAM Access Analyzer for S3 and the S3 console’s “public” indicator both exclude bucket policies from public findings whenever a recognised condition key such as aws:PrincipalArn is present, irrespective of the comparison operator used. Security teams see a clean dashboard while the bucket is functionally open to the internet through a one-line role creation.
The Lead Mechanic Fix
Replace the glob entirely. Use StringEquals against aws:PrincipalAccount with an explicit list of trusted account IDs, or against aws:PrincipalArn with full, literal ARNs, never a partial pattern:
"Condition": {"StringEquals": {"aws:PrincipalAccount": ["111122223333", "444455556667"]}}
If cross-account role names genuinely vary, pin the trust in the Principal element itself with explicit account root ARNs rather than "*", and reserve any remaining StringLike use for path or tag matching only, never for identity boundaries. Re-run aws accessanalyzer list-findings after the change and confirm the finding disappears for the correct reason: the policy is actually restrictive, not merely exempt from the check.
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
StringEqualsaws:PrincipalAccountaws:PrincipalArnVerify, 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.