IAM security · AWS Terraform · cross-resource reasoning

AWS IAM privilege escalation detection in Terraform

Most IaC scanners check IAM resources in isolation — matching wildcard actions in a single policy file. audytx builds an effective-permission graph across all your Terraform resources and walks AssumeRole / PassRole chains to find privilege-escalation paths by reachability, not by a hand-written pattern. It detects all 31 documented IAM privesc paths on the BishopFox iam-vulnerable corpus — the only other tool that does is Checkov.

What IAM privilege escalation is in Terraform

IAM privilege escalation happens when a principal with limited permissions can acquire additional privileges by chaining a sequence of API calls — attaching a policy, assuming a role, passing a role to a service, or calling a privileged service like Lambda or EC2. In Terraform, these chains are visible in the configuration: an aws_iam_role_policy grants iam:PassRole, an aws_lambda_function references that role, and a trust policy allows the principal to assume it. Detecting the path requires reading all three resources together.

Pattern-matching scanner

Flags each resource against a checklist: "this policy has iam:PassRole." Misses chains that only become privesc when combined — 25+ of the 31 paths require reading two or more resources.

audytx — graph reachability

  • Expands wildcard actions (iam:*, lambda:*) to concrete permissions
  • Resolves trust policies — who can assume this role?
  • Walks AssumeRole and PassRole chains across the resource graph
  • Flags the full chain: source principal → escalation vector → elevated permissions

The 31 documented IAM privilege-escalation paths — all detected

From the BishopFox iam-vulnerable corpus — 31 documented AWS IAM privilege-escalation paths, one Terraform file per path. audytx detects all 31. Organized below by escalation vector.

PassRole → Lambda
CreateFunction + InvokeFunction
iam:PassRole + lambda:CreateFunction + lambda:InvokeFunction
PassRole → EC2
EC2 instance with privileged profile
iam:PassRole + ec2:RunInstances
PassRole → CloudFormation
Stack creation with admin role
iam:PassRole + cloudformation:CreateStack
PassRole → Glue
Glue job with admin role
iam:PassRole + glue:CreateJob + glue:StartJobRun
Direct privilege
CreateNewPolicyVersion
iam:CreatePolicyVersion
Direct privilege
SetDefaultPolicyVersion
iam:SetDefaultPolicyVersion
Direct privilege
CreateAccessKey
iam:CreateAccessKey on other principals
Direct privilege
CreateLoginProfile
iam:CreateLoginProfile on other users
Direct privilege
UpdateLoginProfile
iam:UpdateLoginProfile → console access
AssumeRole chain
AttachUserPolicy
iam:AttachUserPolicy → AdministratorAccess
AssumeRole chain
AttachGroupPolicy
iam:AttachGroupPolicy → AdministratorAccess
AssumeRole chain
AttachRolePolicy
iam:AttachRolePolicy → AdministratorAccess
AssumeRole chain
PutUserPolicy
iam:PutUserPolicy → inline admin policy
AssumeRole chain
PutGroupPolicy
iam:PutGroupPolicy → inline admin policy
AssumeRole chain
PutRolePolicy
iam:PutRolePolicy → inline admin policy
AssumeRole chain
AddUserToGroup
iam:AddUserToGroup → admin group
AssumeRole chain
UpdateAssumeRolePolicy
iam:UpdateAssumeRolePolicy → assume privileged role
PassRole → SSM
SSM Send Command
ssm:SendCommand on EC2 with admin profile
PassRole → DataPipeline
Data Pipeline activation
iam:PassRole + datapipeline:CreatePipeline
PassRole → SageMaker
SageMaker training job
iam:PassRole + sagemaker:CreateTrainingJob
EC2 SSRF
EC2 IMDS v1 exposure
IMDSv1 on EC2 with instance profile → metadata SSRF
Wildcard
AdministratorAccess direct attach
iam:* or AdministratorAccess on a principal
Wildcard
IAM full access
iam:* as standalone permission
Service role
CodeBuild role with admin pass
iam:PassRole → CodeBuild project
Service role
CodePipeline admin escalation
iam:PassRole → CodePipeline with admin stage
Service role
ECS task definition with admin role
iam:PassRole + ecs:RegisterTaskDefinition
Service role
Fargate task escalation
iam:PassRole + ecs:RunTask (Fargate)
Service role
StepFunctions admin role
iam:PassRole + states:CreateStateMachine
Service role
EventBridge rule → Lambda admin
events:PutRule + iam:PassRole + privileged target
Secrets exfil
Secrets Manager unrestricted read
secretsmanager:GetSecretValue without resource constraint
STS
sts:AssumeRole wildcard
sts:AssumeRole on * resource

Benchmark: audytx vs Checkov, Trivy, KICS on iam-vulnerable

100%
Recall — all 31 paths detected
tied with Checkov · KICS 3% · Trivy 0%
IAM precision vs Checkov
23% vs 12% at same 100% recall
½
Alert volume vs Checkov
135 HIGH findings vs 269
ToolHIGH findingsTP (paths)FPPrecisionRecall
audytx1353110423%100%
Checkov2693123812%100%
KICS91811%3%
Trivy7070%0%

Corpus: BishopFox iam-vulnerable · 31 documented paths · one Terraform file per path. Full methodology and raw data: audytx benchmark page.

A worked example: PassRole privilege escalation in Terraform

How a three-file configuration creates an IAM privilege-escalation path that pattern matching misses.

The setup: A developer role with limited permissions, a Lambda function, and an admin role. The Lambda can be invoked by the developer. The developer has iam:PassRole on the admin role. Single-resource scanners see three separate files — none flagged. audytx reads the chain.
# iam_role_developer.tf
resource "aws_iam_role" "developer" {
  name = "developer-role"
}

resource "aws_iam_role_policy" "developer_policy" {
  role = aws_iam_role.developer.id
  policy = jsonencode({
    Statement = [{
      Effect   = "Allow"
      Action   = ["lambda:InvokeFunction", "iam:PassRole"]
      Resource = "*"
    }]
  })
}
# lambda.tf
resource "aws_lambda_function" "updater" {
  function_name = "config-updater"
  role          = aws_iam_role.admin.arn  # ← receives the passed role
}

resource "aws_iam_role" "admin" {
  name = "admin-role"
}

resource "aws_iam_role_policy_attachment" "admin_policy" {
  role       = aws_iam_role.admin.name
  policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}
audytx finding: [CRIT] ATTACK_PATH_SEARCH — aws_iam_role.developer has iam:PassRole on aws_iam_role.admin (AdministratorAccess). Principal can invoke aws_lambda_function.updater to execute arbitrary code with admin permissions. Privilege-escalation path: developer → PassRole → admin → AdministratorAccess.

A pattern matcher scanning only iam_role_policy sees iam:PassRole as a potential concern but can't confirm the target role is privileged without reading iam_role_policy_attachment. audytx resolves the full chain across all three resources.

IAM security rules in audytx

audytx's IAM engine covers both direct misconfigurations and cross-resource attack paths. Rules fire when the configuration provides enough information to reason about them — missing attributes produce Incomplete (no false alarm from partial config).

ATTACK_PATH_SEARCH
Graph-reachability search across all roles, policies, trust relationships, and services. Finds PassRole chains, AssumeRole traversal, and service-linked escalation vectors.
Effective-permission expansion
Wildcards (iam:*, *) are expanded to concrete actions before reasoning. A principal with iam:* gets the same treatment as one with every individual IAM action listed.
Trust policy analysis
Who can assume each role? audytx checks both the principal list and aws:PrincipalAccount conditions — a cross-account trust to * is treated differently from a same-account service trust.
Context suppression
Not every high-privilege role is a finding. An admin role used only by a known-safe service with a narrowly-scoped trust policy may be suppressed — with the reasoning shown in the PR comment.

See the full IAM rule inventory on the features page and the raw benchmark data on the comparison page.

See IAM findings on your next PR

Install audytx on one repo. The next PR that touches an IAM resource gets the full attack-path analysis — PassRole chains, AssumeRole traversal, wildcard expansion — in the PR comment.

Install audytx free →

Or use the MCP server: claude mcp add --transport http audytx https://audytx.com/mcp