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:
--configflag (explicit path)- Current directory:
iam-validator.yaml,.iam-validator.yaml - Parent directories (walks up to root)
- 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_severitylists 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):
- Built-in defaults (from Python modules)
- Configuration file (YAML)
- Environment variables
- 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-typeis supplied on the CLI — that value is applied to every policy in the run, full stop. Auto-detection and thepolicy_types:glob mapping are skipped. Use this when your pipeline only ever validates one type of policy. - If
--policy-typeis 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 toTRUST_POLICY; any other Principal/NotPrincipal resolves toRESOURCE_POLICY; otherwiseIDENTITY_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/*.jsonalso matchesscp/org.jsonat the top of the scan. - First match wins; the list is only consulted when
--policy-typeis not provided on the CLI. - The list form above is required. A mapping (
"**/scp/*.json": SERVICE_CONTROL_POLICY) or an entry missingpattern/typeis 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.