Skip to content

S3 buckets should prohibit public read access

A single publicly readable S3 bucket can expose customer data, credentials, database backups, or application source code to the entire internet. Data breaches from misconfigured S3 buckets have resulted in hundreds of millions of exposed records and regulatory fines exceeding $100 million. These exposures frequently stem from permissive ACLs or bucket policies granting s3:GetObject to the * principal.

Enabling S3 Block Public Access at both the account and bucket level is the most direct way to prevent this. Without it, any IAM principal with s3:PutBucketPolicy or s3:PutBucketAcl permissions can inadvertently (or intentionally) make a bucket public with a single API call.

Retrofit consideration

Existing buckets serving public content (static websites, public assets) will fail this control. Migrate public content to CloudFront with an origin access control before enabling Block Public Access, or you will break production workloads.

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  = "soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws"
  version = ">=5.0.0"

  bucket = "abc123"
}

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  = "awscontroltower.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  = "nydfs23.compliance.tf/terraform-aws-modules/s3-bucket/aws"
  version = ">=5.0.0"

  bucket = "abc123"
}

module "s3_bucket" {
  source  = "ffiec.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  = "rbicybersecurity.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  = "hipaasecurity2003.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  = "nist80053rev4.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 control checks that S3 buckets cannot be read publicly. The primary Terraform resource is aws_s3_bucket_public_access_block, which must be attached to every aws_s3_bucket. To pass, block_public_acls and block_public_policy must both be true to prevent future public grants. ignore_public_acls = true neutralizes any existing public ACLs. restrict_public_buckets = true overrides access granted by public bucket policies, restricting it to AWS service principals and authorized users. All four arguments must be true for complete protection.

No aws_s3_bucket_acl resource may use the public-read or public-read-write canned ACL values. No aws_s3_bucket_policy may contain statements granting s3:GetObject, s3:ListBucket, or s3:Get* to a principal of "*" or {"AWS": "*"} without appropriate conditions. Account-level protection via aws_s3_account_public_access_block with all four settings enabled is a useful backstop, but it does not replace bucket-level configuration for this control.

Common pitfalls

  • Legacy inline ACL grants in aws_s3_bucket

    Older Terraform configurations may use the deprecated acl argument directly on aws_s3_bucket (e.g., acl = "public-read"). This still works in some provider versions and can silently grant public read access even when a separate aws_s3_bucket_public_access_block exists with ignore_public_acls set to false. Migrate to aws_s3_bucket_acl as a separate resource and set ignore_public_acls = true.

  • Bucket policy added outside Terraform

    A bucket policy granting public read can be attached via the AWS Console, CLI (aws s3api put-bucket-policy), or another IaC pipeline without Terraform knowing about it. Terraform won't detect the drift unless you import the aws_s3_bucket_policy resource. Setting restrict_public_buckets = true in aws_s3_bucket_public_access_block overrides such out-of-band changes, but importing the resource is still the right long-term fix.

  • Block Public Access not applied to all four settings

    Previously applied public ACLs or policies stay effective if you only set block_public_acls = true and block_public_policy = true. Those two flags prevent new public grants but do nothing about existing ones. You also need ignore_public_acls = true and restrict_public_buckets = true. All four arguments on aws_s3_bucket_public_access_block must be true.

  • S3 static website hosting conflicts

    Buckets configured with aws_s3_bucket_website_configuration for static hosting require public s3:GetObject access unless served through CloudFront with an origin access control (aws_cloudfront_origin_access_control). Enabling Block Public Access on these buckets will break direct website endpoints. Refactor to CloudFront before enforcing this control.

  • Account-level block does not appear in bucket-level checks

    Get this wrong and you will have buckets that are effectively protected but still flagged as non-compliant. Enabling aws_s3_account_public_access_block protects all buckets at the account level, but some compliance tools evaluate bucket-level settings independently. A bucket without its own aws_s3_bucket_public_access_block resource can still be flagged even when the account-level block is active. Apply both.

Audit evidence

Auditors expect evidence that public read access is blocked across all in-scope S3 buckets. The AWS Config rule s3-bucket-public-read-prohibited, with all buckets showing COMPLIANT, is the clearest artifact. S3 console screenshots showing all four Block Public Access toggles enabled per bucket, alongside the account-level Block Public Access settings page, provide supporting documentation.

Auditors may also ask for aws s3api get-public-access-block --bucket <name> and aws s3api get-bucket-policy-status --bucket <name> output showing "IsPublic": false. Access Analyzer for S3 findings confirming no buckets are publicly accessible round out the picture. Security Hub or Trusted Advisor reports summarizing public access posture across the account are also accepted.

Framework-specific interpretation

SOC 2: CC6.6 is about restricting external access; CC6.1 requires that information assets are accessible only to authorized users. Public read access on an S3 bucket violates both, and Type II examiners routinely flag it as a finding.

PCI DSS v4.0: Requirement 7 calls for access to cardholder data to be limited to individuals whose job requires it. A publicly readable S3 bucket containing cardholder data or system configuration files is a straightforward Requirement 7 violation. Requirement 7.2.1 goes further, requiring access control systems that enforce need-to-know, which anonymous public access does not satisfy.

HIPAA Omnibus Rule 2013: Public read access to S3 buckets containing ePHI violates the HIPAA Security Rule's access control requirements under 45 CFR 164.312(a)(1). The Omnibus Rule made clear that covered entities and business associates must limit ePHI access to authorized persons only. Unrestricted public access is a direct violation of that standard, not a configuration gray area.

NIST SP 800-53 Rev 5: AC-3, AC-6, and SC-7 all apply here. AC-3 requires systems to enforce approved authorizations for logical access, and anonymous public access bypasses that entirely. SC-7 covers boundary protection; a publicly readable bucket is a gap in the boundary that those access controls were meant to close.

NIST Cybersecurity Framework v2.0: S3 buckets with public read access fail PR.AA directly: anonymous object retrieval involves no authentication or authorization. PR.DS adds a second concern, since there is no control over who reads objects stored in the bucket. Both subcategories sit under the Protect function, and a publicly readable bucket fails both.

FedRAMP Moderate Baseline Rev 4: At the Moderate baseline, AC-3 and AC-6 are required controls. Federal data in S3 cannot be publicly accessible, and publicly readable buckets are one of the more common findings during annual assessments. They have delayed and blocked authorizations, and assessors look for them specifically.

Tool mappings

Use these identifiers to cross-reference this control across tools, reports, and evidence.

  • Compliance.tf Control: s3_bucket_restrict_public_read_access

  • AWS Config Managed Rule: S3_BUCKET_PUBLIC_READ_PROHIBITED

  • Checkov Check: CKV_AWS_20

  • Powerpipe Control: aws_compliance.control.s3_bucket_restrict_public_read_access

  • Prowler Checks: s3_bucket_level_public_access_block, s3_bucket_public_access, s3_bucket_public_list_acl

  • AWS Security Hub Controls: S3.2, S3.8

  • KICS Queries: 1df37f4b-7197-45ce-83f8-9994d2fcf885, 1ec253ab-c220-4d63-b2de-5b40e0af9293, 38c5ee0d-7f22-4260-ab72-5073048df100, 57b9893d-33b1-4419-bcea-a717ea87e139, 66c6f96f-2d9e-417e-a998-9058aeeecd44, 7af43613-6bb9-4a0e-8c4d-1314b799425e, bf878b1a-7418-4de3-b13c-3a86cf894920, d0cc8694-fcad-43ff-ac86-32331d7e867f

  • Trivy Checks: AWS-0086, AWS-0093

Last reviewed: 2026-03-09