S3 buckets should have policies that prohibit public access
An S3 bucket policy with "Principal": "*" and no limiting Condition block exposes objects to the entire internet. This is the root cause of most high-profile S3 data breaches, where sensitive records, backups, or logs become downloadable by anyone with the bucket name. Unlike S3 Block Public Access (which acts as a guardrail), the bucket policy defines the actual permissions, so checking its content is a direct test for exposure.
Public bucket policies also create indirect risk: even if current objects are non-sensitive, future writes inherit the same open access. Catching overly permissive policies at deploy time prevents silent exposure drift.
Retrofit consideration
Existing bucket policies with broad Principal grants may be serving legitimate use cases, static website hosting and cross-account replication are common ones. Tightening them requires auditing every consumer of the bucket before touching the policy, or you risk breaking dependent services.
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 = "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 = "nydfs23.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 = "hipaasecurity2003.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_policy = 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_policy = true
restrict_public_buckets = true
}
What this control checks
This control evaluates the aws_s3_bucket_policy resource and inspects the JSON policy document in the policy argument. Every Statement must restrict access through at least one of: a scoped Principal (specific AWS account, IAM role, or service principal rather than "*"), or a Condition block limiting access by aws:SourceIp, aws:SourceVpc, aws:SourceVpce, aws:PrincipalOrgID, or similar keys. A statement with "Principal": "*" or "Principal": {"AWS": "*"} and no restricting Condition fails. Buckets with no aws_s3_bucket_policy resource pass; no policy means no public grant exists. When using aws_iam_policy_document data sources, ensure each statement block sets principals to specific identities or includes a condition block with network or organization constraints.
Common pitfalls
Wildcard principal hidden in Allow with Deny override
Some teams use a broad
Allowwith"Principal": "*"paired with an explicitDenyto restrict access. The control flags theAllowstatement regardless, because it checks statement-level grants, not net-effective permissions. If your architecture relies on this pattern, refactor to scope thePrincipalon theAllowstatement directly rather than relying on theDenyto neutralize it.Policy generated outside Terraform
Raw JSON strings passed to
aws_s3_bucket_policyfrom external files or variables won't be parseable at plan time when they contain values interpolated at apply time. Useaws_iam_policy_documentinstead so the policy structure is visible duringterraform planand the control can evaluate it before anything is deployed.CloudFront OAI/OAC policies look public
CloudFront Origin Access Control policies should grant access to the
cloudfront.amazonaws.comservice principal with aConditiononAWS:SourceArnscoped to a specific distribution. Origin Access Identity policies use a canonical user principal. Get either the principal or the condition wrong, and the bucket falls back to public access with no obvious error on apply.Static website hosting exemption assumptions
Buckets serving static websites via
aws_s3_bucket_website_configurationneed public read access by design, and this control flags those policies with no automatic exemption. The cleaner path is CloudFront with OAC in front of a private bucket rather than direct public bucket access, which avoids the exemption problem entirely.
Audit evidence
Auditors expect Config rule evaluation results for each S3 bucket, typically via the managed rule s3-bucket-policy-grantee-check, showing compliant or non-compliant status per bucket. Supplementary evidence is a point-in-time export of all bucket policies from aws s3api get-bucket-policy across every account, confirming no policy contains an unscoped "Principal": "*" without a limiting condition. Screenshots from the S3 console policy editor for previously flagged buckets corroborate remediation.
For continuous assurance, Security Hub findings tied to this control, or CSPM scan results from Prowler or Steampipe showing a passing state across all regions and accounts, are what recurring audits ask to see.
Framework-specific interpretation
SOC 2: CC6.1 requires restricting access to information assets to authorized users. A public bucket policy is a direct failure of that criterion, and this check catches it.
PCI DSS v4.0: For systems that store cardholder data in S3, Requirement 7's business-need-to-know rules out any policy that grants unrestricted access. This control verifies no policy statement opens that door.
HIPAA Omnibus Rule 2013: 45 CFR 164.312(a)(1) requires covered entities to implement technical controls that limit access to ePHI to authorized users. A bucket policy with an unscoped "Principal": "*" puts any ePHI stored in that bucket on the open internet. This control checks that bucket-level permissions are explicitly bounded to authorized entities.
NIST SP 800-53 Rev 5: AC-3 (Access Enforcement) and AC-6 (Least Privilege) both apply here. S3 bucket policies are the primary access enforcement mechanism for object storage, and "Principal": "*" without conditions violates both by definition.
NIST Cybersecurity Framework v2.0: S3 bucket policies are the data-layer enforcement point for PR.AA, which covers identity management and access control under the Protect function. Restricting them to named principals or network boundaries is how least privilege gets applied at the storage layer, preventing uncontrolled data disclosure.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
s3_bucket_policy_restrict_public_accessAWS Config Managed Rule:
S3_BUCKET_LEVEL_PUBLIC_ACCESS_PROHIBITEDCheckov Check:
CKV_AWS_70Powerpipe Control:
aws_compliance.control.s3_bucket_policy_restrict_public_accessProwler Checks:
s3_bucket_policy_public_write_access,s3_bucket_public_accessAWS Security Hub Controls:
S3.2,S3.3KICS Query:
7af43613-6bb9-4a0e-8c4d-1314b799425e
Last reviewed: 2026-03-09