Skip to main content
KBY Technologies Logo
cd ../config-traps
risk/register/account-level-block-public-access-the-silent-reversal.html
AWS Storage Securitycritical severityAWS S3

Account-Level Block Public Access: The Silent Reversal

Overview

A single Terraform apply that flips all four account-level Block Public Access flags to false on AWS S3 reactivates every dormant wildcard GetObject statement written years earlier, and no bucket-level control remains to stop it because RestrictPublicBuckets no longer applies.

At a glance

Unsafe setting
All four account-level Block Public Access flags set to false to fix one bucket's access error.
Failure trigger
Account-wide BPA override reactivates every pre-existing wildcard GetObject bucket policy across the account simultaneously.
Blast radius
Every bucket with a legacy Principal wildcard statement becomes internet-readable with no deployment event to alert on.
Recommended control
Enforce account-level BPA via SCP denial and require bucket-scoped exemptions with explicit Condition blocks instead of account-wide toggles.

Fix commands and configuration

aws s3control put-public-access-block --account-id <id> --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
PublicAccessBlockConfiguration
s3:PutAccountPublicAccessBlock

The Trap

Account-level S3 Block Public Access (BPA) override masking legacy wildcard bucket policies. The four settings — BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, and RestrictPublicBuckets — are treated as a single on/off toggle rather than four independent controls, and engineers disable all of them at once to solve one narrow problem.

The Default State

New AWS accounts opened since April 2023 ship with account-level BPA fully enabled, which is correct. The trap is introduced later: a CloudFront-to-S3 migration, a static website hosting requirement, or a vendor integration fails with AccessDenied, and the fix committed to the Terraform module is aws_s3_account_public_access_block with all four booleans set to false. This satisfies the one bucket that needed public read access, but the setting is account-wide, not bucket-scoped. Nobody audits the other buckets in the account before merging, because BPA was previously assumed to be a hard backstop against any pre-existing Principal: "*" statement.

The Blast Radius

Every bucket policy in the account is re-evaluated the moment RestrictPublicBuckets flips to false. Old statements granting s3:GetObject to Principal: "*" — written for a 2019 static site, a partner data drop, or a since-abandoned proof of concept — stop being suppressed by the account guardrail and become internet-readable immediately, with no deployment event, no CloudTrail write action, and no application-layer change to trigger existing alerting. S3 Storage Lens and Access Analyzer for S3 will eventually flag the exposure, but by the time those findings surface, objects have already been enumerated and pulled by scanners that continuously probe for exactly this condition. There is no rollback window: the data was public from the second the account setting changed.

The Lead Mechanic Fix

Never disable all four BPA flags together. Run aws s3control put-public-access-block --account-id <id> --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true as the enforced baseline, and grant the one exceptional bucket a scoped exemption via its own bucket-level PublicAccessBlockConfiguration, not an account-wide relaxation. Attach an SCP denying s3:PutAccountPublicAccessBlock outside a break-glass role, and require every bucket policy with a wildcard Principal to carry an explicit Condition block — aws:SourceVpce or aws:Referer — validated by a pre-merge Access Analyzer for S3 check before Terraform apply proceeds.