S3 public access should be blocked at bucket level
A single misconfigured bucket ACL or bucket policy can expose sensitive data to the internet. Account-level public access blocks provide a safety net, but bucket-level blocks give you explicit, per-bucket enforcement that survives account-level setting changes and makes intent visible in code review.
Public S3 bucket exposures remain one of the most common causes of large-scale data breaches. Enforcing all four flags at the bucket level closes off entire classes of accidental exposure, including cases where a developer attaches a wildcard principal policy or sets a canned ACL like public-read.
Retrofit consideration
Existing buckets intentionally serving public content (static websites, public datasets) will fail. Identify them and either exclude them from the policy or migrate to CloudFront with an origin access control pointing at a private bucket.
Implementation
Choose the approach that matches how you manage Terraform.
Use the compliance.tf module to enforce this control by default. See get started with compliance.tf.
module "s3_bucket" {
source = "pcidss.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "hipaa.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nist80053.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nistcsf.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "fedrampmoderate.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "cisv80ig1.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nist800171.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "cisacyberessentials.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "cisv140.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "acscessentialeight.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "cfrpart11.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "rbiitfnbfc.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "fedramplow.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "iso270012013.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nistcsfv11.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "pcidssv321.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
If you use terraform-aws-modules/s3-bucket/aws, set the right module inputs for this control. You can later migrate to the compliance.tf module with minimal changes because it is compatible by design.
module "s3_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
Use AWS provider resources directly. See docs for the resources involved: aws_s3_bucket_public_access_block.
resource "aws_s3_bucket" "this" {
bucket = "pofix-abc123"
force_destroy = true
}
resource "aws_s3_bucket_public_access_block" "this" {
bucket = "example-bucket-abc123"
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
What this control checks
The policy checks that every S3 bucket has an aws_s3_bucket_public_access_block resource with all four arguments set to true: block_public_acls, ignore_public_acls, block_public_policy, and restrict_public_buckets. It fails if any argument is false, omitted (each defaults to false), or if no block resource exists for the bucket. The bucket argument must reference the target aws_s3_bucket resource. This is separate from aws_s3_account_public_access_block, which applies globally; both should be configured, but this policy only checks bucket-level settings.
Common pitfalls
Omitted arguments default to false
Each of the four arguments in
aws_s3_bucket_public_access_blockdefaults tofalseif not set explicitly. Creating the resource with onlyblock_public_acls = trueleaves the other three off and the control fails. Set all four totrueexplicitly every time.Account-level block does not satisfy bucket-level check
aws_s3_account_public_access_blockwith all four flags set totrueprotects the account globally, but this control checks the bucket-level resource specifically. Account-level and bucket-level blocks are evaluated independently. You need both.Static website hosting conflict
Enabling all four block settings on a bucket configured for static hosting via
aws_s3_bucket_website_configurationwill break public access to the site. Move off direct S3 hosting and serve through CloudFront withaws_cloudfront_origin_access_controlpointing at a private bucket.Terraform import may miss the resource
aws_s3_bucket_public_access_blockis a separate resource fromaws_s3_bucket. When you import an existing bucket withterraform import aws_s3_bucket.example my-bucket, Terraform has no visibility into the public access block unless you also import or declareaws_s3_bucket_public_access_block. Get this wrong and the control treats the bucket as unprotected even if the block is enabled in AWS.
Audit evidence
The primary evidence is the Config rule s3-bucket-level-public-access-prohibited evaluation showing all buckets as COMPLIANT. Direct API confirmation via aws s3api get-public-access-block --bucket <name> showing all four fields as true works as point-in-time evidence for individual buckets. For continuous posture, a Security Hub or CSPM dashboard showing zero public S3 buckets is standard.
If buckets are intentionally excluded for public static content, auditors will want a documented exception with business justification, risk acceptance sign-off, and evidence that those buckets hold no sensitive data.
Framework-specific interpretation
PCI DSS v4.0: Requirements 7.1 and 7.2 restrict access to system components by business need and least privilege. For S3 buckets holding cardholder data, public accessibility is never a valid business need, and bucket-level blocks enforce that even when someone later introduces a permissive bucket policy or canned ACL.
HIPAA Omnibus Rule 2013: 45 CFR 164.312(a)(1) requires access controls and 164.312(e)(1) addresses transmission security. PHI in S3 must not be reachable by the public under either provision. Bucket-level blocks prevent accidental exposure through misconfigured ACLs or wildcard policies, which is the failure mode these provisions are designed to catch.
NIST SP 800-53 Rev 5: AC-3, AC-6, and SC-7 together require access enforcement, least privilege, and boundary protection. Bucket-level blocks address all three: they enforce the access boundary, prevent privilege escalation via public ACL or policy grant, and apply per-resource rather than globally, which is the scope AC-6 expects.
NIST Cybersecurity Framework v2.0: PR.DS and PR.AA, both in the Protect function, cover data security and identity management respectively. Blocking public access at the bucket level covers the storage side of both subcategories.
FedRAMP Moderate Baseline Rev 4: AC-3 calls for access enforcement that prevents unauthorized disclosure of federal data. At the Moderate baseline, any S3 bucket holding government data needs explicit access boundaries set at the resource level, not just the account, so that a downstream account-level change can't silently widen exposure.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
s3_public_access_block_bucketAWS Config Managed Rule:
S3_BUCKET_LEVEL_PUBLIC_ACCESS_PROHIBITEDCheckov Check:
CKV2_AWS_6Powerpipe Control:
aws_compliance.control.s3_public_access_block_bucketProwler Check:
s3_bucket_level_public_access_blockAWS Security Hub Controls:
S3.1,S3.8KICS Queries:
1ec253ab-c220-4d63-b2de-5b40e0af9293,4fa66806-0dd9-4f8d-9480-3174d39c7c91Trivy Checks:
AWS-0086,AWS-0087,AWS-0091,AWS-0093,AWS-0094
Last reviewed: 2026-03-09