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 (26 service prefixes as of 2026-07-20, e.g. s3, sts, kms, dynamodb, codebuild, textract); 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.

Pass Example (canonical identity perimeter)

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