Skip to main content

How to Run an IAM Security Assessment

Evaluate AWS IAM configuration against 38 controls covering identity, credential lifecycle, privilege escalation, and monitoring.

Prerequisites

  • Stave binary built (cd stave && make build)
  • IAM observations in obs.v0.1 format (see observation shapes below)

1) Extract IAM observations

Use an extractor to produce obs.v0.1 JSON from AWS IAM APIs. The extractor must call:

AWS APIWhat it producesControls that need it
iam list-users + get-credential-reportUser metadata, last activityCRED.UNUSED, CRED.ROTATION, ACCOUNT.INACTIVE
iam list-attached-user-policies + get-policy-versionPolicy documentsPOLICY.ADMIN, POLICY.ESCALATION, PASSROLE, COMPLEXITY
iam list-mfa-devices + list-virtual-mfa-devicesMFA device typesROOT.MFA, CONSOLE.MFA, MFA.HWKEY
iam list-rolesRole metadata + boundaryBOUNDARY
iam get-account-password-policyPassword policyPASSWORD.LENGTH, PASSWORD.REUSE, PASSWORD.COMPLEXITY
accessanalyzer list-analyzersAnalyzer status per regionANALYZER
organizations list-policiesSCP listingSCP.FULLACCESS
logs describe-metric-filtersCloudWatch metric filtersCLOUDWATCH.MONITOR.MFADEVICE

2) Evaluate observations against the IAM control pack

stave apply \
--controls controls/iam \
--observations ./iam-obs/ \
--max-unsafe 168h \
--now 2026-01-15T00:00:00Z \
--format json > iam-findings.json

Exit code 3 means violations found.

3) What the IAM pack covers

The iam pack evaluates 38 controls across these categories:

CategoryControlsWhat they detect
Root accountROOT.MFA, ROOT.ACCESSKEY, ROOT.HWMFA, ROOT.USAGERoot MFA, access keys, hardware MFA, usage monitoring
Console accessCONSOLE.MFA, MFA.HWKEYConsole MFA, hardware MFA for admin users
CredentialsCRED.ROTATION, CRED.UNUSED, CRED.UNUSED45, CRED.SETUPKEY, CRED.SINGLEKEY, CRED.EXPIRY, ACCOUNT.INACTIVEKey rotation, unused credentials, credential TTL, inactive accounts
PoliciesPOLICY.ADMIN, POLICY.INLINE, POLICY.DIRECT, POLICY.CLOUDSHELL, POLICY.ESCALATION, POLICY.PASSROLE, POLICY.COMPLEXITY, POLICY.ASSUMEROLE, POLICY.SODFull admin, inline policies, self-modification, PassRole/AssumeRole scoping, complexity, separation of duties
PermissionsBOUNDARY, SCP.FULLACCESS, ROLE.BREAKGLASS, CROSS.ENV, TRUST.EXTERNALIDPermissions boundaries, org guardrails, break-glass, cross-env, external ID
Zero TrustZT.PERIMETER, ZT.SHORTLIVEDIdentity-based access required, short-lived credentials
Cross-cloudCROSSCLOUD.ADMIN, CROSSCLOUD.MFANo admin access across any cloud, MFA enforced everywhere
PasswordPASSWORD.LENGTH, PASSWORD.REUSE, PASSWORD.COMPLEXITYPassword policy requirements
InfrastructureANALYZER, SUPPORT, CERT.EXPIREDAccess Analyzer, support role, certificate expiry
MonitoringCLOUDWATCH.MONITOR.MFADEVICEMFA device enrollment monitoring

4) New controls added for IAM failure requirements

Ten controls address gaps identified in IAM failure analysis and CSA Top Threats 2025:

ControlRequirementWhat it detects
CTL.IAM.POLICY.ESCALATION.001Privilege escalationPolicies granting iam:CreatePolicyVersion, iam:AttachRolePolicy on self
CTL.IAM.POLICY.PASSROLE.001Privilege escalationiam:PassRole with wildcard Resource *
CTL.IAM.BOUNDARY.001Excessive permissionsIAM roles without permissions boundary
CTL.IAM.POLICY.COMPLEXITY.001Poor policiesPolicy documents with >25 statements
CTL.IAM.MFA.HWKEY.001SMS 2FA insufficientAdmin users without hardware MFA
CTL.CLOUDWATCH.MONITOR.MFADEVICE.001Unauthorized MFANo metric filter for MFA device changes
CTL.IAM.ACCOUNT.INACTIVE.001Legacy accountsAccounts inactive for 90+ days
CTL.IAM.CRED.EXPIRY.001CSA TT2 (Snowflake)Credentials without defined TTL
CTL.IAM.ROLE.BREAKGLASS.001CSA TT1 (drift)Break-glass elevated roles persisting >7 days
CTL.IAM.CROSS.ENV.001CSA TT1 (Microsoft)Non-production roles with production resource access
CTL.IAM.POLICY.SOD.001CSA P2-01 (CCM IAM-09)Roles with both data access and IAM management

5) Shadow Admin detection — the entropy family

Five controls under controls/iam/entropy/ plus two compound chains detect the pattern where a role accumulates permissions far beyond its declared scope:

ControlDetectsReads (collector pre-computes)
CTL.IAM.ROLE.PERMISSIONDRIFT.001Drift past threshold over a role active >90 dayspermission_drift.threshold_exceeded (accounts for stave/permission-drift-threshold tag override)
CTL.IAM.ROLE.CATEGORYMIX.001Incompatible permission category pair (e.g. data_read + secrets_access)permission_categories.has_incompatible_categories
CTL.IAM.ROLE.INTENTTAG.001Role missing the role-type tagtags.role-type absent
CTL.IAM.ROLE.INTENTMISMATCH.001Actual permissions contradict declared role-typeintent_match.has_intent_mismatch
CTL.IAM.ROLE.ENTROPY.INCOMPLETE.001Access Advisor data absentaccess_advisor.available == false

Compound chains compose them:

  • shadow_admin_by_accumulation — drift + categorymix + intent mismatch, threshold 2 of 3, CRITICAL.
  • privilege_creep_lateral_movement — categorymix + drift, threshold 2 of 2, CRITICAL.

The collector consults two taxonomy files when stamping the pre-computed booleans:

  • internal/controldata/taxonomy/permission_categories.yaml — 11 categories + the incompatible-pair matrix.
  • internal/controldata/taxonomy/role_type_matrix.yaml — 8 role types with allowed/forbidden category lists.

Worked demo: examples/shadow-admin-detection/run.sh walks a 2-year-old S3-ReadOnly-tagged role that has accumulated secrets-access and compute-invoke permissions. See examples/shadow-admin-detection/multi-engine-results.md for the CEL + chain + Clingo + Soufflé output.

6) Observation shapes

User with privilege escalation risk

{
"id": "iam-user-dev",
"type": "aws_iam_user",
"vendor": "aws",
"properties": {
"identity": {
"kind": "user",
"policies": {
"has_admin_access": false,
"has_self_modify": true,
"passrole_unrestricted": true,
"statement_count": 30
},
"mfa": { "hardware_mfa": false },
"credentials": { "inactive_days": 5 }
}
}
}

Triggers: ESCALATION.001 (self-modify), PASSROLE.001 (wildcard), COMPLEXITY.001 (30 > 25).

Role without permissions boundary

{
"id": "iam-role-app-service",
"type": "aws_iam_role",
"vendor": "aws",
"properties": {
"identity": {
"kind": "role",
"role": { "has_permissions_boundary": false }
}
}
}

Triggers: BOUNDARY.001.

MFA monitoring observation

{
"id": "cloudwatch-us-east-1",
"type": "aws_cloudwatch_account",
"vendor": "aws",
"properties": {
"monitoring": {
"kind": "account",
"metric_filters": {
"mfa_device_changes": { "exists": false }
}
}
}
}

Triggers: CLOUDWATCH.MONITOR.MFADEVICE.001.

Notes

  • IAM and S3 controls coexist without conflict when evaluated together.
  • For HIPAA compliance, use --profile hipaa which includes IAM controls for MFA (§164.312(d)) and inactive accounts (§164.312(a)(2)(i)).