Skip to content

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 (errors invalid_principal / invalid_not_principal)
  • 5,120-byte size limit, enforced by the policy_size check (policy_size_exceeded)
  • Allow statements may use Condition, scoped resource ARNs, NotAction and NotResource — AWS Organizations supports the full IAM policy language in SCPs since 2025-09-19, so these are no longer flagged

RCP Rules

  • Effect must be Deny; Principal must be exactly "*"; NotAction / NotPrincipal are unsupported; Resource or NotResource is required
  • Actions must come from RCP-supported services (62 service prefixes as of 2026-09-17, e.g. s3, sts, kms, dynamodb, cloudfront, wafv2); bare "*" in Action is 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 no Condition blocks 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* on aws:PrincipalOrgID, aws:PrincipalOrgPaths, or aws:PrincipalAccount) without an aws:PrincipalIsAWSService carve-out can deny AWS service-to-service calls (e.g. CloudTrail log delivery) and break integrations.
  • rcp_carveout_missing_ifexists (medium): the service carve-out is written with Bool instead of BoolIfExists. aws:PrincipalIsAWSService is present only on signed requests, and a non-IfExists operator cannot match an absent key — so for an anonymous caller the whole Condition evaluates false and they fall outside the Deny, while StringNotEqualsIfExists on the organization boundary still matches them. Pair StringNotEqualsIfExists with BoolIfExists.

Pass Example (canonical identity perimeter)

{
  "Effect": "Deny",
  "Principal": "*",
  "Action": "s3:*",
  "Resource": "*",
  "Condition": {
    "StringNotEqualsIfExists": {
      "aws:PrincipalOrgID": "o-123456789"
    },
    "BoolIfExists": {
      "aws:PrincipalIsAWSService": "false"
    }
  }
}