NotAction Plus Allow: IAM's Silent Escalation Path
NotAction Plus Allow: IAM's Silent Escalation Path sits in the AWS IAM Policy Design risk register because a small configuration shortcut can widen access, weaken control boundaries, or hide failure conditions. The risk usually starts with a typical offender looks like this: {"Effect": "Allow", "NotAction": ["iam:*", "organizations:*"], "Resource": "*"}. In production, the concern is the role granted this policy can call sts:AssumeRole to pivot into other roles in the account, lambda:UpdateFunctionCode to inject payloads into privileged
The Trap
Writing an IAM permission statement with "Effect": "Allow" and NotAction instead of a curated Action list. The pattern usually appears when someone tries to write a “grant everything except administration” policy for a contractor, CI runner, or third-party integration role.
The Default State
A typical offender looks like this: {"Effect": "Allow", "NotAction": ["iam:*", "organizations:*"], "Resource": "*"}. The author reads this as “allow everything except IAM and Organizations actions.” That reading is wrong. NotAction paired with Allow actually means “allow every action that AWS has not named in this list,” including every action that exists today under s3, ec2, lambda, sts, kms, and every action AWS ships tomorrow under services that don’t yet exist. Terraform modules copied from internal wikis and AWS console policy templates propagate this shape because it looks compact and self-documenting.
The Blast Radius
The role granted this policy can call sts:AssumeRole to pivot into other roles in the account, lambda:UpdateFunctionCode to inject payloads into privileged functions, and ec2:RunInstances with an attached instance profile carrying broader permissions than the original role itself. None of this requires a policy change on the attacker’s part — it is already permitted. Worse, AWS adds dozens of new API actions per quarter. A NotAction/Allow policy that passed a security review six months ago now covers a materially larger action surface without anyone touching the policy document. An access review signed off in Q1 is silently wrong by Q3, and nothing in CloudTrail or IAM’s policy JSON flags the drift.
The Lead Mechanic Fix
Never combine NotAction with Effect: Allow. If an exclusion pattern is genuinely required, use NotAction only under Effect: Deny, layered as a permissions boundary or Service Control Policy, never as the primary grant. Replace the allow-list with an explicit Action array generated from real usage: run aws accessanalyzer start-policy-generation against 90 days of CloudTrail data for the role, then attach the resulting least-privilege policy. Enforce this account-wide by requiring every role creation to set iam:PermissionsBoundary to a boundary policy that itself uses Effect: Deny with NotAction covering the approved service set, so any Allow statement attached later is capped regardless of how it is written.