TCH
Published on

AWS Guardrails: SCPs, RCPs, Permission Boundaries and Session Policies — What Actually Limits What

Authors

AWS Guardrails: SCPs, RCPs, Permission Boundaries and Session Policies — What Actually Limits What

Most AWS security conversations focus on what permissions to grant. Far fewer focus on the mechanisms that cap those permissions — the guardrails that sit above identity policies and enforce limits regardless of what any individual policy says.

AWS has four of them: Service Control Policies, Resource Control Policies, Permission Boundaries, and Session Policies. They are often mentioned together, frequently confused, and rarely explained in terms of how they interact. This article fixes that.

Why These Mechanisms Exist

Each guardrail was built in response to a specific class of problem that the previous layer could not solve.

SCPs (2017) came with AWS Organizations. Before them, every account was an island — a rogue IAM admin in a child account could disable CloudTrail, leave the organization, or create admin roles for outsiders, and the central security team had no lever to stop it. SCPs gave organizations a way to set hard limits that no account-level admin could override.

Permission Boundaries (2018) solved the delegation problem SCPs created. If you give a team iam:CreateRole, they can create a role with AdministratorAccess and escalate privileges. Boundaries let you say "you can create roles, but never with more permissions than this ceiling" — enabling safe IAM delegation without losing control.

Session Policies addressed the just-in-time access problem. As automation grew, teams needed temporary scoped credentials for specific tasks without creating dozens of narrow roles. Session policies let you scope down an existing role at assume-time, enabling just-in-time access patterns without role sprawl.

RCPs (2024) were built to close two gaps SCPs could never fill.

The first is the data exfiltration via resource policies gap: an attacker who compromises credentials in your org can add an external principal to an S3 bucket policy and exfiltrate data. An SCP won't stop it because SCPs don't evaluate resource-based policies — they only restrict identities, not resources.

The second is the confused deputy problem. When an AWS service like CloudTrail or Lambda acts on your behalf, it uses a service principal (cloudtrail.amazonaws.com, lambda.amazonaws.com). SCPs do not apply to AWS service principals — they only apply to IAM identities in your account. So an attacker who tricks a trusted service into accessing your resources through a manipulated request bypasses SCPs entirely. RCPs evaluate from the resource side, for all callers regardless of who they are, which is why they can catch what SCPs cannot.


The IAM Evaluation Engine — A Quick Reminder

Before getting into each mechanism, it helps to understand where they sit in AWS's permission evaluation logic. AWS publishes the official flowcharts for both IAM users and IAM roles within a single account.

For an IAM role:

AWS IAM policy evaluation logic for a role within a single account

For an IAM user:

AWS IAM policy evaluation logic for a user within a single account

A few things worth noting from these diagrams:

  • RCPs are evaluated before SCPs in the official AWS enforcement logic — a detail that matters when debugging unexpected denials.
  • Resource-based policies can short-circuit the evaluation for IAM users and role sessions: if a resource-based policy grants access directly to the session principal, an implicit deny in a permission boundary or session policy does not block it.
  • Every layer must allow the action for it to succeed. A single denial anywhere in the chain is final.

The guardrails covered in this article — SCPs, RCPs, Permission Boundaries, and Session Policies — each map to a specific decision point in these flowcharts.


Service Control Policies (SCPs)

What they are

SCPs are organization-level policies attached to OUs (Organizational Units) or individual accounts via AWS Organizations. They define the maximum permissions available to identities in those accounts — IAM users, roles, and the root user.

The critical point: SCPs do not grant permissions. They only restrict what identity policies can allow. If an SCP allows s3:* and an IAM role has s3:GetObject, the role can get objects. But if the SCP only allows s3:GetObject, even a role with s3:* can only get objects.

What they protect against

SCPs are your organization-wide guardrail against actions you never want to happen in any account — regardless of what any IAM admin in that account does.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyDeleteVPC",
      "Effect": "Deny",
      "Action": [
        "ec2:DeleteVpc",
        "ec2:DeleteSubnet",
        "ec2:DeleteRouteTable"
      ],
      "Resource": "*"
    }
  ]
}

This SCP, attached to a production OU, ensures no IAM principal in any account under that OU can delete core networking resources — even if they have AdministratorAccess.

What SCPs do not cover

This is where most people get it wrong.

SCPs do not apply to the management account — the root account of the AWS Organization itself is always exempt from SCPs, regardless of what is attached to the root OU. This is a hard AWS limitation and a strong reason to never run workloads in the management account.

SCPs only apply to known IAM identities within the account — IAM users, IAM roles, and the root user of member accounts. They do not apply to AWS service principals (cloudtrail.amazonaws.com, lambda.amazonaws.com, etc.) acting on your behalf, nor to anonymous requests. If an AWS service assumes a role in your account and accesses a resource, the request comes from a service principal — SCPs never evaluate it. This is exactly why SCPs cannot mitigate the confused deputy problem.

More importantly, SCPs only restrict identities, not resources. If you want to prevent an S3 bucket from being made public, an SCP is not the right tool — because the bucket policy is a resource-based policy, and SCPs do not evaluate resource-based policies. That is exactly the gap that RCPs fill.

SCP limits worth knowing

LimitValue
Maximum size per SCP5,120 characters
Maximum SCPs per organization10,000
Maximum SCPs attached to root5
Maximum SCPs attached to an OU5
Maximum SCPs attached to an account5
Minimum SCPs attached to an entity1 (SCPFullAWSAccess is auto-attached and cannot be removed)
Maximum OU nesting depth5 levels under root

The 5,120 character limit per policy is the one you will hit first in practice. Whitespace is stripped when saving via the console, so formatting does not count — but complex deny lists with many actions and conditions can fill up quickly. The common workaround is to split logic across multiple SCPs, keeping in mind the 5-per-entity attachment limit.

The FullAWSAccess managed policy is automatically attached to every entity when you enable SCPs. It allows all actions to pass through SCP evaluation by default — meaning your existing permissions are unaffected until you start adding restrictive SCPs. It counts toward the 5-per-entity limit, so in practice you have 4 slots for your own policies per entity.


Resource Control Policies (RCPs)

What they are

RCPs are the newest addition to the guardrail family, introduced in late 2024. They work like SCPs but from the resource side: instead of restricting what identities can do, they restrict what resources can accept — regardless of who is calling.

An RCP is attached to an OU or account via Organizations and applies to supported resource types (S3, SQS, KMS, Secrets Manager, and more). When a principal tries to access a resource, the RCP is evaluated as part of the resource's effective policy.

The gap SCPs cannot fill

Consider this scenario: a developer in your organization creates an S3 bucket and adds a bucket policy that grants public read access. Their IAM role has the permissions to do this. An SCP restricting s3:PutBucketPolicy would help — but only if you remembered to add it, and only for that specific action.

An RCP solves this at a higher level:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EnforceOrgAccess",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": "*",
      "Condition": {
        "StringNotEqualsIfExists": {
          "aws:PrincipalOrgID": "o-xxxxxxxxxxxx"
        },
        "BoolIfExists": {
          "aws:PrincipalIsAWSService": "false"
        }
      }
    }
  ]
}

This RCP, attached to the organization root, ensures that no S3 bucket in any account can be accessed by a principal outside the organization — regardless of what the bucket policy says. Even if a developer explicitly makes a bucket public, the RCP overrides it.

SCPs vs RCPs — the mental model

Think of it this way:

  • SCP: "No identity in this account can perform action X"
  • RCP: "No resource in this account can be accessed by principal Y"

They are complementary. An SCP prevents your own identities from doing dangerous things. An RCP prevents your resources from being accessed by unauthorized principals — including external ones that your own identities might accidentally invite in.

RCP limits worth knowing

LimitValue
Maximum size per RCP5,120 characters
Maximum RCPs per organization1,000
Maximum RCPs attached to root5
Maximum RCPs attached to an OU5
Maximum RCPs attached to an account5
Minimum RCPs attached to an entity1 (RCPFullAWSAccess is auto-attached and cannot be removed)

The RCPFullAWSAccess managed policy is automatically attached to every entity when you enable RCPs. It allows all actions to pass through RCP evaluation by default — meaning your existing permissions are unaffected until you start adding restrictive RCPs. It counts toward the 5-per-entity limit, so in practice you have 4 slots for your own policies per entity.


Permission Boundaries

What they are

Permission Boundaries operate at the individual identity level. A boundary is an IAM managed policy attached to a role or user that defines the maximum permissions that identity can ever have — regardless of what identity policies are attached to it.

If a role has AdministratorAccess but a permission boundary that only allows s3:* and ec2:*, the role can only perform S3 and EC2 actions. The AdministratorAccess policy is effectively capped.

The delegation problem they solve

The primary use case is safe delegation of IAM management. Consider a CI/CD pipeline that needs to create IAM roles for the services it deploys. Without a boundary, you face a dilemma:

  • Give the pipeline iam:CreateRole → it can create a role with AdministratorAccess and escalate privileges
  • Refuse to delegate IAM → every role creation becomes a manual bottleneck

Permission Boundaries solve this cleanly. You give the pipeline iam:CreateRole but require it to attach a specific boundary to every role it creates:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iam:CreateRole",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/deployment-boundary"
        }
      }
    }
  ]
}

Now the pipeline can create roles freely, but every role it creates is capped by deployment-boundary. It cannot create a role with more permissions than the boundary allows — privilege escalation through role creation is structurally impossible.

What permission boundaries do not do

Boundaries only apply to the identity they are attached to. They do not propagate to resources that identity accesses, and they do not apply to other identities in the account. They are a per-identity ceiling, not an account-wide guardrail.

Permission Boundary limits worth knowing

LimitValue
Policy typeMust be a managed policy (AWS or customer managed) — inline policies cannot be used as boundaries
Maximum size of the boundary policy6,144 characters (same as any customer managed policy)
Maximum managed policies per role10 (default), up to 25 with quota increase
One boundary per identityOnly one boundary policy can be attached to a role or user at a time

The single-boundary-per-identity constraint is worth keeping in mind: if you need to enforce multiple restrictions, they must all be combined into one managed policy. You cannot stack boundaries the way you can stack SCPs on an OU.


Session Policies

What they are

Session Policies are inline policies passed at the moment of AssumeRole. They restrict the permissions of a specific session without modifying the role itself.

aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/my-role \
  --role-session-name my-session \
  --policy '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:GetObject","Resource":"arn:aws:s3:::my-bucket/*"}]}'

This session can only call s3:GetObject on my-bucket — even if my-role has broad S3 permissions. The session policy intersects with the role's permissions: only actions allowed by both are permitted.

When to use them

Session policies are ideal for just-in-time, scoped access:

  • A user assumes a shared role but should only access their own resources during this session
  • An automated process needs a role's permissions but only for a specific resource during a specific task
  • You want to issue temporary credentials with a reduced blast radius without creating a new role

The key constraint: session policies can only restrict, never expand. You cannot use a session policy to grant permissions the role does not already have.

Session Policy limits worth knowing

LimitValue
Maximum size (JSON policy + managed policy ARNs combined)2,048 characters
Maximum managed policy ARNs per session10
JSON policy documents per session1
Maximum role session duration12 hours
Maximum role session name length64 characters

The 2,048 character limit is tight. It covers the combined size of the inline JSON policy and any managed policy ARN strings you pass. Complex session policies hit this limit quickly — keep them focused on a single resource or action scope. If you need more, consider creating a dedicated role with the exact permissions needed instead of scoping down a broad role at session time.


How They Interact: A Complete Example

Here is a scenario with all four active simultaneously.

A CI/CD pipeline has a role (pipeline-role) with s3:* on all resources. The account is in an OU with:

  • An SCP that denies s3:DeleteBucket
  • An RCP that denies S3 access from principals outside the organization

The role has a Permission Boundary that allows only s3:GetObject and s3:PutObject.

At deployment time, the pipeline explicitly calls sts:AssumeRole on pipeline-role and passes a Session Policy that further restricts access to a single bucket, my-bucket. Because the pipeline controls the AssumeRole call, it can inject the session policy itself.

Note: an EC2 instance profile session cannot have a session policy — IMDS calls AssumeRole automatically with no way to inject a policy. Session policies only apply when a principal explicitly calls AssumeRole and passes the policy at that moment.

What can the pipeline session actually do?

ActionSCPRCPBoundaryRole policySession policyResult
s3:GetObject on my-bucketAllowed
s3:PutObject on my-bucketAllowed
s3:PutObject on other-bucketDenied (session policy)
s3:DeleteObject on my-bucketDenied (boundary)
s3:DeleteBucketDenied (SCP + boundary)
Access from external principalDenied (RCP)

Every layer must allow the action. A single denial is enough.


Comparison Table

SCPRCPPermission BoundarySession Policy
ScopeAccount / OUAccount / OUIndividual identityIndividual session
ProtectsIdentitiesResourcesIdentitySession
Requires OrganizationsYesYesNoNo
Grants permissionsNoNoNoNo
Applies to root userYes (member accounts only — management account is exempt)N/A — RCPs restrict resources, not identitiesNoNo
Primary use caseOrg-wide action guardrailsOrg-wide resource access guardrailsSafe IAM delegationJust-in-time scoped access

Closing Thoughts

These four mechanisms are not alternatives — they are complementary layers that operate at different levels of the stack. SCPs and RCPs are organization-wide blunt instruments: they enforce invariants that no individual account admin can override. Permission Boundaries are surgical tools for safe delegation. Session Policies are ephemeral restrictions for scoped, time-limited access.

Used together, they let you build a permission model where the blast radius of any compromise or misconfiguration is structurally bounded — not just by policy review, but by the evaluation engine itself.

The next step after putting guardrails in place is knowing whether they are working. That is where IAM Access Analyzer comes in — and why visibility into who actually has access to what is just as important as the guardrails themselves.


Related reading: