Safeguard
Application Security

AWS IAM permissions boundaries best practices

How AWS IAM permissions boundaries cap delegated identities, differ from SCPs, and where teams get privilege escalation wrong.

Vikram Iyer
Cloud Security Engineer
7 min read

AWS IAM permissions boundaries let account administrators cap the maximum permissions a delegated IAM user or role can ever hold, even if someone later attaches an over-permissive policy. AWS shipped the feature in July 2018 to solve a problem large organizations kept hitting: once you delegate IAM role creation to application teams, someone eventually creates a role with AdministratorAccess attached, either by mistake or by taking the path of least resistance under a deadline. A permissions boundary sits alongside a role's identity-based policy, and AWS computes effective permissions as the intersection of the two, not the union. Teams running dozens or hundreds of AWS accounts under AWS Organizations use boundaries to let engineers self-service IAM without security reviewing every policy change by hand. Misconfigured boundaries are also one of the more common paths to privilege escalation found in cloud penetration tests, which is why the mechanics below matter more than the marketing copy around them.

What is an AWS IAM permissions boundary?

A permissions boundary is a managed policy — AWS-managed or customer-managed, up to 6,144 characters — attached to an IAM user or role that sets the maximum permissions that identity can ever exercise, regardless of what its identity-based policies grant. If a role's identity policy allows s3:*, ec2:*, and iam:*, but its boundary only allows s3:GetObject on a single bucket ARN, the role's effective permission is exactly that one read action on that one bucket — everything else is denied even though the identity policy technically permits it. This is different from a Deny statement in that boundaries never grant permissions on their own; they only constrain what an already-attached policy can grant. AWS evaluates the request against the identity policy, the boundary, and any Service Control Policy (SCP) in scope, and the request must pass all three for access to be allowed.

How is a permissions boundary different from a Service Control Policy?

An SCP applies at the AWS Organizations level to every identity in an account or organizational unit, while a permissions boundary applies to one specific IAM user or role inside a single account. AWS Organizations supports a default limit of 5 SCPs attached directly to any one account or OU, and up to 1,000 policies per organization, but SCPs can't be scoped to "this one role that the checkout team created" — they're a blunt instrument for the whole account tree. Permissions boundaries fill that gap: security can set an org-wide SCP that blocks all IAM identities from ever leaving us-east-1 or eu-west-1, and separately require every role created by the platform-eng team to carry a boundary that blocks access to the billing and KMS-key-management APIs. Most mature AWS environments run both — SCPs for account-wide guardrails, boundaries for per-identity delegation — because relying on either alone leaves a gap the other was built to close.

When should you apply a permissions boundary instead of just tightening the identity policy?

Apply a boundary specifically when you're delegating IAM creation rights, not when you're just writing a normal least-privilege policy for a workload. The scenario permissions boundaries exist for is: a platform team gets iam:CreateRole, iam:CreatePolicy, and iam:AttachRolePolicy so they can provision their own service roles without filing a ticket to central security, but you don't want them able to create a role with more access than they themselves have. AWS's documented pattern is to grant iam:CreateRole conditioned on iam:PermissionsBoundary equaling a specific policy ARN, for example arn:aws:iam::123456789012:policy/DeveloperBoundary, so any role that team creates is automatically capped no matter what identity policy they attach to it. If you're securing a single application's own runtime role — the thing that actually calls S3 or DynamoDB — a tightly scoped identity policy is usually sufficient and a boundary adds operational overhead without benefit.

What permissions boundary mistakes actually lead to privilege escalation?

The most common mistake is leaving iam:DeleteRolePermissionsBoundary, iam:PutRolePermissionsBoundary, or iam:PutRolePolicy available to the delegated identity itself, which lets it strip or swap the boundary that was supposed to contain it. Rhino Security Labs documented this pattern publicly in 2018 in its widely cited "AWS IAM Privilege Escalation" research, cataloging over 20 distinct escalation methods, several of which involve a role modifying its own permissions boundary or attaching a new policy version after the boundary was set but before it was enforced on the modification action itself. A second common error is writing the boundary using NotAction instead of an explicit Action allow-list — a NotAction boundary that excludes ten sensitive API calls still implicitly allows every other API in AWS, including new services AWS ships after the policy was written. A third: teams set the boundary on role creation but never re-check it after the role exists, so a later iam:AttachRolePolicy call that grants more identity-side permissions passes silently because nobody re-validates the intersection at runtime — the boundary is still correct, but the assumption that "boundary equals ceiling forever" breaks down if the boundary itself later gets deleted by an over-permissioned break-glass account.

How do you implement a permissions boundary for delegated IAM administration?

Implement it by creating one customer-managed boundary policy per delegation tier, then denying the ability to remove or replace that boundary from within the delegated identity's own policy. A typical two-part setup: a boundary policy (DeveloperBoundary) that allow-lists specific service actions and explicitly excludes IAM, KMS key policy, and Organizations APIs; and a separate policy attached to the platform team's own role that grants iam:CreateRole and iam:CreatePolicy only when the request includes "iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/DeveloperBoundary", combined with an explicit Deny on iam:DeleteRolePermissionsBoundary and iam:PutRolePermissionsBoundary against any resource. Roll this out account-by-account starting with your highest-risk delegation — usually whichever team has the broadest iam:* grant today — rather than organization-wide on day one, since a boundary that's too restrictive breaks deployments in a way that's immediately visible and disruptive.

How do you audit and monitor permissions boundaries across dozens of AWS accounts?

Audit boundaries continuously by alerting on the two CloudTrail events that change them — PutRolePermissionsBoundary and DeleteRolePermissionsBoundary — because a boundary that silently disappears is functionally identical to never having set one. IAM Access Analyzer can generate policies based on actual CloudTrail activity, which is useful for tightening a boundary that was originally written broad "to unblock the team" and never revisited; pair that with an AWS Config rule that flags any IAM role lacking a PermissionsBoundary attribute in accounts where your policy requires one. At organizations running 50+ AWS accounts under Organizations, doing this manually per-account doesn't scale — you need the CloudTrail events and IAM Access Analyzer findings aggregated centrally and diffed against a known-good baseline, otherwise a boundary removed in account 41 of 60 during an on-call incident at 2 a.m. simply won't surface until the next quarterly access review, if it surfaces at all.

How Safeguard Helps

Safeguard extends the same reachability-driven approach we apply to application vulnerabilities to cloud identity misconfiguration: rather than flagging every IAM role missing a permissions boundary as equally urgent, Safeguard's reachability analysis correlates each role's effective permissions with the code paths and services that role actually invokes in production, so security teams triage the roles that can reach sensitive data or admin APIs first. Griffin AI, Safeguard's analysis engine, reads CloudTrail activity and IaC definitions (Terraform, CloudFormation) side by side to catch boundary drift — like a DeleteRolePermissionsBoundary call — and explain the blast radius in plain language instead of a raw event log. Safeguard also ingests and generates SBOMs for the workloads those roles run, tying an IAM overprivilege finding to the specific software components it protects (or fails to). When a fix is identified — adding a missing boundary condition, scoping a NotAction statement to an explicit allow-list — Safeguard can open an auto-fix pull request against the Terraform or CloudFormation source so the change ships through your normal review process instead of a manual console edit that nobody documents.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.