This check flags S3 buckets that do not have all four Public Access Block settings enabled, which leaves a path open for accidental public exposure. Fix it by turning on full Block Public Access at both the account and bucket level.
S3 Block Public Access (BPA) is the safety net that stops a bucket from going public by accident. It is made up of four independent settings, and unless all four are switched on, there is still a route by which an ACL or a bucket policy can open your data to the world. This check catches buckets where one or more of those settings is missing or disabled.
Partial protection is the trap here. Teams often enable two of the four settings, see "public access blocked" in the console summary, and assume they are covered. They are not. A single misconfigured policy or a copied ACL can still expose the bucket.
What this check detects
The s3_public_access check inspects the PublicAccessBlockConfiguration on each S3 bucket and compares it against the four flags that make up a complete configuration. If any of them is set to false or absent, the bucket is flagged.
The four settings are:
- BlockPublicAcls — rejects new public ACLs and public object uploads via ACL.
- IgnorePublicAcls — ignores any existing public ACLs already attached to the bucket or its objects.
- BlockPublicPolicy — rejects any bucket policy that grants public access.
- RestrictPublicBuckets — restricts access to a bucket with a public policy so that only AWS service principals and authorized users can reach it.
Note: The first two settings deal with ACLs, the last two deal with bucket policies. ACLs and policies are two separate access mechanisms in S3, which is exactly why you need all four flags to fully close the door. Turning on only the ACL settings leaves the policy path wide open, and vice versa.
Worth knowing: Block Public Access can be set at two levels. The account level applies to every bucket in the account, and the bucket level applies to a single bucket. The account-level setting always wins when it is more restrictive. This check evaluates the effective configuration so a bucket can pass on its own settings or inherit full protection from the account.
Why it matters
Public S3 buckets remain one of the most common causes of large data leaks. The pattern repeats year after year: a bucket holding logs, backups, customer records, or internal documents gets a permissive policy or ACL, and that data ends up indexed, scraped, or sold.
The reason BPA matters so much is that it works regardless of who later edits the bucket. Without it, your data is only as safe as the most careless policy any engineer writes. With all four settings on, even a wide-open "Principal": "*" policy is neutralized.
Consider a realistic scenario. An engineer is debugging why a static asset will not load from a bucket. Under time pressure, they attach a policy granting s3:GetObject to everyone "just to test it." If BlockPublicPolicy is off, that policy takes effect immediately and the entire bucket is now readable by anyone on the internet. If it is on, S3 rejects the policy outright and the engineer is forced to find the correct, scoped fix.
Danger: A bucket without complete Block Public Access is one bad policy away from a breach. Attackers run automated scanners against S3 endpoints constantly. A bucket that becomes public is often discovered within minutes, not days.
The business impact goes beyond the data itself. Public exposure of regulated data triggers breach notification requirements under GDPR, HIPAA, and similar frameworks, with fines, mandatory disclosures, and reputational damage attached.
How to fix it
You can remediate at the bucket level, the account level, or both. For most teams the right answer is to enable account-level Block Public Access as a baseline, then keep it on per bucket for any that genuinely need a different posture.
Option 1: Fix a single bucket (CLI)
Warning: If a bucket currently serves content publicly on purpose (for example a static website fronted directly by S3), enabling these settings will break that access immediately. Confirm the bucket is not intentionally public before applying. The recommended pattern for public static content is to keep the bucket private and serve through CloudFront with Origin Access Control.
aws s3api put-public-access-block \
--bucket my-bucket-name \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
Verify the result:
aws s3api get-public-access-block --bucket my-bucket-name
You should see all four values returned as true:
{
"PublicAccessBlockConfiguration": {
"BlockPublicAcls": true,
"IgnorePublicAcls": true,
"BlockPublicPolicy": true,
"RestrictPublicBuckets": true
}
}
Option 2: Fix the whole account (CLI)
The account-level setting is the most effective control because it covers buckets that do not exist yet. New buckets inherit the protection automatically.
aws s3control put-public-access-block \
--account-id 123456789012 \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
Option 3: Console
- Open the S3 console and select the bucket.
- Go to the Permissions tab.
- Under Block public access (bucket settings), choose Edit.
- Check Block all public access to enable all four at once, then save.
- For account-wide settings, open Block Public Access settings for this account from the left navigation.
Option 4: Terraform
If you manage buckets as code, define the block explicitly so it cannot drift:
resource "aws_s3_bucket" "data" {
bucket = "my-bucket-name"
}
resource "aws_s3_bucket_public_access_block" "data" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
For the account baseline:
resource "aws_s3_account_public_access_block" "account" {
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
Tip: When you create a bucket through the AWS console today, all four settings are on by default. The buckets that fail this check are usually older ones, buckets created by scripts or SDKs that omit the configuration, or buckets where someone deliberately loosened a setting and never reverted it.
How to prevent it from happening again
Remediating a bucket once is not enough. Drift creeps back in through new buckets, automation, and well-meaning manual changes. Lock it down at the source.
Set the account-level block as your baseline
Enabling account-level BPA is the single highest-leverage move. It protects every existing and future bucket, and a per-bucket setting cannot override it to be more permissive. Make this a standing control across every account in your organization.
Enforce with Service Control Policies
In AWS Organizations, you can deny any attempt to weaken Block Public Access. This SCP blocks the API calls that would disable account-level protection:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyDisablingS3BPA",
"Effect": "Deny",
"Action": [
"s3:PutAccountPublicAccessBlock"
],
"Resource": "*",
"Condition": {
"Bool": {
"aws:PrincipalIsAWSService": "false"
}
}
}
]
}
Warning: An SCP this broad will also block legitimate administrators. Scope it to allow a dedicated break-glass role if you ever need to change the configuration, and test it in a non-production OU first.
Gate it in CI/CD
If you provision buckets through Terraform, run a policy-as-code check before apply. Both tfsec and checkov ship with rules for this out of the box:
# Checkov - fails the pipeline if a bucket lacks full public access block
checkov -d . --check CKV_AWS_53,CKV_AWS_54,CKV_AWS_55,CKV_AWS_56
Wire that into your pull request pipeline so a bucket without complete BPA never reaches an environment.
Add a guardrail with AWS Config
Turn on the managed rule s3-bucket-level-public-access-prohibited for continuous detection, and pair it with an automatic remediation action so any drift is corrected without human intervention.
Best practices
- Treat all four settings as one unit. Never enable a subset. "Block all public access" is the only configuration that fully closes both the ACL and policy paths.
- Disable ACLs entirely where you can. Set the bucket's object ownership to
BucketOwnerEnforced. This removes ACLs from the equation and forces all access decisions through IAM and bucket policies, which are far easier to reason about. - Serve public content through CloudFront, not S3 directly. Keep the bucket private and use a CloudFront distribution with Origin Access Control. You get a CDN, TLS, and caching while the bucket stays locked down.
- Default deny, grant narrowly. When a workload needs access, grant it to a specific IAM role or VPC endpoint, never to
"*". - Scan continuously. A bucket that passes today can fail tomorrow when someone changes it. Continuous monitoring, like the Lensix
s3_public_accesscheck, catches drift before an attacker does.
Tip: Run a quick audit across every bucket in an account to find the gaps before they become findings. This lists each bucket and its current block configuration so you can spot the partial ones at a glance.
for bucket in $(aws s3api list-buckets --query 'Buckets[].Name' --output text); do
echo "== $bucket =="
aws s3api get-public-access-block --bucket "$bucket" \
--query 'PublicAccessBlockConfiguration' --output json 2>/dev/null \
|| echo " NO PUBLIC ACCESS BLOCK CONFIGURED"
done
Block Public Access is cheap, has no performance cost, and prevents one of the most damaging classes of cloud misconfiguration. There is rarely a good reason to leave any of the four settings off. Set the account baseline, enforce it with policy, and let your monitoring catch anything that slips.

