Almost nobody writes an over-permissive IAM policy on purpose. It starts narrow, a deploy fails with AccessDenied, someone adds a wildcard to get the release out, and the wildcard stays. Six months later nobody remembers which action was needed.
These are the seven patterns I look for first in a policy review, roughly in order of how much damage they allow. If you want to check a policy as you read, paste it into the IAM Policy Analyzer. It runs in your browser, and the policy is not sent anywhere.
1. Every action on every resource
{ "Effect": "Allow", "Action": "*", "Resource": "*" }
This is AdministratorAccess under another name. Whoever holds it can do anything in the account, including changing their own permissions. It is occasionally right for a break-glass role. It is never right for an application or a pipeline.
2. Actions that grant more actions
This is the one that gets past reviewers, because the policy looks narrow:
{ "Effect": "Allow", "Action": ["iam:PassRole", "iam:AttachRolePolicy"], "Resource": "*" }
Two actions, and the holder can attach any policy to any role. The actions to watch for:
| Action | What it allows |
|---|---|
iam:PassRole | Hand a more powerful role to a service you control |
iam:AttachUserPolicy, AttachRolePolicy, AttachGroupPolicy | Attach any managed policy, including admin |
iam:PutUserPolicy, PutRolePolicy, PutGroupPolicy | Write an inline policy of your choosing |
iam:CreatePolicyVersion, SetDefaultPolicyVersion | Rewrite an existing policy |
iam:CreateAccessKey, CreateLoginProfile, UpdateLoginProfile | Obtain credentials for another user |
iam:UpdateAssumeRolePolicy | Let yourself assume another role |
iam:AddUserToGroup | Join a group with more access |
If a workload needs one of these, scope Resource to the specific roles or users and add a permissions boundary. Watch for patterns such as iam:Put*, which include several of them at once.
3. A whole service on every resource
s3:* or ec2:* on Resource: "*" includes the delete actions and the ones that change permissions. For IAM, KMS, STS, Organizations, and Secrets Manager, a service wildcard is close to account-wide control.
List the actions the workload calls. IAM Access Analyzer can generate that list from CloudTrail activity, which removes the usual excuse.
4. Allow with NotAction
{ "Effect": "Allow", "NotAction": "iam:*", "Resource": "*" }
This reads as "everything except IAM". It means every action in every other service, including the ones AWS releases next year. NotAction belongs in Deny statements. In an Allow, use Action and list what you need.
5. A public principal with no Condition
On a resource policy, such as a bucket policy or a role trust policy, "Principal": "*" means any AWS account, and for some services anonymous callers. Name the accounts or roles. If the statement must be broad, add a Condition such as aws:PrincipalOrgID.
6. Sensitive actions on every resource
secretsmanager:GetSecretValue on * reads every secret in the account. s3:GetObject on * reads every bucket. The action is specific and the resource is not, so the statement passes a quick review.
Replace * with the ARNs the workload uses. This single change removes most of the blast radius of a leaked credential.
7. A missing or old Version
Without "Version": "2012-10-17", AWS falls back to the 2008 policy language, in which policy variables like ${aws:username} are treated as literal text. A policy that appears to scope each user to their own folder then matches nothing, or the wrong thing.
What a document check cannot tell you
A check of the policy document finds these patterns. It does not evaluate Condition logic, permission boundaries, or service control policies, and it does not know what else is attached to the same role. Treat a clean result as "no common mistakes", and use IAM Access Analyzer when you need proof about what a principal can reach.
A review routine that takes ten minutes
- Search for
"*"in Action and Resource. Justify each one or remove it. - Search for the escalation actions in the table above.
- Check every resource policy for
"Principal": "*". - Compare the allowed actions with what CloudTrail shows the role has used in the last 90 days.