An AWS Principal Field Left as an Account ARN Silently Grants Every Role in That Account
An S3 bucket policy Principal set to an account-root ARN grants access to every IAM identity in that account, not just the intended role, creating a silent and growing exposure.
Operational summary
At a glance
- Symptom
- A bucket policy written to grant one specific automation role read access to an S3 bucket instead permits every IAM user and role inside…
- Likely cause
- The root cause is a Principal element referencing the account root ARN (arn:aws:iam:::root) instead of a specific role or user ARN (arn:aws:iam:::role/ci-deploy-role).
- Impact
- Any IAM identity in the trusted account that is subsequently granted broad S3 permissions, including by an unrelated change such as an AmazonS3FullAccess-equivalent policy attached for a different…
- Verification signal
- Validation confirms the narrowed policy blocks the previously-successful unintended access while preserving the intended role's access.Action: Run aws s3api get-bucket-policy --bucket example-artifact-bucket after the change.
- Safe correction
- Replace the account-root Principal with the exact ARN of the intended role or user, and verify no other statement in the policy re-widens the grant.{ "Version": "2012-10-17", "Statement"…
- Rollback or recovery
- If the corrected policy blocks a legitimate workflow that depended on the broader account-root grant, restore access without reverting to the account-root exposure.Stop condition: any legitimate automated workflow…
Symptom
A bucket policy written to grant one specific automation role read access to an S3 bucket instead permits every IAM user and role inside the trusted AWS account to read the same objects. Access logs and CloudTrail show requests succeeding from identities that were never named in the policy, and no error or deny event is generated because, from IAM’s perspective, nothing is being denied.
The team only notices during a quarterly access review when a security engineer runs aws s3api get-bucket-policy against a bucket believed to be scoped to a single CI/CD role and finds successful GetObject calls from developer IAM users who were never granted explicit permissions to that bucket.
False Assumption
The engineer who wrote the policy assumed that specifying an account ID inside a Principal ARN restricts access to “resources belonging to that account” in the same way a security group references a specific instance. The mental model was: “this ARN identifies the automation role, and the account ID confirms which account it lives in.”
In reality, an ARN of the form arn:aws:iam::111122223333:root used as a Principal does not mean “only the root user” or “only trusted callers with an assumed context matching this account.” AWS interprets a bare account-root principal as a grant to the entire account: every IAM user, role and federated identity in account 111122223333 that has been granted the corresponding S3 permission in its own IAM policy is now permitted by the bucket policy. The account-root Principal form is a well-documented AWS IAM behaviour, not a bug, but it is routinely misread as identity-specific scoping.
Root Cause
The root cause is a Principal element referencing the account root ARN (arn:aws:iam::<ACCOUNT_ID>:root) instead of a specific role or user ARN (arn:aws:iam::<ACCOUNT_ID>:role/ci-deploy-role). Under AWS’s resource-based policy evaluation model, granting to the account root delegates the access decision entirely to that account’s own IAM policies. Any principal in the trusted account whose IAM identity policy allows s3:GetObject on the bucket ARN will succeed, because two independent authorisation checks (the bucket policy and the caller’s IAM policy) both evaluate to allow, and S3 requires only that at least one applicable policy on each side grants the action for cross-account access, with the account-root grant satisfying the bucket-policy side for the whole account.
This pattern is frequently introduced when a policy is copied from an AWS documentation example that intentionally demonstrates account-level trust, then adapted for a narrower single-role use case without changing the Principal from the account root to the specific role ARN.
Impact
Any IAM identity in the trusted account that is subsequently granted broad S3 permissions, including by an unrelated change such as an AmazonS3FullAccess-equivalent policy attached for a different project, gains immediate read access to this bucket with no further change to the bucket policy itself. The exposure grows silently as the account’s IAM footprint changes, and the bucket owner has no visibility into that drift because it happens entirely on the trusting account’s IAM side. For buckets holding customer data, credentials or build artefacts, this converts a single scoped grant into an account-wide standing exposure that is invisible in the bucket policy’s plain-English review and only surfaces through access log analysis or IAM Access Analyzer.
Diagnosis
Diagnosis is read-only and should be run from a role with s3:GetBucketPolicy and iam:GetAccountAuthorizationDetails permissions in an environment where inspecting production policy documents is authorised.
- Retrieve the current bucket policy document and inspect every Principal element for account-root ARNs.
- Cross-reference the trusted account ID against the list of IAM roles and users in that account with any S3 permissions, using IAM Access Analyzer for S3 if enabled, or manually reviewing attached and inline policies.
- Review S3 server access logs or CloudTrail data events for
GetObject/ListBucketrequests from principals other than the intended automation role.
Correction
Replace the account-root Principal with the exact ARN of the intended role or user, and verify no other statement in the policy re-widens the grant.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCiDeployRoleReadOnly",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/ci-deploy-role"
},
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::example-artifact-bucket/*"
}
]
}
Applying this correction is a state-changing action against a production trust boundary and must go through the validation and rollback steps below before and after the change.
Validation
Validation confirms the narrowed policy blocks the previously-successful unintended access while preserving the intended role’s access.
- Action: Run
aws s3api get-bucket-policy --bucket example-artifact-bucketafter the change. Expected evidence: the Principal element shows only the specific role ARN, with no account-root entry remaining in any statement. Pass condition: no statement in the returned policy document contains a bare:rootARN. - Action: From the intended CI/CD role’s assumed session, run
aws s3api get-object --bucket example-artifact-bucket --key test-object --outfile /tmp/verify. Expected evidence: the call succeeds and the object downloads. Pass condition: exit code 0 and the file is retrieved. - Action: From a separate IAM identity in the same account that is not the intended role, attempt the same
get-objectcall. Expected evidence: anAccessDeniederror. Pass condition: the call fails withAccessDenied, confirming the account-wide grant has been removed.
Rollback
If the corrected policy blocks a legitimate workflow that depended on the broader account-root grant, restore access without reverting to the account-root exposure.
- Stop condition: any legitimate automated workflow reports
AccessDeniedafter the change and cannot be immediately re-scoped. - Identify the specific role or user ARN the affected workflow uses via CloudTrail
userIdentity.arnon the failing request. - Add that exact ARN as an additional Principal entry in the same statement or a new statement; do not revert to the account-root Principal.
- Re-run the validation steps above to confirm both the newly added identity succeeds and unrelated identities remain denied.
- If the affected workflow’s identity cannot be determined quickly, apply the corrected policy in a maintenance window and keep the previous policy document saved locally so it can be reapplied deliberately, with an explicit follow-up ticket to identify and re-scope the correct principal rather than leaving the account-root grant in place indefinitely.
Prevention
Treat any bucket policy Principal ending in :root as a finding requiring explicit justification during review, not a default pattern. Enable IAM Access Analyzer for S3 on the bucket to receive ongoing findings when a policy grants broader access than intended. During policy authoring, always reference the specific role or user ARN the workload uses, and add a code-review checklist item that flags account-root principals in any S3, KMS or SQS resource policy pull request. Where cross-account access genuinely needs to cover multiple identities in a partner account, use an external ID and a dedicated cross-account role in the partner account rather than granting to that account’s root, so the partner account retains its own scoping decision without silently absorbing future IAM changes.
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
aws s3api get-bucket-policyGetObjectarn:aws:iam::111122223333:rootVerify, roll back or escalate
Verify
Validation confirms the narrowed policy blocks the previously-successful unintended access while preserving the intended role's access.Action: Run aws s3api get-bucket-policy --bucket example-artifact-bucket after the change.
Rollback
If the corrected policy blocks a legitimate workflow that depended on the broader account-root grant, restore access without reverting to the account-root exposure.Stop condition: any legitimate automated workflow reports AccessDenied after the change and cannot be immediately re-scoped.Identify the specific…
Escalate
Escalate when the blast radius is uncertain, the control cannot be tested safely, or remediation requires an outage or security exception.