Skip to main content
cd ../config-traps
risk/register/diagnosing-hidden-aws-iam-policy-design-configuration-trap.html
AWS IAM Policy Designhigh severity

Diagnosing a Hidden AWS IAM Policy Design Configuration Trap in Amazon Web Services

Severity
high
Reviewed
1 Aug 2026
Remediation
~20 minutes
Overview

A deceptive AWS IAM NotResource-plus-Allow statement grants access to every bucket except the one used for testing. Diagnose, correct and roll back safely.

Operational summary

At a glance

Verification signal
Re-run the detection test and a controlled negative-path test.
Rollback or recovery
Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.

Symptom

During a policy review conducted in an isolated, non-production AWS account, a platform engineering team tested an IAM role that was designed to restrict an application to reading objects from a single Amazon S3 bucket. Manual verification against the intended bucket behaved exactly as expected: requests to the named bucket succeeded, and the team recorded the change as validated. A later access review found the same role could also list and read objects in every other bucket in the account, none of which the workload was supposed to reach.

False Assumption

The engineer who wrote the policy assumed that naming a specific bucket ARN inside a statement, on its own, is enough to scope an Allow effect to that one bucket. The statement had been copied from an internal template using the NotResource element, on the understanding that “NotResource with one bucket named” reads as “grant access to only this bucket.” In AWS IAM’s policy grammar, NotResource paired with Effect: Allow does the opposite: it grants the listed actions on every resource except the ones named. Treating Resource and NotResource as interchangeable ways of writing “just this one resource” is the misleading step that produced the trap.

Root Cause

The deployed statement read:

{
  "Effect": "Allow",
  "Action": ["s3:GetObject", "s3:ListBucket"],
  "NotResource": [
    "arn:aws:s3:::intended-bucket",
    "arn:aws:s3:::intended-bucket/*"
  ]
}

Because the statement excludes intended-bucket from its scope, no access is granted to that bucket by this statement; access to it depends entirely on another policy. For every other bucket in the account, the statement grants both actions unconditionally. The manual check against intended-bucket only exercised the one resource the statement explicitly excludes, so the test could not reveal the excess grant elsewhere. The trap is deceptive precisely because the resource used for validation is the one resource the policy does not touch.

Diagnosis

Confirming the real effect of a statement requires testing resources outside the one named in it, not only the one inside it.

  • Command: aws iam simulate-principal-policy --policy-source-arn <role-arn> --action-names s3:GetObject s3:ListBucket --resource-arns arn:aws:s3:::unrelated-bucket/object-key — read-only. Confirms whether effective permissions extend beyond the intended bucket.
  • Command: aws iam get-policy-version --policy-arn <policy-arn> --version-id <current-version> — read-only. Retrieves the exact deployed statement, since console summaries can compress Resource and NotResource into similar-looking labels.

Expected evidence of the trap: the simulation for an unrelated bucket returns allowed for both actions, even though the change record states access should be limited to one bucket.

Correction

The statement must name the intended bucket directly under Resource, not under NotResource:

{
  "Effect": "Allow",
  "Action": ["s3:GetObject", "s3:ListBucket"],
  "Resource": [
    "arn:aws:s3:::intended-bucket",
    "arn:aws:s3:::intended-bucket/*"
  ]
}

Publish the corrected statement as a new policy version rather than editing in place, so the previous version remains available for rollback:

  • Command: aws iam create-policy-version --policy-arn <policy-arn> --policy-document file://corrected-policy.json --set-as-default — state-changing. Publishes the corrected statement while preserving the prior version.

Apply this only in the isolated validation account first, and confirm the role has no other attached policy separately granting the same broad access before treating the correction as complete.

Validation

  • Simulate s3:GetObject and s3:ListBucket against intended-bucket; expected result: allowed.
  • Simulate the same actions against the unrelated bucket used during diagnosis; expected result: implicitDeny or explicitDeny, with no other attached policy providing an alternative allow.
  • List all policies attached to the role with aws iam list-attached-role-policies and aws iam list-role-policies; confirm no remaining statement grants the excluded actions outside intended-bucket.

Rollback

If the corrected policy blocks a legitimate access path that had unintentionally depended on the wider grant, do not re-introduce NotResource. Instead:

  1. Identify the prior version ID with aws iam list-policy-versions --policy-arn <policy-arn> — read-only.
  2. Restore it only as a temporary measure with aws iam set-default-policy-version --policy-arn <policy-arn> --version-id <previous-version-id> — state-changing.
  3. Record the legitimate access path that depended on the wider grant, and write a deliberate, explicitly scoped statement for it rather than leaving the reverted broad grant in place.

Keep the reverted version in place only long enough to design the scoped replacement; treat it as a stop-gap, not a resolution.

Prevention

The underlying design lesson matches AWS’s own published guidance on protecting workloads through deliberate, least-privilege permission design. Three checks reduce the chance of this trap recurring:

  • Treat any statement combining NotResource or NotAction with Effect: Allow as requiring a second reviewer, since the exclusion logic runs opposite to how a scoping element instinctively reads.
  • Require that every policy validation pass includes at least one resource outside the intended scope, not only the intended resource.
  • Where a policy linter or access-analysis tool is available, run it against every new or modified statement before it reaches a shared account, and treat any flagged NotResource/NotAction combination with Allow as needing explicit sign-off rather than routine approval.
03

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

Allow
NotResource
Effect: Allow
04

Verify, 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.

After remediation

Further reading stays below the corrective workflow and is selected by platform, category and shared technical keywords.

Discover more

Connected KBY resources