AWS S3 Bucket Policy Analyzer

Paste an S3 bucket policy and get the answer you came for first: can anyone on the internet read, write or list this bucket. Then the guardrails it is missing, and the ARN mistakes that make parts of it silently do nothing.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Check this policy. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

A public bucket

Principal * on GetObject, which is how an S3 bucket becomes readable by the internet

{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":"*","Action":"s3:GetObject","Resource":"arn:aws:s3:::b/*"}]}

No TLS enforcement

A policy that permits plaintext requests, because nothing denies aws:SecureTransport false

{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"arn:aws:iam::123456789012:root"},"Action":"s3:GetObject","Resource":"arn:aws:s3:::b/*"}]}

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Using Principal * to make a bucket public

    It grants to everyone on the internet, including unauthenticated requests. Almost every S3 leak is this line.

    Instead:Use a specific principal, or CloudFront with origin access control for public content.

  2. Forgetting to deny plaintext requests

    Without a Deny on aws:SecureTransport false, the bucket accepts HTTP.

    Instead:Add an explicit Deny with that condition.

  3. Confusing the bucket ARN with the object ARN

    arn:aws:s3:::bucket covers bucket-level actions and arn:aws:s3:::bucket/* covers objects. ListBucket needs the first, GetObject the second.

    Instead:List both, with the right actions on each.

What it checks

Public S3 buckets are still one of the most common causes of a data breach, and almost never because someone decided to make a bucket public. They get there one statement at a time.

The public/not-public answer, first

Before any list of findings, a straight verdict: can anyone on the internet read objects, write objects, list keys, or change the policy itself. Each is assessed separately, because public read and public write are very different problems and public list is what turns the first one from careless into catastrophic.

Conditions that actually narrow

A wildcard principal narrowed by aws:PrincipalOrgID, aws:SourceArn or aws:SourceVpce is a normal pattern and is treated as such. An IP allowlist is not: aws:SourceIp does not apply when a request arrives over a VPC endpoint, so it is not a boundary and the tool says so.

The two guardrails almost nobody has

S3 accepts plain HTTP unless a Deny on aws:SecureTransport says otherwise, and nothing tells you when a client uses it. Separately, requiring SSE-KMS on upload is what lets you name the key protecting the data, which is the question an auditor asks.

Self-inflicted takeover

An anonymous grant on s3:PutBucketPolicy or s3:PutBucketAcl means whoever finds the bucket can rewrite its permissions. That makes every other restriction in the document advisory, so it is reported above everything else.

ARN shape mistakes

Object actions scoped to a bucket ARN with no /*, or s3:ListBucket scoped to object ARNs. Both are silently denied at runtime rather than rejected at save time, which is why they survive review.

Everything the IAM rules catch

A bucket policy is an IAM policy, so it also gets the wildcard grading, the escalation-path checks and the JSON validation from the IAM Policy Validator, including exact line and column on a syntax error.

Why public list is the one that hurts

A bucket with public read on objects is exposed, but only for keys somebody knows. Plenty of real buckets have lived that way for years because the keys are UUIDs and nothing links to them.

Add anonymous s3:ListBucket and that changes completely. The keys are no longer secret, so every object is reachable by anyone who finds the bucket name, and bucket names are guessable because they follow company naming conventions. This is the difference between the two findings, and why they are reported separately rather than as one "bucket is public".

Public asset buckets essentially never need ListBucket. Clients fetch URLs the application already gave them.

What makes an S3 bucket public?

A bucket policy is a resource policy: it is attached to the bucket and names who may do what to it. A bucket becomes public when that policy grants an action to a principal of "*" without a condition that meaningfully narrows it, and the word meaningfully is doing a lot of work, because several conditions that look like boundaries are not.

Principal "*" means the entire internet

Not every AWS account, not authenticated users: anyone at all, with no credentials. A statement with that principal and no narrowing condition grants its actions to the world. Which actions matters as much as the fact: read, write, list and policy-control are four different problems, and public list combined with public read is worse than either alone because it turns a bucket you have to guess the keys of into a directory anyone can walk.

{
  "Effect": "Allow",
  "Principal": "*",
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::my-bucket/*"
}

anonymous read of every object: the standard public-website policy,
and a data breach if the bucket was not meant to be one

The bucket ARN and the object ARN are different resources

Actions on the bucket itself, such as ListBucket, take the bucket ARN. Actions on objects, such as GetObject, take the ARN with a slash-star after it. Getting this wrong is a common and confusing failure: a policy granting GetObject on the bucket ARN grants nothing at all, and one granting ListBucket on the object ARN does the same. Both are valid JSON and neither warns you.

arn:aws:s3:::my-bucket        the bucket   -> s3:ListBucket
arn:aws:s3:::my-bucket/*      the objects  -> s3:GetObject

Block Public Access sits above the policy

The four Block Public Access settings are evaluated before the policy and can override it, at bucket level or account level. This is genuinely useful and it is also why a policy review alone is not the whole answer: a policy can look public and be blocked, or look safe while an account-level setting is off. Reading the file tells you what the policy grants; only the account tells you what is actually reachable.

The conditions that are not boundaries

aws:SourceIp looks like a restriction and is not a reliable one: an IP condition does not apply to requests arriving over a VPC endpoint, so it can be bypassed rather than merely narrowed. The CloudFront service principal without an aws:SourceArn condition is the subtler one, without it, any CloudFront distribution in any AWS account can read the bucket, and the policy reads as perfectly reasonable in review. Conditions that do narrow a wildcard properly are aws:PrincipalOrgID, aws:SourceVpce and aws:SourceArn.

"Condition": {
  "StringEquals": { "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/E1234" }
}

without this line, every CloudFront distribution on earth qualifies