← all posts
· Kikimora Team · guide / aws / triage / cloud-security

Close a public S3 bucket in three sentences

Finding a public S3 bucket is easy. Any scanner will tell you “this bucket allows public access” and feel very proud of itself. The hard part is the next ten minutes, when you have to figure out whether that bucket is public on purpose and what breaks if you slam it shut.

Why “just make it private” is a trap

Blocking public access on the wrong bucket is how you take down a static website at 2pm or break a partner integration that’s been quietly pulling files for two years. Some buckets are supposed to be public. The real work isn’t toggling a setting; it’s knowing which exposure is dangerous and which one is load-bearing. A finding that says “public” without that context has handed you a risk, not an answer.

This is why the reflexive fix is dangerous. The scanner flags ten public buckets, someone with the best intentions enables Block Public Access on all ten, and now the company website returns AccessDenied and the data pipeline that ships nightly exports to a partner has gone dark. Both were “public.” Only some of them were wrong. The triage that separates the two is the actual job, and it’s exactly the part a finding leaves you to do alone.

Step 1: find what’s actually public

Start by getting the full picture in one place, across every account:

Show me every S3 bucket with public access across all accounts.

Kikimora connects to AWS through read-only APIs and pulls this directly, so you’re not region-hopping through the console or running aws s3api in a loop. It dedups and groups by root cause the same way it does when you triage Security Hub findings: forty findings about the same misconfigured bucket policy come back as one item, not forty rows you have to mentally collapse yourself.

The grouping matters more than it sounds. The default console experience hands you a flat list where one root cause, say, a single overly broad account-level policy, manifests as a dozen separate “public bucket” warnings. You end up triaging the same problem twelve times. Grouping by cause means you see the dozen as one, which is the difference between a five-minute fix and a morning of clicking through near-identical findings convincing yourself they’re not the same thing.

Step 2: separate intentional from accidental

This is the step scanners skip, and it’s the one that matters most. A list of public buckets is useless until you know which ones are public by design:

Which of these are serving a website or a known integration, and which look accidental?

Here’s the angle a standalone bucket checker can’t reach. Whether a bucket allows public access is an AWS API question. Whether the internet is actually reaching it is a different question, and the answer lives outside AWS. Kikimora cross-references with edge and external exposure data, using Shodan to see what attackers see first, so a bucket that’s technically public but serving live traffic gets flagged as intentional, while one that’s public and dark gets flagged as the accident it probably is. That distinction is the whole game.

If you want to confirm reachability on a specific bucket rather than trust the API’s word for it, ask directly:

Is the prod-backups bucket actually reachable from the internet right now, or just configured to allow it?

A bucket can have a permissive policy and still sit behind a VPC endpoint, an Object Ownership setting, or an account-level Block Public Access that quietly overrides it. “Allows public” and “is public” are not the same sentence, and treating them as one is how teams waste an afternoon locking down something that was never reachable in the first place.

The bucket-policy vs. ACL vs. Block Public Access tangle

It helps to know why S3 exposure is confusing in the first place. AWS controls public access through three overlapping layers, and people fix the wrong one constantly:

  • Block Public Access (BPA) is the master switch, settable at the account and bucket level. When its settings are on, they override everything below, which is why it’s usually the right lever. Turn BPA on and the bucket is private regardless of what the policy or ACLs say.
  • Bucket policies are JSON documents that can grant public read with a Principal of *. A bucket can be exposed entirely through its policy with no ACL involved at all.
  • ACLs are the legacy mechanism. AWS now disables them by default on new buckets, but older buckets may still carry a public-read ACL granting access to everyone.

The reason “just make it private” goes wrong is that someone edits the bucket policy, sees public access is still allowed, and panics, when the actual exposure was an old ACL or a disabled BPA setting. You have to fix the layer that’s actually granting access, and that means seeing all three at once.

Step 3: fix it, with approval

Once you know which bucket is the real problem, close it precisely, and leave the ones doing their job alone:

Enable Block Public Access on prod-backups and leave the marketing-site bucket alone.

Kikimora proposes the exact change, shows you the precise BPA settings it’s about to apply, and waits. Nothing happens without your explicit yes; write actions always require human approval, by design. On approval it applies the change and writes an audit entry recording what changed, when, and on whose say-so. If you’d rather the change flow through your existing review process, ask for the Terraform instead and let it land as a pull request.

One honest caveat: Kikimora can tell you a bucket is reachable and unprotected, but it can’t always know your intent. A bucket full of public marketing assets and a bucket full of public backups look similar to an API; only you know which is a feature and which is a five-alarm fire. That’s exactly why the flow stops and asks rather than auto-remediating. The judgment about what’s supposed to be public stays with you.

What you didn’t have to do

Count the steps that just disappeared:

  • No region-hopping through the S3 console across multiple accounts.
  • No copy-pasting ARNs into a runbook to keep track of which bucket was which.
  • No guessing whether “public” meant “broken” or “working as intended.”
  • No fixing the bucket policy when the real exposure was a stale ACL.

The finding and the fix lived in the same conversation, with a human approval gate between proposal and change. If you want to take this further and map every exposed asset you forgot you had, not just the buckets, that’s the natural next step: see how to find every internet-facing asset.

Connect AWS and try it on your own buckets. The free tier includes 30 assets, unlimited integrations, and 5 million AI credits with no card, and it takes about five minutes: start free. The first question to ask is the one at the top of this post. The answer is usually a bucket you’d forgotten about.