How to Find Publicly Accessible S3 Buckets in Your AWS Account
Find publicly accessible S3 buckets with the AWS CLI. Check policy status, ACLs, and Block Public Access in one loop, then lock the account down safely.
“Is anything in our AWS account public?” It’s the question that arrives before every audit, and after every news story about a leaked bucket. The console answers it one bucket at a time. The CLI can answer it for the whole account in a few seconds.
The catch is that “public” has more than one meaning, and checking only one of them gives you a false all-clear.
Three Ways a Bucket Becomes Public
- A bucket policy that grants access to
Principal: "*"without a restricting condition. - ACL grants to the
AllUsersorAuthenticatedUsersgroups. - Block Public Access (BPA) switched off. BPA is the override: with all four settings on, policy and ACL exposure is neutralized. With it off, the first two routes take effect.
A good audit checks all three.
Check the Account-Level Safety Net First
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
aws s3control get-public-access-block --account-id "$ACCOUNT_ID"
If all four flags are true, account-wide BPA is on and the rest of the audit is mostly confirmation. A NoSuchPublicAccessBlockConfiguration error means nothing is set at account level, so continue to the loop.
Audit Every Bucket in One Loop
for b in $(aws s3api list-buckets --query 'Buckets[].Name' --output text); do
policy=$(aws s3api get-bucket-policy-status --bucket "$b" \
--query 'PolicyStatus.IsPublic' --output text 2>/dev/null || echo "no-policy")
bpa=$(aws s3api get-public-access-block --bucket "$b" \
--query 'PublicAccessBlockConfiguration.[BlockPublicAcls,IgnorePublicAcls,BlockPublicPolicy,RestrictPublicBuckets]' \
--output text 2>/dev/null || echo "no-bpa")
acl=$(aws s3api get-bucket-acl --bucket "$b" \
--query "length(Grants[?Grantee.URI=='http://acs.amazonaws.com/groups/global/AllUsers' || Grantee.URI=='http://acs.amazonaws.com/groups/global/AuthenticatedUsers'])" \
--output text 2>/dev/null || echo "error")
echo "$b policy_public=$policy public_acl_grants=$acl bpa=[$bpa]"
done
How to read each line:
policy_public=True: the bucket policy makes it public.public_acl_grantsabove 0: an ACL grants access to everyone.bpa=[False ...]orno-bpa: nothing stops either of the above from taking effect.errororno-policy: the call failed or there is no policy. Re-check any bucket that errors, since it may sit in another region.
The loop prints bucket names only. It checks bucket-level exposure; individual object ACLs are why IgnorePublicAcls matters.
No policy is not the same as private.
A bucket with no policy can still be public through an ACL. Read all three columns, not just the first.
Separate Intentional From Accidental
Before changing anything, answer these for every hit:
- Is it a static website, a public dataset, or a CloudFront origin?
- Who owns it? Check the tags and the
CreateBucketevent in CloudTrail. - What’s in it? List key names and prefixes only, and don’t download contents.
- Is the whole bucket meant to be public, or just one prefix?
With many accounts, IAM Access Analyzer for S3 gives the same view across an organization without looping.
Lock It Down Without Breaking Things
For exposure nobody can justify, turn BPA on for the whole account:
aws s3control put-public-access-block --account-id "$ACCOUNT_ID" \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
This will break buckets that are public on purpose, such as a static site served straight from S3. Put those behind CloudFront with origin access control first, or exempt them at bucket level, then enable it account-wide.
GCP and Azure Equivalents
The same question, different switches:
gcloud storage buckets get-iam-policy gs://BUCKET_NAME --format=json | grep -E 'allUsers|allAuthenticatedUsers'
az storage account list --query "[].{name:name, publicBlobAccess:allowBlobPublicAccess}" -o table
On GCP you’re looking for a public principal in the bucket’s IAM policy. On Azure, for the account-level public blob access setting left on.
Check the account-level safety net, loop over every bucket for policy, ACL and BPA, decide what’s intentional, then turn on Block Public Access. Run it on a schedule, or enable account-level BPA and Access Analyzer, so exposure is caught when it happens and not at the next audit.