S3 PutBucketPolicy Ignores Block Public Access State
A wildcard bucket policy can be written and saved successfully in S3 even while every Block Public Access flag is enabled, because the API validates syntax, not exposure. The bucket sits dormant until someone flips the account-level BPA setting, and objects go public with no new policy event in CloudTrail to catch.
Operational summary
At a glance
- Symptom
- Account-level or bucket-level Block Public Access is disabled or was never enabled, instantly activating an already-saved wildcard policy.
- Likely cause
- PutBucketPolicy accepts Principal: "*" successfully regardless of current Block Public Access state, since enforcement happens only at data-plane read/write time.
- Impact
- Every object under the affected bucket becomes publicly accessible with no corresponding PutBucketPolicy event for alerting tools to catch.
- Verification signal
- Re-run the detection test and a controlled negative-path test.
- Safe correction
- Lock Block Public Access at the account level via s3control put-public-access-block, deny changes through an SCP, and monitor exposure continuously with IAM Access Analyzer for S3.
- Rollback or recovery
- Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.
The Trap
PutBucketPolicy accepts a resource policy with "Principal": "*" and no scoping conditions regardless of the bucket’s or account’s Block Public Access configuration. S3 only enforces BlockPublicPolicy, IgnorePublicAcls, BlockPublicAcls and RestrictPublicBuckets at data-plane request time, on GetObject, ListBucket and similar calls. The write path is entirely separate from the enforcement path, so a dangerous policy can sit committed and inert for months.
The Default State
Buckets created through the S3 console since 2023 get all four BPA flags set to true automatically. Buckets provisioned through CloudFormation, Terraform or the CLI without an explicit aws_s3_bucket_public_access_block resource, or an equivalent PutPublicAccessBlock call, inherit only whatever the account-level setting provides. Many accounts migrated before the account-level default existed, or had it disabled during a one-off migration and never re-enabled it. Engineers writing the wildcard policy see the PutBucketPolicy call succeed and assume the platform would have rejected an unsafe configuration if one existed.
The Blast Radius
Detection tooling built around CloudTrail alerts on PutBucketPolicy events misses the actual exposure entirely, because the triggering action is a later PutAccountPublicAccessBlock or per-bucket BPA change, not a policy write. The moment account-level BPA is disabled, every wildcard policy already resting in the account activates simultaneously across every affected bucket, with no corresponding policy-change event that day. Static analysis run at deploy time reports the bucket as compliant, since BPA was enabled during the scan, and nobody re-scans after unrelated account-level toggles.
The Lead Mechanic Fix
Treat account-level BPA as a locked control-plane setting rather than a per-project toggle. Apply it explicitly with aws s3control put-public-access-block --account-id ACCOUNT_ID --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true, then deny s3:PutAccountPublicAccessBlock and s3:PutBucketPublicAccessBlock in a Service Control Policy scoped to everyone except a named break-glass role. Because SCPs cannot inspect the content of a resource policy document, pair this with continuous evaluation from IAM Access Analyzer for S3, which flags external access based on actual policy effect at any time, not just at write time, closing the gap that PutBucketPolicy leaves open.
Apply the safer control
Before you change production
Confirm the affected scope, export the current configuration, and test the replacement control in a non-production environment first.
Fix commands and configuration
aws s3control put-public-access-block --account-id ACCOUNT_ID --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=trues3:PutAccountPublicAccessBlocks3:PutBucketPublicAccessBlockVerify, roll back or escalate
Verify
Re-run the detection test and a controlled negative-path test. Confirm the unsafe behaviour is blocked while approved traffic still succeeds.
Rollback
Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.
Escalate
Escalate when the blast radius is uncertain, the control cannot be tested safely, or remediation requires an outage or security exception.