calricon.

General

The AWS IAM Permission Boundary Trap: Why Delegated Administration Silently Enables Account Takeovers

Oct 25, 2026/8 min read

calricon / field note

Enterprise security teams rely on IAM Permission Boundaries to delegate role creation to DevOps teams without handing over full admin rights. But subtle flaws in PassRole constraints and resource-based policies turn delegated boundaries into silent privilege escalation paths. Here is how to lock them down.

Oct 25, 2026/General
255
Advertisement
<h1>The AWS IAM Permission Boundary Trap: Why Delegated Administration Silently Enables Account Takeovers</h1>

<p><strong>To eliminate the notorious bottleneck of central security teams manually approving every single IAM role for microservices and CI/CD pipelines, enterprise cloud architects turn to a seemingly elegant AWS feature: IAM Permission Boundaries.</strong> By requiring developer roles to attach an organizationally mandated boundary policy whenever they create new IAM roles or users, enterprises believe they have achieved the holy grail of cloud governance—safe, delegated self-service administration without granting full <code>AdministratorAccess</code>. Yet, in real-world red team exercises and cloud audits, over seventy percent of these delegated IAM architectures collapse under trivial privilege escalation chains, allowing developers or compromised CI/CD runners to promote themselves to full cloud account administrators within minutes.</p>

<p>The failure stems from a fundamental misunderstanding of how IAM evaluation logic handles delegated authority. Permission boundaries do not establish absolute security boundaries on their own; they merely set the maximum permissions an identity-based policy can grant. When engineering teams fail to account for <code>iam:PassRole</code> lateral movement, self-modification loopholes, and resource-based policy exemptions, a permission boundary becomes nothing more than security theater. Here is an architectural deep dive into why delegated IAM boundaries fail and the exact engineering blueprint required to build impenetrable cloud guardrails.</p>

<h2>The Architecture: How Permission Boundaries Are Supposed to Work</h2>

<p>In standard AWS IAM evaluation logic, an explicit <code>Allow</code> in an identity policy grants access unless overridden by an explicit <code>Deny</code>. When an IAM entity (user or role) has a <strong>Permissions Boundary</strong> attached, the effective permissions are determined by the <em>intersection</em> of the identity-based policy and the boundary policy:</p>

<pre><code>Effective Permissions = Identity-Based Policy &cap; Permissions Boundary
</code></pre>

<p>To implement delegated administration, the central security team grants developers or CI/CD pipelines the ability to run <code>iam:CreateRole</code>, <code>iam:AttachRolePolicy</code>, and <code>iam:PutRolePolicy</code>, but binds these actions to a strict condition:</p>

<pre><code>{
"Effect": "Allow",
"Action": [
"iam:CreateRole",
"iam:AttachRolePolicy",
"iam:PutRolePolicy"
],
"Resource": "arn:aws:iam::123456789012:role/app-*",
"Condition": {
"StringEquals": {
"iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/DevPermissionBoundary"
}
}
}
</code></pre>

<p>The developer can now create arbitrary roles and attach <code>AdministratorAccess</code> to them, secure in the knowledge that AWS will cap their actual execution privileges at whatever the <code>DevPermissionBoundary</code> permits (e.g., S3 access, DynamoDB reads, CloudWatch logging).</p>

<blockquote>
<p>"A permission boundary is not a sandbox; it is an intersection filter. If your filter leaks a single control-plane delegation vector, the entire perimeter dissolves."</p>
</blockquote>

<h2>Vector 1: The iam:PassRole Lateral Movement & Service Execution Trap</h2>

<p>The most common and catastrophic vulnerability in delegated IAM setups is an unconstrained or loosely constrained <code>iam:PassRole</code> permission. Developers legitimately need <code>iam:PassRole</code> to pass execution roles to AWS services like AWS Lambda, ECS Tasks, EC2 instances, or AWS CodeBuild.</p>

<p>However, if the developer's permissions boundary allows <code>iam:PassRole</code> with a wildcard (<code>"Resource": "*"</code>) or matches high-privilege administrative roles, the boundary is instantly subverted:</p>

<ol>
<li>The developer cannot directly call administrative APIs because their current boundary blocks EC2 or IAM writes.</li>
<li>However, the account contains pre-existing administrative roles (e.g., an AWS Organizations management role, a Terraform deployment role, or an administrative EC2 instance profile).</li>
<li>The developer crafts an ephemeral Lambda function or CodeBuild project, invokes <code>iam:PassRole</code> to attach that pre-existing administrative role to the function, and triggers execution.</li>
<li>The Lambda function executes in the AWS environment under the administrative role's credentials—completely bypassing the developer's identity boundary!</li>
</ol>

<pre><code>// THE VULNERABLE BOUNDARY PERMISSION:
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::123456789012:role/*" // FATAL: Allows passing ANY role in the account!
}

// THE ATTACKER SCRIPT: Escalating to Admin via Lambda
const { LambdaClient, CreateFunctionCommand } = require('@aws-sdk/client-lambda');
const lambda = new LambdaClient({});

await lambda.send(new CreateFunctionCommand({
FunctionName: 'PrivilegeEscalator',
Runtime: 'nodejs20.x',
Role: 'arn:aws:iam::123456789012:role/OrganizationAdminRole', // Pre-existing unconstrained role
Handler: 'index.handler',
Code: { ZipFile: Buffer.from('exports.handler = async () => { /* Attach AdministratorAccess to my user */ };') }
}));
</code></pre>

<h2>Vector 2: The Self-Modification and Versioning Loophole</h2>

<p>If the delegated administrator has permissions to manage policies or boundaries, subtle syntax gaps allow them to modify or detach their own constraints:</p>

<ul>
<li><strong>Missing Deletion Guard:</strong> If the boundary policy does not explicitly deny <code>iam:DeleteRolePermissionsBoundary</code> on the role itself, an actor can simply strip the boundary off their own role in a single API call.</li>
<li><strong>The Default Policy Version Escalation:</strong> If a developer is granted <code>iam:CreatePolicyVersion</code> or <code>iam:SetDefaultPolicyVersion</code> on customer-managed policies, they can create a new version of the permission boundary policy that grants <code>*:*</code>, set it as the default version, and instantaneously elevate their entire permission ceiling across the account.</li>
<li><strong>Tag-Based IAM Bypass:</strong> Many organizations use Attribute-Based Access Control (ABAC) with tags like <code>Environment=Dev</code>. If the delegated role can run <code>iam:TagRole</code> or <code>iam:UntagRole</code>, they can re-tag an administrative role as <code>Dev</code>, pass it to a service, and seize control.</li>
</ul>

<h2>Vector 3: The Resource-Based Policy Blind Spot</h2>

<p>The most dangerous architectural misconception among cloud engineers is assuming that a permission boundary constrains what an identity can access across the AWS account. <strong>Permission boundaries have zero effect on resource-based policies.</strong></p>

<p>Consider an S3 bucket containing sensitive customer financial data or KMS customer-managed keys (CMKs) used for envelope encryption:</p>

<ul>
<li>A developer's boundary explicitly blocks <code>s3:GetObject</code> on <code>arn:aws:s3:::prod-customer-pii/*</code>.</li>
<li>However, the developer has <code>s3:PutBucketPolicy</code> or <code>kms:PutKeyPolicy</code> to configure application storage.</li>
<li>Because resource-based policies evaluate independently, the developer edits the bucket policy to grant an external, attacker-controlled AWS account (e.g., <code>arn:aws:iam::999999999999:root</code>) full read access!</li>
<li>The developer then logs into their external account and downloads the entire S3 bucket via cross-account access, completely bypassing the identity boundary.</li>
</ul>

<h2>The Economics of Governance: Centralized Queue vs. Naive Delegation vs. Hardened IAM Guardrails</h2>

<table>
<thead>
<tr>
<th>Dimension</th>
<th>Centralized Security Bottleneck</th>
<th>Naive Permission Boundaries</th>
<th>Calricon Hardened Delegated Model</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Role Provisioning Velocity</strong></td>
<td>Days to Weeks (Jira ticket queues)</td>
<td>Instant (Self-service via Terraform/CLI)</td>
<td><strong>Instant</strong> (Self-service via strict template enforcement)</td>
</tr>
<tr>
<td><strong>Privilege Escalation Risk</strong></td>
<td>Low (Central team audits each role)</td>
<td>Critical (PassRole leaks &amp; resource policy bypass)</td>
<td><strong>Zero</strong> (Cryptographically bound boundaries + SCPs)</td>
</tr>
<tr>
<td><strong>Resource-Based Policy Leaks</strong></td>
<td>Uncontrolled without central review</td>
<td>Completely unmonitored blindspot</td>
<td><strong>Blocked</strong> via Service Control Policies (SCPs)</td>
</tr>
<tr>
<td><strong>Maintenance Overhead</strong></td>
<td>Unsustainable engineering toil</td>
<td>Low until security breach occurs</td>
<td><strong>Automated</strong> policy validation via IAM Access Analyzer</td>
</tr>
</tbody>
</table>

<h2>The Calricon Production Playbook: Bulletproofing Delegated IAM</h2>

<p>To safely empower developers with self-service role creation without opening lateral account takeover paths, implement this five-layer defense-in-depth model:</p>

<h3>1. Enforce Atomic Permission Boundary Attachment</h3>
<p>Ensure that any attempt to create a role or modify role policies without attaching the approved boundary fails with an explicit denial. Implement reciprocal deny conditions:</p>

<pre><code>{
"Sid": "DenyRoleCreationWithoutBoundary",
"Effect": "Deny",
"Action": [
"iam:CreateRole",
"iam:PutRolePermissionsBoundary"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/EnterpriseDevBoundary"
}
}
}
</code></pre>

<h3>2. Restrict iam:PassRole with PassedToService Conditions</h3>
<p>Never permit open <code>iam:PassRole</code>. Constrain role passing strictly to approved execution role ARN paths, and mandate the destination service using <code>iam:PassedToService</code>:</p>

<pre><code>{
"Sid": "RestrictPassRoleToSpecificServicesAndPaths",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::123456789012:role/app-execution/*",
"Condition": {
"StringEquals": {
"iam:PassedToService": [
"https://www.google.com/url?q=http://lambda.amazonaws.com&source=gmail&ust=1792987895094000&sa=E",
"https://www.google.com/url?q=http://ecs-tasks.amazonaws.com&source=gmail&ust=1792987895094000&sa=E"
]
}
}
}
</code></pre>

<h3>3. Block Self-Defense Actions within the Boundary Policy</h3>
<p>The boundary policy itself must contain explicit self-defense blocks preventing the caller from stripping the boundary, altering policy versions, or untagging protected infrastructure:</p>

<pre><code>{
"Sid": "PreventBoundaryTampering",
"Effect": "Deny",
"Action": [
"iam:DeleteRolePermissionsBoundary",
"iam:CreatePolicyVersion",
"iam:DeletePolicyVersion",
"iam:SetDefaultPolicyVersion",
"iam:DeletePolicy"
],
"Resource": [
"arn:aws:iam::123456789012:policy/EnterpriseDevBoundary",
"arn:aws:iam::123456789012:role/app-*"
]
}
</code></pre>

<h3>4. Enforce Organization SCPs on Resource-Based Policies</h3>
<p>Because boundaries cannot stop resource policy data exfiltration, deploy an AWS Organizations Service Control Policy (SCP) at the root or organizational unit (OU) level. Explicitly deny modifying resource policies that grant cross-account access unless originating from an authorized security deployment role:</p>

<pre><code>{
"Sid": "BlockCrossAccountResourcePolicyTampering",
"Effect": "Deny",
"Action": [
"s3:PutBucketPolicy",
"kms:PutKeyPolicy",
"sqs:SetQueueAttributes"
],
"Resource": "*",
"Condition": {
"StringNotLike": {
"aws:PrincipalARN": "arn:aws:iam::*:role/SecurityDeployerRole"
}
}
}
</code></pre>

<h3>5. Automated Policy Verification via IAM Access Analyzer</h3>
<p>Integrate AWS IAM Access Analyzer and CloudFormation Guard into your CI/CD pull request workflows. Automatically evaluate role policies for privilege escalation risks (such as <code>iam:PassRole</code> wildcard grants or STS assume-role trust relationships) before Terraform or CDK scripts merge to main.</p>

<h2>Conclusion</h2>

<p>Delegating IAM administration is mandatory for modern engineering velocity, but doing so via standalone, unhardened permission boundaries is an open door to account takeover. By combining strictly bound role creation conditions, scoped <code>iam:PassRole</code> guardrails, self-defense policy blocks, and organization-level SCPs on resource-based policies, platform security teams can safely eliminate provisioning bottlenecks without compromising the cloud perimeter.</p>
Advertisement
Talk to Calricon