Skip to main content
CloudArq
topics3
scopeaws
typehow-to
guide · aws · s3

How to audit S3 public access on AWS

Public S3 buckets are still one of the most common ways company data leaks. This is the practical checklist: how a bucket becomes public, how to prove which of yours are exposed with the AWS CLI and IAM Access Analyzer, and how to lock the door with Block Public Access and Terraform so it stays shut.

Updated 2026-07-20 · ~8 minute read

3
Block Public Access settings
2
checks flag public S3
163
read-only checks in total
0
agents installed
why it matters

A bucket can go public three different ways

Auditing S3 exposure is not a single check — it is three, because S3 grants public access through three independent mechanisms. A bucket ACL can grant the built-in AllUsers group (anyone on the internet) or AuthenticatedUsers (any AWS account). A bucket policy can set Principal: "*" with no condition. And a bucket configured for static-website hosting is only private if Block Public Access is on. You have to look at all three, at both the account and bucket level, to say a bucket is genuinely closed.

step 1 · account switch

Turn on account-level Block Public Access

The account-level setting is the master switch: it overrides every bucket in the account, so a future public policy or ACL simply cannot take effect. It has 4 independent flags — turn all of them on unless you have a specific reason not to. First read the current state (an empty or errored response means it has never been configured, which is the risk), then set all four.

aws cli · s3control
# Read the account-level setting (no output / error = not configured)
aws s3control get-public-access-block --account-id 111122223333

# Turn all four flags on at the account level
aws s3control put-public-access-block \
  --account-id 111122223333 \
  --public-access-block-configuration \
  BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
BlockPublicAcls

Rejects PUT requests that would set a public ACL on a bucket or object.

IgnorePublicAcls

Ignores any public ACLs already attached, so they grant no access.

BlockPublicPolicy

Rejects bucket policies that grant public access.

RestrictPublicBuckets

Limits access on a public-policy bucket to AWS service principals and authorised users in the account.

step 2 · per bucket

Verify each bucket, and let S3 tell you if it's public

Each bucket also carries its own Block Public Access settings. You don't have to reason about policies by hand — S3 exposes a get-bucket-policy-status call that returns an IsPublic boolean it computes itself. Loop it over every bucket to get a one-line public/not-public report for the whole account.

aws cli · s3api
# Per-bucket Block Public Access settings
aws s3api get-public-access-block --bucket my-bucket

# Ask S3 whether the bucket is public (it computes IsPublic for you)
aws s3api get-bucket-policy-status --bucket my-bucket
# => {"PolicyStatus": {"IsPublic": false}}

# Sweep every bucket in the account
for b in $(aws s3api list-buckets --query 'Buckets[].Name' --output text); do
  echo "$b -> $(aws s3api get-bucket-policy-status --bucket "$b" \
    --query PolicyStatus.IsPublic --output text 2>/dev/null || echo 'no-policy')"
done
step 3 · policy & acl

Read the bucket policy and ACL directly

For anything flagged public — or anything you want to confirm manually — pull the policy and ACL and look for two tells: a statement with "Principal": "*" and no restricting condition, and any ACL grant to the AllUsers or AuthenticatedUsers group URIs.

aws cli · s3api
# The full bucket policy (look for Principal "*" without a condition)
aws s3api get-bucket-policy --bucket my-bucket --query Policy --output text

# The ACL — a grant to this URI is world-readable:
#   http://acs.amazonaws.com/groups/global/AllUsers
aws s3api get-bucket-acl --bucket my-bucket
step 4 · second signal

Cross-check with IAM Access Analyzer

IAM Access Analyzer is an AWS service that identifies resources — including S3 buckets — shared with a principal outside your account or organisation. It is a valuable second, independent signal: it catches cross-account sharing that a simple "is it world-public?" check misses. Enable an external-access analyzer (console: IAM → Access Analyzer), then list its S3 findings.

aws cli · accessanalyzer
aws accessanalyzer list-findings \
  --analyzer-arn arn:aws:access-analyzer:eu-north-1:111122223333:analyzer/ExternalAccess \
  --filter '{"resourceType": {"eq": ["AWS::S3::Bucket"]}}'
step 5 · make it stick

Codify it so it can't regress

A one-time console fix drifts. Put both the account-level and per-bucket Block Public Access blocks in Terraform so any new bucket is private by default and a reviewer sees an exposure change in the plan.

terraform · aws provider
# Account-wide guard rail
resource "aws_s3_account_public_access_block" "this" {
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# Per-bucket (defence in depth)
resource "aws_s3_bucket_public_access_block" "assets" {
  bucket                  = aws_s3_bucket.assets.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

Intentionally-public bucket (a static site)? Keep these blocks on and serve it through Amazon CloudFront with Origin Access Control, so the bucket stays private and only CloudFront can read it.

product · detection

How CloudArq detects this for you

CloudArq runs the same logic on a schedule across every region so you never have to remember to sweep. Two of its 192 read-only checks cover this issue directly: public_s3 flags any bucket made public by an ACL or policy, and no_account_s3_block flags an account with no account-level Block Public Access. It reads configuration metadata only — never your objects — and each finding ships with the exact CLI and Terraform fix from the steps above. It detects and hands you the fix; it never changes your account.

findings · category · s3-exposureillustrative example
Critical1High2
public_s3
Bucket world-readable via bucket policy
arn:aws:s3:::example-assets · eu-north-1
fix →
no_account_s3_block
Account-level Block Public Access is off
account 1111…3333 · s3control
fix →
public_s3
AllUsers ACL grant on a logs bucket
arn:aws:s3:::example-logs
fix →

Each finding carries a copy-paste CLI / Terraform fix. Read-only — CloudArq never auto-changes your account.

faq

Frequently asked

01What actually makes an S3 bucket "public"?
Three independent things: a bucket ACL that grants the AllUsers or AuthenticatedUsers group, a bucket policy with a wildcard Principal ("*") and no restricting condition, or a static-website configuration with Block Public Access turned off. Any one of them exposes objects. That is why you check ACLs, policies, and the Block Public Access settings — not just one of them.
02Should I just turn on account-level Block Public Access everywhere?
For most accounts, yes — enabling all four settings at the account level is the safest default and it overrides per-bucket mistakes. The exception is a bucket that is intentionally public (for example, a static site served directly from the S3 website endpoint). The recommended pattern is to keep Block Public Access on and front the bucket with Amazon CloudFront using Origin Access Control, so the bucket itself stays private.
03How do I find every public bucket without checking each one by hand?
Loop over aws s3api list-buckets and call get-bucket-policy-status for each — it returns an IsPublic boolean computed by S3 itself. For a second, independent signal, turn on IAM Access Analyzer, which reports buckets shared with principals outside your account or organisation. CloudArq runs both classes of check across every region on a schedule and groups the results for you.
04Will enabling Block Public Access break a CloudFront-fronted bucket?
No. With Origin Access Control, CloudFront reaches the bucket through a scoped bucket policy that names the CloudFront service principal — that is not "public" access, so Block Public Access leaves it intact. Only buckets that rely on a wildcard-public policy or a public ACL are affected, which is exactly what you want to close.
05Is CloudArq read-only, and does it ever store my S3 objects?
Yes to read-only, no to storage. CloudArq connects through a read-only IAM role with an ExternalId and reads only configuration metadata — bucket policies, ACLs, and the Block Public Access settings. It never reads, copies, or stores your S3 objects, credentials, or database contents. Findings are encrypted at rest with AES-256-GCM and hosted in the EU (Helsinki). It detects and hands you the fix; it never auto-changes your account.
06Which CloudArq checks flag public S3 exposure?
public_s3 flags any bucket exposed by a public ACL or bucket policy, and no_account_s3_block flags an account that has not enabled account-level Block Public Access. Both are read-only checks and each finding ships with the exact CLI and Terraform steps to close the gap.

Audit it once, then keep it audited

Public S3 is one item on a much longer list. CloudArq is an agentless, read-only AWS audit — see how it fits with AWS-native signals on the AWS CSPM overview, browse the wider security coverage, or find more walkthroughs in the guides library.