reference
IAM risk scanner
Cloud IAM is where a small mistake becomes a total compromise. The IAM risk scanner reads the IAM policy documents you commit — Terraform, CloudFormation, raw JSON, serverless configs — and flags the identity-and-access patterns that actually get people owned: loose GitHub OIDC trust, privilege-escalation paths, and admin-equivalent grants. 12 checks across three families.
It reads policy-as-code, not your live cloud
This scanner analyzes the IAM configuration in your repository. It does not connect to your AWS/GCP/Azure control plane, and it does not need read:orgor any elevated GitHub scope — just the read access every scan already has. (Organization MFA enforcement, which some tools fold into “IAM,” is a posturesignal here, not part of this scanner — see Posture score.) If it can't read the repo's files, the result is marked degraded rather than reported as a clean pass.
How it runs
From the repo tree, it selects files that look like they carry IAM policy — .tf, serverless.yml, and JSON/YAML whose path mentions iam/policy/role/trust/cloudformation/template — up to 80 files, 256 KB each. It extracts policy statements from each (JSON documents, Terraform jsonencode / aws_iam_policy_document / heredoc policies, and CloudFormation/SAM YAML), then runs the three check families over them. Findings deduct from a 100-point score: critical −20, high −10. The remaining score maps to a level: 90+ low, 70+ medium, 50+ high, below 50 critical.
GitHub OIDC trust
When a repo uses GitHub Actions OIDC to assume an AWS role, the role's trust policy is what decides who can assume it. A trust policy that is too loose lets workflows you do not control mint your credentials.
- critical
OIDC trust has no Condition block
iam-oidc-no-condition
- critical
Wildcard repo/org in the sub claim
iam-oidc-wildcard-repo
- critical
Trust accepts the pull_request context
iam-oidc-pull-request-trust
- high
Wildcard ref/environment in the sub claim
iam-oidc-wildcard-ref
Privilege escalation
These cross statements rather than auditing a policy in isolation — the IGA angle. They flag permission combinations that let a principal grant itself more than it started with.
- critical
iam:PassRole granted on Resource: *
iam-passrole-wildcard
- critical
iam:PassRole + compute creation in the same file
iam-passrole-with-create-compute
- critical
Policy can modify IAM policies on Resource: *
iam-self-managing
- high
sts:AssumeRole allowed without a Condition
iam-assume-role-no-condition
- high
Allow combined with NotAction (inverted logic)
iam-not-action-allow
Admin equivalents
The table-stakes wildcard checks — grants that are administrator-equivalent in practice even when they don't attach the AdministratorAccess policy by name.
- critical
Allow with Action: * and Resource: * (or 3+ service:* on Resource: *)
iam-action-resource-wildcard
- high
Sensitive service (iam/kms/secretsmanager/sts) with Resource: *
iam-sensitive-service-wildcard
- critical
Resource policy with a wildcard Principal and no Condition
iam-principal-wildcard
OIDC trust — vulnerable vs fixed
A trust policy with no condition can be assumed by any workflow on the public GitHub OIDC provider — including repos you don't own:
// FLAGGED: iam-oidc-no-condition (critical)
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity"
}Fixed — the sub claim is pinned to one repo and one ref, so only that workflow can assume the role:
// OK
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main"
}
}
}The same statement with a sub of repo:my-org/*:* trips iam-oidc-wildcard-repo; a sub ending in :pull_request trips iam-oidc-pull-request-trust (a fork PR could assume it); and one pinned to the repo but ending in :* trips iam-oidc-wildcard-ref.
Privilege escalation — vulnerable vs fixed
iam:PassRoleon every resource lets a principal hand any role to compute it controls — a documented escalation path:
// FLAGGED: iam-passrole-wildcard (critical)
{ "Effect": "Allow", "Action": "iam:PassRole", "Resource": "*" }Fixed — scoped to the exact role, and constrained to the service that may receive it:
// OK
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::123456789012:role/my-app-task-role",
"Condition": {
"StringEquals": { "iam:PassedToService": "ecs-tasks.amazonaws.com" }
}
}If the same file also grants compute creation (lambda:CreateFunction, ec2:RunInstances, ecs:RunTask, glue:CreateJob), that combination alone trips iam-passrole-with-create-compute. Granting IAM-mutating actions (iam:AttachRolePolicy, iam:PutRolePolicy, …) on Resource: * trips iam-self-managing— the principal can attach AdministratorAccess to itself.
Admin equivalents — vulnerable vs fixed
The classic full wildcard:
// FLAGGED: iam-action-resource-wildcard (critical)
{ "Effect": "Allow", "Action": "*", "Resource": "*" }Fixed — enumerate the actions and pin the resources:
// OK
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
}A resource policy whose Principal is "*" (or { "AWS": "*" }) with no Condition trips iam-principal-wildcard— anyone in any AWS account can act on the resource. Constrain it with the specific principals, or add a Condition such as aws:PrincipalOrgID.
Where this fits
IAM-in-code findings are computed dynamically, so they don't appear in the static rule catalog. For a one-line summary of every detector, see Detectors. For deep cross-account IAM analysis against your live cloud, a dedicated CSPM/CIEM tool goes further than policy-as-code static analysis can.
Found an IAM pattern we should catch (or a false positive)? Open an issue.