Most IAM pain comes from a short list of repeat offenders. Here they are.
1. Wildcards everywhere
"Action": "*" on "Resource": "*" is admin access. It sneaks into policies "just to get it working" and never gets removed. Enumerate specific actions and ARNs instead.
2. Forgetting the bucket ARN
S3 has two ARN shapes:
- the bucket:
arn:aws:s3:::my-bucket - the objects:
arn:aws:s3:::my-bucket/*
ListBucket needs the first; GetObject needs the second. List both or you will get mysterious AccessDenied errors.
3. Assuming Allow is enough
An explicit Deny always overrides an Allow. If access fails despite an Allow, hunt for a Deny — often in a Service Control Policy or permission boundary, not the policy you are editing.
4. Over-broad Principal
In resource-based policies, "Principal": "*" means the entire internet. Combine it with a Condition (source account, VPC, or org id) or scope it to specific role ARNs.
5. Skipping HTTPS enforcement
Add a deny for insecure transport to any bucket that matters:
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::b", "arn:aws:s3:::b/*"],
"Condition": { "Bool": { "aws:SecureTransport": "false" } }
}
Fix it fast
Load your policy into the workspace and ask "find and fix problems". The AI flags these exact issues and proposes a corrected version you can review as a diff.