An IAM permission boundary is an AWS managed or customer-managed policy that sets the maximum permissions an IAM entity can have — the effective access is the intersection of the entity's attached policies and its boundary, so nothing outside the boundary is ever allowed. It does not grant anything on its own. It only limits. That single property is what makes it the right tool for safe delegation, and the wrong tool for handing out access.
The confusion usually starts because a permission boundary looks exactly like an identity policy: same JSON, same actions, same resources. The difference is entirely in how it is attached and evaluated. An attached policy says "you may do these things." A permission boundary says "you may never do more than these things." An action is only allowed when both agree.
Where boundaries sit in the evaluation logic
AWS evaluates a request against several policy types. For an IAM user or role with a boundary, the allow decision requires the action to be permitted by the identity-based policies and permitted by the permission boundary (and not blocked by any explicit deny or service control policy). If either the identity policy or the boundary is silent on an action, the request is denied.
That means a boundary can never widen access. If a role has an attached policy granting s3:* but a boundary that only allows s3:GetObject, the role can read objects and nothing else. Delete a bucket? Denied by the boundary. Add the delete permission to the attached policy later? Still denied, because the boundary never mentioned it.
The problem boundaries actually solve
The classic use case is delegated administration. You want to let a team's lead create IAM roles for their own developers, but you do not want them minting roles with AdministratorAccess. Without boundaries you would have to review every role they create.
With boundaries, you write a permissions policy that lets the lead create roles only if those roles carry a specific boundary. The condition looks like this:
{
"Effect": "Allow",
"Action": ["iam:CreateRole", "iam:PutRolePolicy"],
"Resource": "*",
"Condition": {
"StringEquals": {
"iam:PermissionsBoundary": "arn:aws:iam::111122223333:policy/dev-boundary"
}
}
}
Now the lead can create as many roles as they like, but every one of those roles inherits dev-boundary as its ceiling. They cannot escalate, because they cannot create a role that lacks the boundary, and they cannot create a boundary more permissive than their own permissions allow.
Writing a boundary that fails safe
A boundary is a filter, so write it as the widest set of things this class of entity should ever touch, then let the day-to-day attached policies carve out the actual grants inside it. A developer boundary might allow the whole of S3, DynamoDB, Lambda, and CloudWatch, but deny all of IAM except read-only, and deny anything in the billing or Organizations namespace.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowedServices",
"Effect": "Allow",
"Action": ["s3:*", "dynamodb:*", "lambda:*", "logs:*"],
"Resource": "*"
},
{
"Sid": "NeverTouchIdentity",
"Effect": "Deny",
"Action": ["iam:*", "organizations:*", "account:*"],
"Resource": "*"
}
]
}
The explicit Deny matters. An explicit deny in the boundary wins over any allow in an attached policy, so even a mistaken AdministratorAccess grant cannot punch through it. This is the belt-and-suspenders pattern for privilege containment.
Where teams get burned
Three mistakes come up repeatedly. First, people expect the boundary to grant access and are surprised when the entity can do nothing — remember it only ever subtracts. Second, they forget that the boundary also constrains the entity's ability to modify its own permissions, which is the whole point but occasionally locks an automation account out of a self-update it needed. Third, they treat one giant boundary as least privilege. A boundary is a guardrail, not a scalpel; you still need tight attached policies inside it to actually reach least privilege.
Boundaries also do not apply to resource-based policies, service-linked roles, or the root user. And they are easy to drift — a boundary written in 2023 that still allows a service you have since forbidden is a silent gap. Treat boundary policies as code, review them in pull requests, and diff them the way you would any other infrastructure. If you are building out a broader access-review discipline, the Safeguard Academy has material on least-privilege patterns across cloud providers.
Boundaries versus SCPs versus session policies
People conflate these. A service control policy (SCP) applies at the AWS Organizations level and caps an entire account. A permission boundary applies to a single IAM user or role. A session policy is passed at AssumeRole time and caps that one session. All three are ceilings, all three intersect with identity policies, but they operate at different scopes. Use SCPs for account-wide guardrails, boundaries for per-entity delegation limits, and session policies for temporary, request-time narrowing.
FAQ
Does a permission boundary grant permissions?
No. It only sets the maximum. An entity with a boundary but no attached identity policy can do nothing at all, because the effective permissions are the intersection of the two and one side is empty.
What happens if the attached policy allows more than the boundary?
The extra permissions are ignored. The effective access is the overlap, so anything the boundary does not also allow is denied regardless of what the identity policy says.
Can a permission boundary override an explicit deny?
No. An explicit deny anywhere in the evaluation — in the boundary, an attached policy, or an SCP — always wins. Boundaries can add denies but never remove them.
How is a permission boundary different from a service control policy?
A permission boundary attaches to one IAM user or role and caps just that entity. An SCP attaches to an Organizations account or organizational unit and caps every principal in it. Both are ceilings, but at different scopes.