Validation & operations
Common IAM security misconfigurations and how to fix them
A field guide to the mistakes that turn into incidents, each with the risky pattern, why it is dangerous, and the corrected policy.
Published · Updated .
The mistakes below show up again and again in real AWS accounts, and most of them pass syntax validation. Each entry shows the risky pattern (marked, with no copy button), why it is dangerous, and the fix.
1. Administrator access for workloads
Risky pattern, do not use: every action on every resource
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}Workload roles with AdministratorAccess turn any application vulnerability (SSRF, dependency compromise) into full account compromise. Fix: scope to the calls the workload makes; see least-privilege design.
2. iam:PassRole on every role
Risky pattern, do not use: can hand any role, including admin roles, to a service
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "*"
}
]
}A principal that can create a Lambda function or EC2 instance and pass any role to it can act with that role's permissions. This is a classic privilege-escalation path. Fix:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PassAppRolesToLambdaOnly",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::111122223333:role/app/*",
"Condition": {
"StringEquals": {
"iam:PassedToService": "lambda.amazonaws.com"
}
}
}
]
}3. Allow with NotAction
Risky pattern, do not use: grants every action not listed, including future ones
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"NotAction": "iam:*",
"Resource": "*"
}
]
}Intended as “everything except IAM”, this also grants every action AWS releases later, and organizations, sts and billing actions today. Fix: an explicit Allow list, or pair a broad managed policy with a Deny + NotAction guardrail where you need “all except”.
4. Public principals in Allow statements
Risky pattern, do not use: anyone, including anonymous callers, can read the bucket
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*"
}
]
}Unless S3 Block Public Access rejects it, this makes every object public. Fix: name specific principals; for websites, use CloudFront with Origin Access Control (example). If a public statement is truly intended, restrict it with conditions and keep the content non-sensitive.
5. Trust policies that trust too much
Risky pattern, do not use: any principal in the other account that its admins allow can assume the role
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::444455556666:root"
},
"Action": "sts:AssumeRole"
}
]
}Trusting an account root delegates the decision to that account. For your own accounts, trust the specific role. For third-party vendors, trusting their account is normal, but require an external ID (example). Fix for internal access:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TrustDeployPipelineRole",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::444455556666:role/DeployPipeline"
},
"Action": "sts:AssumeRole"
}
]
}6. Long-term access keys for workloads and humans
Access keys do not expire and leak through source control, CI logs and laptops. Fix: workloads on AWS use roles (instance profiles, execution roles, task roles); workloads outside AWS use IAM Roles Anywhere or OIDC federation; people use IAM Identity Center. Find old keys with the credential report:
aws iam generate-credential-report
aws iam get-credential-report --query Content --output text | base64 --decode > credential-report.csv
# review access_key_1_last_rotated / access_key_2_last_rotated and *_last_used_date columns7. Actions paired with the wrong resource type
Risky pattern, do not use: GetObject on the bucket ARN grants nothing, which leads people to widen it to *
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetObject"
],
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket"
}
]
}Fix: bucket-level actions on the bucket ARN, object actions on bucket/*, as in the read-only bucket example. The Service Authorization Reference lists the resource type for every action.
8. Delegated IAM administration without a boundary
Risky pattern, do not use: the holder can grant itself any permission
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:CreateRole",
"iam:AttachRolePolicy",
"iam:PutRolePolicy",
"iam:CreatePolicyVersion"
],
"Resource": "*"
}
]
}Combined like this, these actions let the holder escalate to administrator: create a role it can use, then attach or write any policy to it. iam:AttachRolePolicy, iam:PutRolePolicy or iam:CreatePolicyVersion on a role or policy the holder already uses is enough on its own. Fix: require a permissions boundary on every role the delegate creates or edits (example).
9. Service principals without source conditions
Risky pattern, do not use: the same service acting for another customer could match
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*"
}
]
}Fix: add aws:SourceArn (or aws:SourceAccount) so only your distribution, trail or topic matches; see the OAC example.
10. MFA conditions that skip access keys
Risky pattern, do not use: requests signed with access keys have no MFA key, so this Deny never applies to them
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "ec2:TerminateInstances",
"Resource": "*",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}Fix: use BoolIfExists, as AWS recommends (example).
Finding these in your accounts
- IAM Access Analyzer policy validation flags several of these patterns as security warnings (see the validation guide).
- External access findings reveal public and cross-account resource policies (items 4, 5 and 9).
- Unused access findings and last-accessed data reveal over-broad grants (items 1 and 3).
- The credential report reveals long-lived keys (item 6).