Advanced Checks¶
These 4 checks provide advanced validation for condition enforcement, trust policies, confused deputy protection, and policy-type-specific rules.
action_condition_enforcement¶
Enforces required conditions for specific actions.
Severity: high
Why It Matters¶
Some actions are dangerous without proper conditions. For example, iam:PassRole without iam:PassedToService allows passing roles to any AWS service.
Configuration¶
action_condition_enforcement:
enabled: true
action_condition_requirements:
- actions: ["iam:PassRole"]
required_conditions:
- condition_key: "iam:PassedToService"
description: "Restrict which services can assume the role"
- actions: ["sts:AssumeRole"]
required_conditions:
- condition_key: "aws:SourceAccount"
description: "Restrict which accounts can assume the role"
Fail Example¶
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "*"
}
Error: Action iam:PassRole requires condition iam:PassedToService
Pass Example¶
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::*:role/lambda-*",
"Condition": {
"StringEquals": {
"iam:PassedToService": "lambda.amazonaws.com"
}
}
}
action_resource_matching¶
Validates actions are compatible with resource types.
Severity: medium
What It Checks¶
- Object actions (
s3:GetObject) used with object ARNs - Bucket actions (
s3:ListBucket) used with bucket ARNs - Service-specific resource type requirements
Fail Example¶
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket"
}
Error: s3:GetObject requires object ARN, got bucket ARN
Pass Example¶
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*"
}
trust_policy_validation¶
Validates IAM role trust policies for security best practices.
Severity: high (trust issues) / medium (confused deputy)
Opt-in Check
Trust policy validation is enabled when using --policy-type TRUST_POLICY. The validator auto-detects trust policies (containing sts:AssumeRole actions with Principal elements) and suggests using this flag.
What It Checks¶
- Action-Principal type matching - correct principal types for each assume action
- Provider ARN validation - SAML and OIDC provider ARN format
- Required conditions - SAML:aud, OIDC audience/subject conditions
- Confused deputy prevention - service principals without source restrictions
Confused Deputy Detection¶
When a trust policy allows a service principal to assume a role without aws:SourceArn or aws:SourceAccount conditions (or the org-wide aws:SourceOrgID / aws:SourceOrgPaths), any resource using that service could assume the role - not just the intended one. This is the confused deputy problem.
Any one of aws:SourceArn, aws:SourceAccount, aws:SourceOrgID, or aws:SourceOrgPaths counts as protection. aws:SourceOrgID is the scalable choice when many accounts in one AWS Organization are involved.
Vulnerable Example¶
{
"Effect": "Allow",
"Principal": {
"Service": "sns.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
Warning: Statement allows service principal sns.amazonaws.com without an aws:SourceArn, aws:SourceAccount, aws:SourceOrgID, or aws:SourceOrgPaths condition. This may be vulnerable to confused deputy attacks.
Secure Example¶
{
"Effect": "Allow",
"Principal": {
"Service": "sns.amazonaws.com"
},
"Action": "sts:AssumeRole",
"Condition": {
"ArnLike": {
"aws:SourceArn": "arn:aws:sns:us-east-1:123456789012:my-topic"
},
"StringEquals": {
"aws:SourceAccount": "123456789012"
}
}
}
Safe Services (Not Flagged)¶
Only services where the role is directly bound to a compute resource owned by the account are exempt from confused deputy checks:
| Service | Reason |
|---|---|
ec2.amazonaws.com |
Instance profile bound to account-owned instance |
lambda.amazonaws.com |
Execution role bound to account-owned function |
edgelambda.amazonaws.com |
Lambda@Edge, same model as Lambda |
All Other Services Require Conditions
All other AWS service principals -- including services that typically use service-linked roles (e.g., guardduty, elasticloadbalancing, organizations) -- require aws:SourceArn or aws:SourceAccount conditions when used in custom trust policies. If a customer writes a custom trust policy for any of these services, the confused deputy risk applies to that custom role.
Required Conditions per Assume Action¶
Each sts: assume action carries its own rule. A glob action (sts:*, sts:AssumeRole*)
is validated against every rule it covers, because it grants all of them.
| Action | Principal types | Required conditions |
|---|---|---|
sts:AssumeRole |
AWS, Service | -- |
sts:AssumeRoleWithSAML |
Federated | SAML:aud |
sts:AssumeRoleWithWebIdentity |
Federated | <provider>:aud and one of <provider>:sub / <provider>:amr |
sts:TagSession |
AWS, Service, Federated | -- |
sts:SetSourceIdentity |
AWS, Service, Federated | -- |
A list inside required_conditions is an "any one of" group. Provider-scoped keys are
written *:aud in configuration and rendered against the statement's own provider
(token.actions.githubusercontent.com:aud) in the finding.
Negated operators (StringNotEquals) on an Allow, and Null conditions asserting a
key is absent, do not count as constraining the key.
Overriding or Opting Out of the Rules¶
validation_rules replaces DEFAULT_RULES wholesale, so it is the opt-out for any
individual requirement -- redefine the action with only the parts you want enforced:
checks:
trust_policy_validation:
enabled: true
validation_rules:
sts:AssumeRoleWithWebIdentity:
allowed_principal_types: ["Federated"]
# `*:aud` only; drops the `*:sub` / `*:amr` requirement
required_conditions: ["*:aud"]
Omit required_conditions entirely to enforce no conditions for that action, and omit
provider_pattern to skip provider-ARN format validation. Actions left out of
validation_rules are not validated at all.
Trust Policy Types¶
AWS Service¶
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
Cross-Account¶
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "unique-external-id"
}
}
}
]
}
OIDC (GitHub Actions)¶
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:org/repo:*"
}
}
}
]
}
Policy Type Validation¶
The validator supports different policy types and validates policies match their declared type.
Policy Types¶
| Type | Principal | Use Case |
|---|---|---|
IDENTITY_POLICY |
Not allowed | User/role policies |
RESOURCE_POLICY |
Required | S3, SQS, etc. |
TRUST_POLICY |
Required | Role trust |
SERVICE_CONTROL_POLICY |
Not allowed | AWS Organizations |
RESOURCE_CONTROL_POLICY |
Required | AWS Organizations |
SCP Rules¶
- No
Principal/NotPrincipal(errorsinvalid_principal/invalid_not_principal) - 5,120-byte size limit, enforced by the
policy_sizecheck (policy_size_exceeded) - Allow statements may use
Condition, scoped resource ARNs,NotActionandNotResource— AWS Organizations supports the full IAM policy language in SCPs since 2025-09-19, so these are no longer flagged
RCP Rules¶
Effectmust beDeny;Principalmust be exactly"*";NotAction/NotPrincipalare unsupported;ResourceorNotResourceis required- Actions must come from RCP-supported services (26 service prefixes as of
2026-07-20, e.g.
s3,sts,kms,dynamodb,codebuild,textract); bare"*"inActionis rejected - New AWS launches can be accepted without a validator upgrade:
policy_type_validation:
additional_rcp_services:
- "newservice"
Policies that match the customer-RCP shape but were not declared as RCPs get
an info-level policy_type_hint (RCPs cannot be auto-detected).
Configuration¶
# Validate as resource policy
iam-validator validate --path bucket-policy.json --policy-type RESOURCE_POLICY
# Validate as trust policy
iam-validator validate --path trust-policy.json --policy-type TRUST_POLICY
rcp_best_practices¶
Severity: medium | Default: enabled (runs only for RESOURCE_CONTROL_POLICY)
Best-practice guidance for RCP deny statements, grounded in AWS's official example RCPs (aws-samples/resource-control-policy-examples and the data-perimeter identity-perimeter RCP).
What It Checks¶
rcp_blanket_deny(low): a Deny statement with noConditionblocks the listed actions for ALL principals — including your organization's own admins. Legal (AWS's Block-Public-Access-protection example does this), but worth confirming intent.rcp_missing_service_carveout(medium): a Deny that restricts principals to the organization (StringNotEquals*onaws:PrincipalOrgID,aws:PrincipalOrgPaths, oraws:PrincipalAccount) without anaws:PrincipalIsAWSServicecarve-out can deny AWS service-to-service calls (e.g. CloudTrail log delivery) and break integrations.
Pass Example (canonical identity perimeter)¶
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"StringNotEqualsIfExists": {
"aws:PrincipalOrgID": "o-123456789"
},
"BoolIfExists": {
"aws:PrincipalIsAWSService": "false"
}
}
}