How to Fix AWS IAM AccessDenied on s3:GetObject Caused by Wildcard Policy Misconfiguration
Threat/Impact Level: CRITICAL | Exploitability/Downtime Risk: HIGH | Time to Fix: 15 mins
TL;DR
- What broke: An IAM policy with
"Action": "*"or"*:*"is either being overridden by an explicit Deny in an SCP/permission boundary, or the wildcard is syntactically malformed — either way,s3:GetObjectis denied and your application is dead in the water. - How to fix it: Replace the wildcard action block with an explicit, scoped least-privilege statement targeting only the required S3 actions and the specific bucket ARN.
- Fast path: Use our Client-Side Sandbox below to auto-refactor this — paste your policy JSON and get a hardened replacement without sending your ARNs to a third-party server.
The Incident (What does the error mean?)
Raw error from CloudTrail or application logs:
An error occurred (AccessDenied) when calling the GetObject operation:
User: arn:aws:iam::123456789012:role/my-app-role is not authorized
to perform: s3:GetObject on resource:
arn:aws:s3:::my-prod-bucket/configs/app.json
with an explicit deny in a service control policy
Your application cannot read from S3. If this is a config file fetch at startup, your service is failing its health checks and is either crash-looping or serving stale/no data. If it's a data pipeline, your ETL is silently dropping records.
The presence of "Action": "*" in an identity policy does not override an explicit Deny anywhere in the policy evaluation chain (SCP → Permission Boundary → Identity Policy → Resource Policy). AWS evaluation logic is: explicit Deny always wins.
The Attack Vector / Blast Radius
This misconfiguration is a two-sided disaster.
Side 1 — The immediate outage: Your wildcard policy likely conflicts with an Organization-level SCP that explicitly denies s3:* or * on non-approved buckets. The SCP wins. Your app role gets nothing.
Side 2 — The critical security exposure (when there is no SCP): If this role does have "Action": "*", "Resource": "*" without an SCP guardrail, you have handed an attacker a skeleton key to your AWS account. The blast radius:
- Privilege escalation:
iam:CreateAccessKey,iam:AttachUserPolicy,iam:PassRole— attacker creates a backdoor admin user in under 60 seconds. - Data exfiltration: Full read access to every S3 bucket, Secrets Manager secret, SSM Parameter Store value, and RDS snapshot in the account.
- Persistence:
lambda:CreateFunction+iam:PassRole= attacker deploys a persistent backdoor Lambda with an execution role they control. - Lateral movement:
sts:AssumeRoleacross every role in the account that trusts this principal.
A compromised EC2 instance, a leaked pod service account token, or a misconfigured OIDC trust is all an attacker needs to pivot from a single compute node to full account takeover. This is a P0 security incident waiting to happen.
How to Fix It (The Solution)
Basic Fix — Scope the Action and Resource
Replace the wildcard policy statement with an explicit least-privilege grant.
{
"Version": "2012-10-17",
"Statement": [
{
- "Sid": "DangerousWildcardAccess",
- "Effect": "Allow",
- "Action": "*",
- "Resource": "*"
+ "Sid": "AllowAppS3ReadOnly",
+ "Effect": "Allow",
+ "Action": [
+ "s3:GetObject",
+ "s3:GetObjectVersion"
+ ],
+ "Resource": "arn:aws:s3:::my-prod-bucket/configs/*"
}
]
}
Also add the s3:ListBucket permission at the bucket level (not object level) or the SDK will throw a misleading 403 instead of a 404 on missing keys:
+ {
+ "Sid": "AllowBucketList",
+ "Effect": "Allow",
+ "Action": "s3:ListBucket",
+ "Resource": "arn:aws:s3:::my-prod-bucket",
+ "Condition": {
+ "StringLike": {
+ "s3:prefix": ["configs/*"]
+ }
+ }
+ }
Enterprise Best Practice — Attribute-Based Access Control (ABAC) with Condition Keys
For multi-tenant or multi-environment deployments, bind S3 access to principal tags so you never have to update policies as new buckets are created.
{
"Version": "2012-10-17",
"Statement": [
{
- "Sid": "DangerousWildcardAccess",
- "Effect": "Allow",
- "Action": "*",
- "Resource": "*"
+ "Sid": "ABACTaggedS3ReadAccess",
+ "Effect": "Allow",
+ "Action": [
+ "s3:GetObject",
+ "s3:GetObjectVersion",
+ "s3:GetObjectTagging"
+ ],
+ "Resource": "arn:aws:s3:::*/*",
+ "Condition": {
+ "StringEquals": {
+ "aws:ResourceTag/Environment": "${aws:PrincipalTag/Environment}",
+ "aws:ResourceTag/AppName": "${aws:PrincipalTag/AppName}"
+ },
+ "StringEqualsIfExists": {
+ "aws:RequestedRegion": ["us-east-1", "us-west-2"]
+ }
+ }
}
]
}
Tag your IAM role: Environment=production, AppName=my-app. Tag your S3 bucket with matching tags. The policy self-enforces without hardcoding ARNs. This is how you scale IAM without drowning in policy versions.
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
This class of misconfiguration should never reach production. Gate it at the PR level.
1. Checkov (Terraform/CloudFormation IaC scanning)
# Install and run against your IaC directory
pip install checkov
checkov -d ./terraform --check CKV_AWS_40,CKV_AWS_289,CKV_AWS_355
# CKV_AWS_40: IAM policies should not have statements with admin permissions
# CKV_AWS_289: No wildcard resource on write actions
# CKV_AWS_355: No wildcard resource on sensitive read actions
2. OPA/Conftest policy (enforce in GitHub Actions)
# policy/iam_no_wildcard_actions.rego
package aws.iam
deny[msg] {
stmt := input.resource.aws_iam_policy.properties.policy.Statement[_]
stmt.Effect == "Allow"
stmt.Action == "*"
msg := sprintf("IAM policy '%v' contains wildcard Action '*'. Scope to explicit actions.", [input.resource.aws_iam_policy.name])
}
deny[msg] {
stmt := input.resource.aws_iam_policy.properties.policy.Statement[_]
stmt.Effect == "Allow"
stmt.Resource == "*"
not contains_condition(stmt)
msg := sprintf("IAM policy '%v' uses wildcard Resource '*' without a Condition block.", [input.resource.aws_iam_policy.name])
}
contains_condition(stmt) {
_ = stmt.Condition
}
# .github/workflows/iac-security.yml
- name: Conftest IAM Policy Check
run: |
conftest test ./terraform/iam --policy ./policy/
3. AWS Config Rule (runtime enforcement)
Enable the managed rule iam-no-inline-policy-check and deploy a custom Config rule using the Lambda evaluator pattern to flag any policy document containing "Action": "*" on "Resource": "*" without a Condition block. Pair with AWS Security Hub for centralized findings.
4. Permission Boundaries as a hard stop
Attach a Permission Boundary to every role created by your CI/CD pipeline. Even if a wildcard policy is accidentally attached later, the boundary caps the maximum effective permissions:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
"Resource": "arn:aws:s3:::my-prod-bucket*"
}]
}
Set this boundary at the AWS Organization level via SCP so no developer or pipeline can create a role without it.