Skip to content

Configuration

IAM Policy Validator works with sensible defaults but supports full customization through YAML configuration files.

Quick Start

No configuration needed! The validator works out-of-the-box.

To customize, create iam-validator.yaml:

settings:
  fail_on_severity: [error, critical, high]

wildcard_action:
  severity: critical

Configuration File Discovery

The validator automatically searches for configuration in this order:

  1. --config flag (explicit path)
  2. Current directory: iam-validator.yaml, .iam-validator.yaml
  3. Parent directories (walks up to root)
  4. Home directory

Settings

fail_on_severity

Control which severities cause validation failures:

settings:
  fail_on_severity: [error, critical, high]

Severity Levels:

Category Levels
IAM Validity error, warning, info
Security critical, high, medium, low

Presets

# Strict - fail on everything
fail_on_severity: [error, warning, info, critical, high, medium, low]

# Default - errors plus high-impact security findings
fail_on_severity: [error, critical, high]

# Serious issues only
fail_on_severity: [error, critical]

# Relaxed - IAM errors only
fail_on_severity: [error]

hide_severities

Remove specific severity levels from the run to cut noise:

settings:
  # Hide low and info severity findings globally
  hide_severities: [low, info]

A hidden severity is dropped completely, not just collapsed or muted:

  • It does not appear in any output — console, JSON/SARIF/CSV/HTML, GitHub PR comments and review comments, PR labels, the job summary.
  • It is not counted in any total (total_issues, the per-severity counts).
  • It is not part of the pass/fail decision, so hiding a severity that fail_on_severity lists stops it from failing the run.

Hiding a severity also stops it failing the build

hide_severities and fail_on_severity are independent settings, and hiding wins: filtering happens before the report is generated. If you want the finding to stay quiet but still gate the merge, lower its severity with a per-check severity: override instead of hiding it.

This applies to iam-validator analyze as well: Access Analyzer findings map onto the same error / warning / info severities, and the command reads hide_severities from the config file it is given with --config.

The one exception is check_execution_error — see on_check_error below.

Per-check override: You can also set hide_severities on individual checks to override the global setting:

settings:
  hide_severities: [info] # Global: hide info

wildcard_resource:
  # Override: hide low severity for this check only
  # (useful when conditions reduce risk to LOW)
  hide_severities: [low]

suppress_superseded_findings

When a statement contains Action: "*" and Resource: "*" with no conditions, every other check produces a redundant finding — the root cause and the fix are always the same: scope down the wildcard. This setting collapses all of that noise into one critical finding from full_wildcard.

settings:
  suppress_superseded_findings: true # default: true

What gets suppressed

When full_wildcard fires on a statement, all other findings for that statement only are dropped — both statement-level checks (built-in and custom) and policy-level check findings that reference that statement index. The suppressed check IDs are listed inside the full_wildcard finding message so nothing is lost silently:

Statement allows all actions on all resources - CRITICAL SECURITY RISK

**20 checks suppressed** for this statement (abac_enforcement,
action_condition_enforcement, action_resource_matching, …).
Scope the statement and re-run to see remaining findings.

What is never suppressed

Statement Suppression
Deny */* No — full_wildcard only fires on Allow
Allow NotAction: "*" No — inverted semantics require full analysis
Sibling statements in the same policy No — only the */* statement is short-circuited
full_wildcard itself Never suppressed

Conditions do not prevent suppression

Allow */* + Condition: {...} is still treated as a full-wildcard statement. The condition does not change the root cause or the fix, so suppression still applies. The full_wildcard finding lists all suppressed checks so nothing is lost silently.

Custom checks

Custom checks loaded via --custom-checks-dir are suppressed automatically — no changes to the check code required.

Opt out

settings:
  suppress_superseded_findings: false

PR comment fingerprint churn

Switching from false to true (or vice versa) causes a one-time churn of existing PR comments anchored to the now-suppressed (or now-visible) check IDs. Re-run the validator once after changing this setting to refresh comment state.

on_check_error

A check that raises an exception returns no findings, so the statement it was given went unvalidated by that check. By default the validator reports this as a check_execution_error finding at severity error, which fails a run gated on error — a run that cannot validate a policy must not report it clean.

settings:
  on_check_error: fail # default: fail

The finding names the check and the exception, and is attributed to the failing check's check_id. It deliberately ignores that check's ignore_patterns and severity overrides: the finding is about the check, not from it, so the check cannot silence the notice that it crashed. This also means a check_execution_error finding is exempt from hide_severities (global or per-check) and from ignore_patterns more generally — a run that cannot finish validating a policy must not be silently filtered into looking clean.

Opt out

settings:
  on_check_error: warn

warn logs the failure and drops it, so a policy can pass with a check's findings missing. This was the behaviour before the setting existed.

Custom checks

A bug in a custom check now fails the run instead of degrading quietly. If you load third-party checks you do not control and would rather not gate on their stability, set on_check_error: warn and watch the logs.

Check Configuration

Disable a Check

policy_size:
  enabled: false

Change Severity

wildcard_action:
  severity: critical

Custom Messages

wildcard_action:
  message: "Wildcard actions violate security policy SEC-001"
  suggestion: |
    Replace with specific actions.
    Contact security@company.com for guidance.

Action Condition Enforcement

Require specific conditions for sensitive actions:

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"

Principal Validation

For resource policies and trust policies, validate Principal elements:

principal_validation:
  enabled: true

  # Block wildcard principal entirely (default: false)
  # When false: allows "*" if appropriate conditions are present
  # When true: blocks "*" regardless of conditions
  block_wildcard_principal: false

  # Block {"Service": "*"} patterns (default: true)
  # This is a dangerous pattern that allows ANY AWS service
  block_service_principal_wildcard: true

  # Explicit block list (evaluated after service principal wildcard check)
  blocked_principals:
    - "arn:aws:iam::*:root"

  # Whitelist mode (when set, only these principals are allowed)
  allowed_principals:
    - "arn:aws:iam::123456789012:*"

  # Service principals whitelist (supports glob patterns)
  allowed_service_principals:
    - "aws:*" # All AWS service principals

Principal Condition Requirements

Require specific conditions when certain principals are used:

principal_validation:
  principal_condition_requirements:
    # Require source verification for wildcard principals
    - principals: ["*"]
      required_conditions:
        any_of: # At least ONE must be present
          - condition_key: "aws:SourceArn"
          - condition_key: "aws:SourceAccount"

    # Require MFA for root account access
    - principals: ["arn:aws:iam::*:root"]
      required_conditions:
        all_of: # ALL must be present
          - condition_key: "aws:MultiFactorAuthPresent"
            expected_value: true

    # Forbid specific conditions
    - principals: ["*"]
      required_conditions:
        none_of: # NONE should be present
          - condition_key: "aws:SecureTransport"
            expected_value: false

Use Cases

Strict mode (block all wildcards):

principal_validation:
  block_wildcard_principal: true
  block_service_principal_wildcard: true

Permissive mode (allow wildcards with conditions):

principal_validation:
  block_wildcard_principal: false
  principal_condition_requirements:
    - principals: ["*"]
      required_conditions:
        any_of:
          - condition_key: "aws:SourceArn"
          - condition_key: "aws:SourceAccount"
          - condition_key: "aws:PrincipalOrgID"

Custom Checks

Load custom checks from a directory. custom_checks_dir is a top-level key, not a settings field — nested under settings: it is silently ignored:

custom_checks_dir: "./my-checks"

checks:
  my_custom_check:
    enabled: true
    severity: high

Environment Variables

The validator supports environment variables for configuration:

Variable Description Example
IAM_VALIDATOR_CONFIG Path to configuration file /etc/iam-validator/config.yaml
IAM_VALIDATOR_MCP_INSTRUCTIONS Custom instructions for MCP server "Require MFA for all actions"
AWS_REGION AWS region for Access Analyzer us-east-1
AWS_PROFILE AWS profile for credentials production

Configuration Precedence

Configuration is applied in this order (later overrides earlier):

  1. Built-in defaults (from Python modules)
  2. Configuration file (YAML)
  3. Environment variables
  4. CLI arguments (highest priority)

Complete Configuration Reference

Global Settings

All settings under the settings key:

settings:
  # Validation behavior
  parallel_execution: true # Enable parallel execution of checks (default: true)
  # Max policies validated concurrently (default: 10); values below 1 are
  # clamped to 1 with a warning. There is no CLI flag for this — only the SDK's
  # `validate_policies()` argument or this setting can override it.
  max_concurrency: 10

  # Failure criteria
  fail_on_severity: # Severities that cause exit code 1
    - error # IAM validity errors
    - critical # Critical security issues
    - high # High severity security issues
    # - medium                  # Uncomment to fail on medium
    # - warning                 # Uncomment to fail on warnings

  # Output filtering
  hide_severities:
    null # Hide these severities from output
    # Example: [low, info]

  # Noise reduction
  suppress_superseded_findings: true # Collapse */* noise into one finding (default: true)

  # What to do when a check raises instead of returning findings
  # fail: report a check_execution_error finding (default) | warn: log only
  on_check_error: fail

  # AWS service definitions
  aws_services_dir: null # Path to offline service definitions
  cache_enabled: true # Cache AWS definitions (default: true)
  cache_ttl_hours: 168 # Cache TTL in hours (default: 7 days)

  # Template variable support
  allow_template_variables: true # Support ${var.name} in ARNs

  # GitHub integration
  # Labels are reconciled on every run: a severity that is still found gets
  # its label added, a severity that is no longer found gets it removed.
  # Label names may contain any character GitHub allows, "/" and "#" included.
  severity_labels: # Map severities to PR labels
    error: "iam-validity-error"
    critical: "iam-security-critical"
    high: "iam-security-high"

  # Optional run scope tag (1-32 chars, [A-Za-z0-9._-]) appended to PR
  # summary, review, analyzer, and ignored-findings markers. Set this
  # when you run the validator multiple times against the same PR (e.g.
  # one run per policy type) so each run keeps its own comment thread
  # instead of overwriting the others. Unset by default.
  comment_tag: null

  # Ignore settings
  ignore_settings:
    enabled: true
    allowed_users: [] # Users allowed to add ignore comments
    post_denial_feedback: false # Post feedback on denied ignores

  # Documentation
  documentation:
    base_url: null # Custom docs base URL
    include_aws_docs: true # Include links to AWS docs

# Custom checks: a TOP-LEVEL key, not a settings field — see "Custom Checks" above
custom_checks_dir: null # Auto-discover checks from directory

Check Configuration

Each check can be configured at the top level using its check_id:

# Common options for all checks
<check_id>:
  enabled: true # Enable/disable check (default: true)
  severity: medium # Override default severity
  description: "Custom desc" # Override description
  message: "Custom message" # Override issue message
  suggestion: "How to fix" # Override suggestion text
  hide_severities: [low] # Per-check severity filtering

  # Ignore patterns (available for ALL checks)
  ignore_patterns:
    # Ignore by file path (regex)
    - filepath: "^test/.*"

    # Ignore by action (regex)
    - action: "^s3:Get.*"

    # Ignore by resource ARN (regex)
    - resource: "arn:aws:s3:::.*-test-.*"

    # Ignore by statement SID
    - sid: "AllowReadOnlyAccess"

    # Combine conditions (AND logic)
    - filepath: "^dev/.*"
      action: "^s3:.*"

    # Lists within patterns (OR logic)
    - filepath:
        - "^test/.*"
        - "^examples/.*"

Built-in Checks

All 22 built-in checks with their default settings:

AWS Validation Checks

Check ID Default Severity Description
action_validation error Actions exist in AWS services
condition_key_validation error Condition keys are valid for actions
condition_type_mismatch error Operator-value type match + format validation
resource_validation error ARN format is valid
principal_validation high Principal format (resource policies)
policy_structure error Required fields, valid values, version check
policy_size error Policy size limits (including SCP-specific)
sid_uniqueness error SIDs are unique across statements
set_operator_validation warning ForAllValues/ForAnyValue used correctly
mfa_condition_antipattern warning MFA anti-patterns detected
trust_policy_validation high Trust policy structure + confused deputy
not_principal_validation warning NotPrincipal usage patterns
action_resource_matching medium Actions match resource types
policy_type_validation error Policy matches declared type

Security Best Practices Checks

Check ID Default Severity Description
wildcard_action medium Action: "*" detection
wildcard_resource medium Resource: "*" detection
full_wildcard critical Action + Resource: "*" (admin access)
service_wildcard high s3:* style wildcards
sensitive_action medium 490+ privilege escalation actions
action_condition_enforcement high Sensitive actions require conditions
not_action_not_resource high Dangerous NotAction/NotResource

Check-Specific Options

wildcard_resource

wildcard_resource:
  enabled: true
  severity: medium
  # Actions allowed with Resource: "*"
  allowed_wildcards:
    - "ec2:Describe*"
    - "s3:List*"
    - "iam:Get*"
    - "cloudwatch:Get*"

service_wildcard

service_wildcard:
  enabled: true
  severity: high
  # Services allowed to use wildcards
  allowed_services:
    - "logs"
    - "cloudwatch"
    - "xray"

sensitive_action

sensitive_action:
  enabled: true
  severity: medium
  # Filter by category
  categories:
    - credential_exposure
    - priv_esc
    - data_access
    - resource_exposure
  # Category-specific severities
  category_severities:
    credential_exposure: high
    priv_esc: critical

action_condition_enforcement

action_condition_enforcement:
  enabled: true
  severity: high
  # Custom requirements (see full-reference-config.yaml)
  requirements:
    - actions: ["iam:PassRole"]
      required_conditions:
        - condition_key: "iam:PassedToService"
          description: "Restrict which services can use the role"

principal_validation

principal_validation:
  enabled: true
  severity: high
  block_wildcard_principal: false
  block_service_principal_wildcard: true
  blocked_principals: []
  allowed_principals: []
  allowed_service_principals:
    - "aws:*"
  # Condition requirements for principals
  principal_condition_requirements:
    - principals: ["*"]
      required_conditions:
        any_of:
          - condition_key: "aws:SourceArn"
          - condition_key: "aws:SourceAccount"

trust_policy_validation

trust_policy_validation:
  enabled: true
  severity: high

The confused deputy detection is built-in and automatically checks service principals in trust policies for missing aws:SourceArn or aws:SourceAccount conditions. Only compute-bound services (ec2, lambda, edgelambda) are automatically excluded.

Opt-in Check

Trust policy validation is enabled when using --policy-type TRUST_POLICY. The validator auto-detects trust policies and suggests using this flag.

not_principal_validation

not_principal_validation:
  enabled: true
  severity: warning

Detects NotPrincipal usage patterns: NotPrincipal with Effect: Allow is flagged as an error (not supported by AWS), while NotPrincipal with Effect: Deny is flagged as a warning (valid but deprecated).

policy_size

policy_size:
  enabled: true
  severity: error
  # Pins every policy in the run to one limit; omit to follow the policy type
  # policy_type: inline_user # managed, inline_user, inline_group, inline_role, inline_role_trust, scp, rcp
  # scp/rcp: count the .json file as written (default) or minified
  organizations_measurement: as_written # as_written | compact
  # Override default size limits (replaces the whole map — list every key)
  size_limits:
    managed: 6144
    inline_user: 2048
    inline_group: 5120
    inline_role: 10240
    inline_role_trust: 2048
    scp: 10240
    rcp: 5120

SCP and RCP size validation

With --policy-type SERVICE_CONTROL_POLICY the SCP limit (10,240 bytes) applies, and with RESOURCE_CONTROL_POLICY the RCP limit (5,120 bytes). Both are measured as written from a .json file. See policy_size.

Policy Type Resolution

When the validator runs, each policy file gets a resolved PolicyType that drives type-specific checks (for example policy_size limits and the policy_type_validation rules). There are two mutually exclusive modes:

  • If --policy-type is supplied on the CLI — that value is applied to every policy in the run, full stop. Auto-detection and the policy_types: glob mapping are skipped. Use this when your pipeline only ever validates one type of policy.
  • If --policy-type is omitted — the type is resolved per file in this priority order:
  • policy_types: glob mapping in the config (first match wins).
  • Content auto-detection: a trust-shaped statement (Principal + exact sts:AssumeRole* action + Effect: Allow + no specific resource ARN) resolves to TRUST_POLICY; any other Principal/NotPrincipal resolves to RESOURCE_POLICY; otherwise IDENTITY_POLICY.
  • Default fallback: IDENTITY_POLICY.

SCP and RCP cannot be auto-detected from content alone (they look structurally identical to identity/resource policies). Use the glob mapping or the explicit flag to drive those types. As a safety net, a policy whose statements all match the customer-RCP shape (Deny + Principal: "*" + service-prefixed actions + Resource: "*") gets an info-level policy_type_hint recommending RESOURCE_CONTROL_POLICY.

policy_types: — per-file glob mapping

policy_types:
  - pattern: "**/scp/*.json"
    type: SERVICE_CONTROL_POLICY
  - pattern: "**/rcp/*.json"
    type: RESOURCE_CONTROL_POLICY
  - pattern: "**/trust-policies/*.json"
    type: TRUST_POLICY
  • Patterns are matched against the POSIX form of each policy file path.
  • A leading **/ is stripped automatically so **/scp/*.json also matches scp/org.json at the top of the scan.
  • First match wins; the list is only consulted when --policy-type is not provided on the CLI.
  • The list form above is required. A mapping ("**/scp/*.json": SERVICE_CONTROL_POLICY) or an entry missing pattern/type is rejected with a warning naming the entry — it does not silently fall back to auto-detection.

Declaring the type also fixes the size limit

An undeclared SCP resolves to IDENTITY_POLICY and an undeclared RCP to RESOURCE_POLICY, so both are measured against the managed-policy size limit instead of their own. See policy_size.

Debugging the resolved type

Run with --log-level debug (or --verbose) to see exactly what type each policy got and why:

policy_type=TRUST_POLICY source=cli-flag file=trust.json
policy_type=SERVICE_CONTROL_POLICY source=config-glob pattern_present=true pattern_len=14 file=org.json
policy_type=RESOURCE_POLICY source=auto-detect file=s3-bucket.json
policy_type=IDENTITY_POLICY source=default file=ro.json

Only the file basename is logged (not the absolute path) and the config-glob line reports pattern_present=true plus pattern_len=<n> instead of the raw glob — this keeps the debug output free of user-controlled content while still letting you grep by source: iam-validator validate ... --verbose 2>&1 | rg 'source='. The glob itself is already visible in your own iam-validator.yaml, so there is no information loss for auditing.

Full Reference

See examples/configs/full-reference-config.yaml for all available options with detailed comments.