Skip to content
>_ITDITDWeb Security Platform

Security Guides

Public buckets — "block public access is on" does not mean the hole is gone

Bucket leaks are configuration events, not intrusions. AWS states that Block Public Access does not alter existing policies or ACLs — so it covers the hole rather than closing it. How to audit and actually fix it.

Published 2026-09-05 Updated 2026-09-05 Last verified 2026-09-05 9 min read

Leaks from object storage usually do not involve an intrusion. Nobody broke anything, no credential was stolen, nothing looks unusual in the logs — the configuration simply said so.

Why data leaks with nobody breaking in

Intrusion

vulnerability or credentials → break in → take data out
patching, authentication and monitoring each get a chance to stop it

Misconfiguration (public bucket)

know (or guess) the URL → fetch it
not "authentication was bypassed" — authentication was never required

An intrusion can be stopped somewhere along its path. A misconfiguration has no path to stop — the request is legitimate.

What makes this shape awkward is that the access does not look illegitimate. The attacker sends an ordinary GET to an endpoint that is genuinely public, so there is little for intrusion detection or log anomalies to catch. It is the same shape as what must never sit in a public directory, moved off the server and into storage.

The four settings are four different things

S3 Block Public Access is four independent settings that can be applied in any combination. The names are similar enough that they get treated as one switch, but each one stops something different.

The four Block Public Access settings (from the AWS documentation)
BlockPublicAcls
Makes attempts to apply a new public ACL fail (PutBucketAcl, PutObjectAcl, or a PutObject carrying a public ACL). Existing policies and ACLs are not modified, so a public ACL already in place stays in place
IgnorePublicAcls
Makes S3 ignore all public ACLs on the bucket and its objects. A PutObject carrying a public ACL still succeeds (that is the difference from BlockPublicAcls). It does not remove existing ACLs and does not prevent new public ACLs from being set
BlockPublicPolicy
Rejects a bucket policy that allows public access (PutBucketPolicy and the related access point calls). It does not affect existing policies
RestrictPublicBuckets
Restricts access to a bucket that has a public policy to AWS service principals and authorised users within the owning account. It blocks cross-account access (other than by AWS service principals), including non-public delegation to a specific account

The property all four share, and almost everyone misses

Notice that every entry above says existing policies and ACLs are not modified. The documentation states the consequence directly: "Block public access settings don't alter existing policies or ACLs. Therefore, removing a block public access setting causes a bucket or object with a public policy or ACL to again be publicly accessible."

So turning blocking on did not remove the danger. It put a cover over it, and the hole underneath is untouched. Somebody lifting it temporarily to investigate, a copy into another account, a Terraform diff reverting it — any of those brings the exposure straight back. Define your target state as "not public with blocking off", not "blocking is on".

"Public" is defined more broadly than you think

You can consider a bucket private and still have S3 evaluate it as public.

Evaluated as public

・An ACL granting permissions to AllUsers or AuthenticatedUsers — the second means everyone with an AWS account, not everyone at your company
・A bucket policy naming principals with values that contain a wildcard or a policy variable
An aws:SourceIp range that is too broad (wider than /8 for IPv4, wider than /32 for IPv6, private ranges excluded)

S3 begins by assuming a bucket policy is public and then evaluates whether it qualifies as non-public. Ambiguity resolves toward public.

Evaluated as non-public

A policy that limits access to fixed values without wildcards — a principal, a set of CIDR blocks, aws:SourceArn, aws:SourceVpc, aws:SourceVpce, aws:SourceOwner, aws:SourceAccount, and so on.

The documentation's own example: aws:SourceVpc set to vpc-* is public; set to a fixed value such as vpc-91237329 it is not. "I narrowed it" and "I narrowed it to a fixed value" are the two sides of that judgement.

Apply it in the right place — the account, not the bucket

The documentation recommends applying BlockPublicPolicy at the account level, and the reason is the substantive part: a bucket policy can allow users to alter that bucket's block public access settings.

Bucket level only

someone who can change the bucket policy → inserts a policy disabling the block → the bucket can be made public

Account level

even after the bucket policy is rewritten, S3 blocks the public policy

A bucket-level block can be removed by whoever can edit bucket policies. An account-level one cannot.

Where access point, bucket and account settings differ, S3 applies the most restrictive combination. It resolves toward the stricter side, so tightening at the level above means it cannot be loosened below — least privilege, applied to storage.

The order to fix it in

1

Look at what is public — before you hide it

Turning blocking on first stops the exposure and simultaneously destroys your ability to see what was exposed. That is exactly what BlockPublicAcls is described as enabling: protection against public access "while allowing you to audit, refine, or otherwise alter the existing policies and ACLs". AWS provides IAM Access Analyzer for S3, which lists buckets whose ACLs or policies grant public access and, for each finding, reports the source and level of that access. Take the inventory there first.

2

Enable all four settings at the account level

AWS recommends turning on all four block public access settings for your account (and for each bucket, which is what Security Hub control S3.8 checks). Verify first that your applications work without public access — if something genuinely requires it, such as static website hosting, adjust that case individually rather than skipping the control everywhere.

3

Actually remove the hole under the cover (this is the real work)

Blocking does not delete existing public policies or public ACLs. Go back to the inventory from step 1 and remove or rewrite them. Only then are you in the "not public with blocking off" state. Most teams stop at step 2 — that is the whole point of this article.

4

For buckets that must be public, control what goes in them

Where public access is a genuine requirement, split storage so that a public bucket only ever holds things that are safe to be public. Backups, database dumps and customer lists do not share a bucket with published assets. What you put somewhere sets the blast radius — the server-side version of the same reasoning is in keeping .env out of reach on shared hosting.

5

Do not rely on the filename

"Nobody knows the URL" is not access control (it is the same problem as guessable identifiers). An unguessable name is insurance, not a substitute for a permission check. When you need to share something temporarily, use something that expires on its own — a time-limited signed URL — and leave the public setting closed.

This site's view: configuration incidents only fall when the state is auditable

Bucket exposure keeps recurring not because people are careless but because the state is not visible. Unlike a file permission or a server setting, whether storage is public is the composition of four layers — policy, ACL, account setting, access point — and no single one of them tells you the answer. Our position for territory like this is to have a machine evaluate the composition rather than relying on human attention: tighten at the level above so it cannot be loosened below, and re-inventory on a schedule using something that renders the public/non-public verdict for you. "Being careful" is not a control — the same conclusion we reach for dependencies (getting started with osv-scanner).

Sources (primary)

  • Amazon Web Services, "Blocking public access to your Amazon S3 storage" (Amazon S3 User Guide) — docs.aws.amazon.com (the behaviour of the four settings, the statement that blocking does not alter existing policies or ACLs, the definition of "public", and the reason account-level application is recommended all come from this document)

FAQ

QIf Block Public Access is enabled, am I safe?
A

You are in a state where nothing is reachable from outside right now, but the danger has not gone away. The AWS documentation states that block public access settings do not alter existing policies or ACLs, and that removing a block public access setting therefore causes a bucket or object with a public policy or ACL to be publicly accessible again. The block covers the hole; it does not close it. What you want is not "blocking is on" but "nothing would be public even with blocking off" — which means deleting the public policies and ACLs themselves.

QShould I apply the settings per bucket or per account?
A

Per account. On BlockPublicPolicy the documentation explains why: a bucket policy can allow users to alter that bucket's block public access settings, so anyone who can change a bucket policy could insert a policy that disables the block. With the setting enabled for the whole account, S3 blocks public policies even if a user alters the bucket policy. A bucket-level setting can be removed by the people who can edit bucket policies.

QWhat exactly counts as "public"?
A

A broader definition than most people assume. An ACL is public if it grants permissions to the predefined AllUsers or AuthenticatedUsers groups — and AuthenticatedUsers means everyone with an AWS account, not everyone at your company. For bucket policies, S3 begins by assuming the policy is public and then evaluates whether it qualifies as non-public; it only qualifies when access is limited to fixed values without wildcards or policy variables. A policy using aws:SourceIp with a very broad range (wider than /8 for IPv4) is also evaluated as public.

QAre newly created buckets safe by default?
A

The documentation says that by default, new buckets, access points and objects don't allow public access — but that users can modify bucket policies, access point policies or object permissions to allow it. So the default is the safe side, and almost every incident happens where somebody deliberately, or temporarily for a piece of work, moved off that default. Do not rely on the default: you need both a control that cannot be turned off locally (account-level blocking) and a way to see when something has drifted.