Fixing IAM Policy Simulator Implicit Deny for s3:ListBucket: Explicit Deny Override Explained
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 10–20 mins
TL;DR
- What broke: An explicit
Denystatement — either scoped too broadly or missing a condition — is matchings3:ListBucketon thedata/*prefix and overriding everyAllowin the policy evaluation chain. - How to fix it: Scope the
Denyresource ARN to the exact prefixes you intend to block, or add aStringNotLikecondition ons3:prefixto carve out the legitimate access path. - Fast path: Use our Client-Side Sandbox below to auto-refactor this — paste your policy and get corrected JSON without sending your ARNs to a third-party server.
The Incident (What Does the Error Mean?)
The IAM Policy Simulator surfaces this as:
Action: s3:ListBucket
Resource: arn:aws:s3:::your-bucket
Final decision: IMPLICIT_DENY
Matched statements:
- PolicyName: S3DataAccessPolicy
Effect: Deny
Action: s3:ListBucket
Resource: arn:aws:s3:::your-bucket
Condition: s3:prefix matches 'data/*'
AWS policy evaluation is deterministic: one explicit Deny wins over every Allow, no exceptions. The simulator calling this "implicit deny" is slightly misleading — the root cause is an explicit Deny statement whose resource or condition scope is wider than intended, consuming the Allow you thought was protecting data/* access. The principal gets zero listing capability on that prefix, regardless of any identity-based or resource-based Allow attached elsewhere.
Immediate consequence: Any IAM role, user, or service assuming this policy cannot enumerate objects under data/*. S3 Select queries, Athena crawlers, Glue jobs, and Lambda functions that depend on prefix listing will fail silently or throw AccessDenied — not a 404, which means your application error handling likely swallows it.
The Attack Vector / Blast Radius
This misconfiguration cuts both ways as a risk:
Operational blast radius: If this Deny was added as a guardrail (e.g., an SCP or permission boundary meant to block data/pii/*) but was scoped to data/*, every downstream pipeline reading from that prefix is now broken. In a data lake architecture, this cascades — Glue crawlers fail, Athena queries return empty results with no error, and S3 Inventory reports stop populating.
Security misconfig angle: The inverse failure is equally dangerous. Engineers who can't list data/* via the console or CLI will often escalate their own permissions to debug — creating a privilege escalation paper trail or, worse, temporarily attaching AdministratorAccess to "just fix it quickly." That temporary escalation is exactly what threat actors look for in CloudTrail logs.
SCP interaction: If this Deny lives in a Service Control Policy at the OU level, no amount of identity-based Allow in the member account will override it. The fix must happen at the SCP layer, not the role policy layer — a mistake that wastes hours of debugging at the wrong level.
How to Fix It (The Solution)
Basic Fix — Scope the Deny Resource Precisely
The most common root cause: the Deny resource is set to the bucket ARN (arn:aws:s3:::your-bucket) with an s3:prefix condition, but the condition operator is wrong or missing entirely.
{
"Effect": "Deny",
"Action": "s3:ListBucket",
- "Resource": "arn:aws:s3:::your-bucket",
- "Condition": {
- "StringLike": {
- "s3:prefix": "data/*"
- }
- }
+ "Resource": "arn:aws:s3:::your-bucket",
+ "Condition": {
+ "StringLike": {
+ "s3:prefix": "data/restricted/*"
+ }
+ }
}
If the intent is to allow data/* and deny only a sub-prefix, invert the condition:
{
"Effect": "Deny",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::your-bucket",
"Condition": {
- "StringLike": {
- "s3:prefix": "data/*"
- }
+ "StringNotLike": {
+ "s3:prefix": [
+ "data/public/*",
+ "data/shared/*"
+ ]
+ }
}
}
Enterprise Best Practice — Least-Privilege with Explicit Allow Scoping + Condition Keys
Stop relying on Deny to carve out access. Use explicit Allow with tight condition keys and let the default implicit deny handle everything else.
{
"Version": "2012-10-17",
"Statement": [
- {
- "Effect": "Allow",
- "Action": "s3:ListBucket",
- "Resource": "arn:aws:s3:::your-bucket"
- },
- {
- "Effect": "Deny",
- "Action": "s3:ListBucket",
- "Resource": "arn:aws:s3:::your-bucket",
- "Condition": {
- "StringLike": { "s3:prefix": "data/*" }
- }
- }
+ {
+ "Effect": "Allow",
+ "Action": "s3:ListBucket",
+ "Resource": "arn:aws:s3:::your-bucket",
+ "Condition": {
+ "StringLike": {
+ "s3:prefix": ["data/*", "logs/*"]
+ },
+ "StringEquals": {
+ "s3:delimiter": "/"
+ }
+ }
+ },
+ {
+ "Effect": "Allow",
+ "Action": ["s3:GetObject", "s3:PutObject"],
+ "Resource": "arn:aws:s3:::your-bucket/data/*"
+ }
]
}
Key principle: s3:ListBucket is a bucket-level action (resource = bucket ARN). s3:GetObject/s3:PutObject are object-level actions (resource = object ARN with prefix). Mixing these up in the same Resource field is the second most common cause of this exact simulator failure.
💡 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
1. Checkov — catch overly broad Deny statements pre-merge:
checkov -f iam_policy.json --check CKV_AWS_40,CKV_AWS_274
Write a custom Checkov check if you need to enforce that no Deny on s3:ListBucket uses data/* without a restrictive condition.
2. OPA/Rego — enforce prefix scoping in policy-as-code:
deny[msg] {
stmt := input.Statement[_]
stmt.Effect == "Deny"
stmt.Action == "s3:ListBucket"
not stmt.Condition
msg := "Deny on s3:ListBucket must include an s3:prefix condition to prevent over-broad denial."
}
3. AWS IAM Access Analyzer — validate before deployment:
aws accessanalyzer validate-policy \
--policy-document file://iam_policy.json \
--policy-type IDENTITY_POLICY
This catches SECURITY_WARNING findings for overly permissive or conflicting statements before the policy ever attaches to a principal.
4. Terraform — use aws_iam_policy_simulator data source in plan stage to assert expected allowed decisions on critical prefixes as part of terraform plan output validation. Gate merges on simulator assertion failures in your PR pipeline.