Skip to main content

S3 controls (124)

CTL.S3.ACCEL.001

S3 Transfer Acceleration Must Not Be Unexpectedly Enabled

  • Severity: low
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: nist_800_53_r5: CM-7;

S3 Transfer Acceleration creates an additional public endpoint (s3-accelerate.amazonaws.com) that bypasses VPC endpoint restrictions. It must not be enabled unless explicitly required and documented.

Remediation: Suspend Transfer Acceleration under bucket Properties unless explicitly required. If required, document the business justification and ensure VPC endpoint policies account for the acceleration endpoint.


CTL.S3.ACCESS.001

No Unauthorized Cross-Account Access

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws_v1.4.0: 1.16; nist_800_53_r5: AC-3; pci_dss_v3.2.1: 7.1; soc2: CC6.3;

S3 bucket policies must not grant access to external AWS accounts. allowed_accounts contains trusted external AWS account IDs (12-digit). Access from accounts outside this allowlist is unsafe.

Remediation: Review bucket policy Principal elements for external account IDs. Remove statements granting access to accounts not in your organization. Use aws:PrincipalOrgID condition to restrict access to your AWS Organization.


CTL.S3.ACCESS.002

No Wildcard Principal Policies

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: nist_800_53_r5: AC-6;

S3 bucket policies must not grant access to a wildcard principal (Principal "" or AWS: ""). A wildcard principal makes every statement in the policy effectively anonymous or cross-account unless a restricting Condition (aws:PrincipalOrgID, aws:SourceVpc, aws:SourceIp with a fixed CIDR, aws:SourceArn, etc.) narrows it. Without such a Condition, the policy is a public-access grant regardless of which specific S3 actions are allowed.

Remediation: 1. Narrow the Allow. Replace the wildcard principal with the specific AWS account IDs or role ARNs that actually need access. If a wildcard must remain (cross-org distribution, service-bound caller), add a restricting Condition that fixes the caller set: aws:PrincipalOrgID, aws:SourceVpc / aws:SourceVpce, aws:SourceIp with a fixed CIDR, or aws:SourceArn.

  1. If the bucket genuinely needs to be reachable from the public internet (CDN origin, static asset distribution), prefer scoped mechanisms over a wildcard bucket policy: CloudFront Origin Access Control, S3 Access Points with network-bound policies, or signed/presigned URLs. These give you a narrow Allow on a defined caller surface instead of a "*" Allow with hopeful Conditions.

  2. Keep S3 Block Public Access fully enforced as an interim guard while the policy change is being rolled out — PAB does not fix a broad Allow, it only suppresses its effect at the network boundary, so treat it as a containment layer rather than the remediation.


CTL.S3.ACCESS.003

No External Write Access

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure

S3 buckets must not grant write or delete permissions to external AWS accounts. Cross-account read access may be acceptable for analytics or auditing, but write access from external accounts creates data integrity and supply chain risks.

Remediation: Remove bucket policy statements granting s3:PutObject, s3:DeleteObject, or s3:PutBucketPolicy to external accounts. If cross-account write is required, restrict to specific account IDs with condition keys.


CTL.S3.ACCESS.004

Bucket Policy Must Not Be Effectively Public

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(a)(1); nist_800_53_r5: AC-3; soc2: CC6.1;

Bucket policy evaluates as effectively public under AWS PolicyStatus.IsPublic semantics — a wildcard principal (Principal: "*" or Principal: {"AWS": "*"}) without a scoping Condition on aws:SourceVpc, aws:SourceVpce, aws:PrincipalOrgID, aws:PrincipalArn, or a narrow aws:SourceIp. The control reads the policy in isolation: Public Access Block state does not affect whether it fires — PAB only affects whether the exposure is active right now or latent (one account-level or bucket-level PAB toggle away from active). Paired with the PAB controls, this is the posture signal that says "if every PAB layer were removed tomorrow, this bucket would be public". Distinct from CTL.S3.ACCESS.002, which detects the raw presence of a wildcard principal without reasoning about scoping Conditions: ACCESS.002 answers "is there a wildcard whose Conditions we still need to verify?", ACCESS.004 answers "has the verification already concluded that the policy is public?".

Remediation: 1. Narrow the Allow. Identify the Allow statement with the wildcard principal. If it is not intentional, remove it. If it is intentional (CDN origin, cross-organization data distribution), add a Condition that fixes the caller set — aws:PrincipalOrgID for same-org access, aws:SourceVpc or aws:SourceVpce for VPC-bound access, aws:SourceIp with a fixed CIDR for known network ranges, or aws:SourceArn for a specific invoking service or distribution.

  1. If the bucket genuinely needs internet reachability, prefer scoped mechanisms over a wildcard bucket policy: CloudFront Origin Access Control, S3 Access Points, or signed / presigned URLs. These collapse the reachable principal set to a defined surface instead of "*" with restricting Conditions stacked on top.

  2. Keep S3 Block Public Access fully enforced at the account and bucket level as an interim containment guard while the policy change is in flight. PAB does not fix the underlying broad Allow — it only suppresses its effect at the network boundary, leaving the exposure latent rather than active.


CTL.S3.ACCESS.EXTERNAL.ORG.001

PHI Buckets Must Not Be Reachable by External Principals

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(a)(1); nist_800_53_r5: AC-6; owasp_nhi: NHI5; soc2: CC6.3;

S3 buckets tagged data-classification=phi must not have policy statements granting access to principals outside the AWS Organization. The control reads the engine-derived external_account_ids list (set when bucket policy Principals or Conditions admit any non-org account); a non-empty list paired with a phi data-classification tag is an HIPAA-reportable breach exposure regardless of which specific S3 actions are allowed.

Remediation: 1) Review the bucket policy and remove any statement whose Principal lists an external AWS account ID. 2) If cross-org access is intentional (e.g., a sanctioned BAA partner), scope the Allow with aws:PrincipalOrgID matching the partner's organization ID and aws:PrincipalArn restricting to the specific partner role. 3) Tag the bucket with phi_external_intended: <baa-id> to suppress this control only after the legal/compliance review is on file.


CTL.S3.ACCESS.GRANTS.001

S3 Access Grants Must Not Grant Broad Permissions

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(a)(1);

S3 Access Grants provide temporary credentials scoped to a bucket or prefix. An Access Grant with READWRITE permission on a broad scope (entire bucket or wildcard prefix) bypasses bucket policy restrictions.

Remediation: Restrict grant scope to specific prefixes. Use READ not READWRITE.


CTL.S3.ACCESS.GRANTS.002

S3 Access Grants Identity Center Must Be Attached

  • Severity: medium
  • Type: unsafe_state
  • Domain: governance

When S3 Access Grants are enabled, IAM Identity Center should be attached to the Access Grants instance. Without Identity Center, grants can only target IAM principals — losing the benefit of centralized identity governance and SSO-based access control.

Remediation: Associate IAM Identity Center with the Access Grants instance using aws s3control associate-access-grants-identity-center. This enables directory-based grantee resolution.


CTL.S3.ACCESS.PHI.001

PHI Bucket Access Must Be Scoped to Specific Principals

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: AC-6; hipaa: 164.502(b); nist_800_53_r5: AC-6; pci_dss_v4.0: 7.2.1; soc2: CC6.1;

S3 buckets tagged with data-classification=phi must have access restricted to explicitly named principals and prefixes. Broad bucket-level access (wildcard principals, unrestricted actions) on PHI data violates the HIPAA minimum necessary standard (§164.502(b)). Access must be narrowed to the exact IAM roles, account IDs, and object prefixes required for each authorized workflow.

Remediation: Restrict bucket policy to named IAM role ARNs and specific object prefixes. Remove wildcard principals and broad s3:* actions. Use IAM Access Analyzer to identify unused permissions and generate least-privilege policies from CloudTrail activity.


CTL.S3.ACCESSPOINT.BROAD.001

S3 Access Point Policy Overly Broad

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: AC-3; nist_800_53_r5: AC-3; pci_dss_v4.0: 7.2.1; soc2: CC6.1;

S3 access point has a policy that grants broader access than the bucket policy. Access points are intended to scope access for specific use cases — a broad access point policy defeats this purpose and may grant access that the bucket policy restricts. Access points with Internet network origin and broad policies are effectively public endpoints.

Remediation: Restrict the access point policy to the specific principals and operations required for its use case. Access points should scope access narrower than the bucket policy, not broader. Set network origin to VPC if internet access is not required.


CTL.S3.ACCOUNT.BOUNDARY.001

S3 Bucket Owned by Account Not in Organization

  • Severity: high
  • Type: unsafe_state
  • Domain: governance
  • Compliance: nist_800_53_r5: AC-2; soc2: CC6.1;

S3 bucket is owned by an AWS account that is not a member of the organization. The bucket is outside organizational governance — no SCPs apply, no centralized logging, no CloudTrail. The bucket may belong to a former employee's personal account, a retired project account, or an account moved to a different organization.

Remediation: Move the bucket to an account within the organization, or migrate the data and decommission the external bucket. Ensure all data-bearing buckets are in accounts governed by organizational SCPs and centralized logging.


CTL.S3.ACCOUNT.PAB.001

Account-Level Block Public Access Must Be Enabled

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws_v3.0: 2.1.4; fedramp_moderate: AC-3; gdpr: Art.32; iso_27001_2022: A.8.3; nist_800_53_r5: AC-3; nist_csf_2.0: PR.PS; soc2: CC6.1;

The AWS account must have S3 Block Public Access enabled at the account level. Account-level PAB overrides all bucket and object settings, providing a hard ceiling that prevents any S3 resource in the account from being made public regardless of bucket policies, ACLs, or access point policies. Without account-level PAB, each bucket's public access depends on its own settings, and a single misconfigured bucket or object ACL can expose data. Account-level PAB is the strongest single defense against accidental public exposure.

Remediation: Enable all four S3 Block Public Access settings at the account level using aws s3control put-public-access-block with the --account-id parameter. This blocks public access for all current and future buckets in the account. If specific buckets require public access, use CloudFront with Origin Access Control instead of making buckets directly public.


CTL.S3.ACCOUNT.SCP.001

S3 Bucket Account SCP Does Not Restrict S3 Public Access

  • Severity: medium
  • Type: unsafe_state
  • Domain: governance
  • Compliance: nist_800_53_r5: AC-6; soc2: CC6.1;

The account's Service Control Policy does not include restrictions on S3 public access configuration changes. Without an S3-restrictive SCP, account-level Public Access Block settings can be changed by any account administrator, undermining organization-level security guardrails.

Remediation: Add an SCP to the organization that denies s3:PutBucketPublicAccessBlock and s3:PutBucketPolicy actions that would weaken public access restrictions. This prevents account administrators from overriding account-level PAB settings.


CTL.S3.ACL.ESCALATION.001

No Public ACL Modification

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure

S3 bucket ACLs must not be writable by AllUsers or AuthenticatedUsers. WRITE_ACP permission enables attackers to modify the ACL itself, granting themselves FULL_CONTROL and escalating to read, write, and delete all objects.

Remediation: Remove WRITE_ACP grants from the bucket ACL and remove policy statements granting s3:PutBucketAcl or s3:PutObjectAcl to public principals. Enable S3 Public Access Block with BlockPublicAcls set to true.


CTL.S3.ACL.FULLCONTROL.001

No FULL_CONTROL ACL Grants to Public

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: nist_800_53_r5: AC-3;

S3 bucket ACLs must not grant FULL_CONTROL to AllUsers or AuthenticatedUsers. FULL_CONTROL is the worst-case ACL misconfiguration — the grantee can read, write, and delete objects and modify the ACL itself.

Remediation: Replace the bucket ACL with "BucketOwnerFullControl" or remove the FULL_CONTROL grant to public groups. Enable S3 Public Access Block with BlockPublicAcls and IgnorePublicAcls set to true.


CTL.S3.ACL.OBJECT.001

Objects Must Not Be Individually Public via ACL

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: AC-3; gdpr: Art.32; iso_27001_2022: A.8.3; nist_800_53_r5: AC-3; soc2: CC6.1;

S3 buckets must not contain objects that are individually made public through object-level ACL grants. When a bucket itself is not public, individual objects can still be accessible from the internet if their ACL grants read access to AllUsers or AuthenticatedUsers. This is the "Objects can be public" status in AWS — the bucket is private but objects inside it are exposed. This is a primary vector for data leakage through misplaced sensitive files, where a single object with a public ACL in an otherwise private bucket exposes data that was never intended to be public.

Remediation: Set Object Ownership to BucketOwnerEnforced to disable all ACLs. If that is not immediately possible, enable S3 Block Public Access with IgnorePublicAcls set to true, then audit object ACLs using S3 Inventory with the optional ACL fields. Remove public grants from individual objects using aws s3api put-object-acl.


CTL.S3.ACL.RECON.001

No Public ACL Readability

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure

S3 bucket ACLs should not be readable by unauthenticated users. READ_ACP permission enables attackers to enumerate ACL grants, discover which principals have access, and find escalation paths.

Remediation: Remove READ_ACP grants from the bucket ACL and remove policy statements granting s3:GetBucketAcl or s3:GetObjectAcl to public principals. Enable S3 Public Access Block with BlockPublicAcls set to true.


CTL.S3.ACL.RECUR.001

S3 Bucket ACL Must Not Oscillate to Public-Read Repeatedly

  • Severity: high
  • Type: unsafe_recurrence
  • Domain: exposure
  • Compliance: hipaa: 164.308(a)(1)(ii)(D); nist_800_53_r5: IR-5; soc2: CC7.1;

S3 bucket ACL has been set to public-read more than once within 7 days. ACL modification is a deliberate action — not accidental IaC drift. A single recurrence within a week is a strong signal of intentional repeated action requiring investigation.

Remediation: Investigate the root cause of the repeated oscillation. Determine whether the pattern indicates a broken process, operational workaround, or active compromise. Review CloudTrail for the API calls that triggered each transition.


CTL.S3.AP.BYPASS.001

Bucket Must Not Be Publicly Accessible Via An Access Point While Its Own Controls Pass

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(a)(1); soc2: CC6.1;

An S3 bucket whose own controls all evaluate clean — Public Access Block fully enforcing, bucket policy not effectively public — can still be publicly reachable through a single-region S3 Access Point that names it as the delegate bucket. Access Points carry their own PAB and their own resource policy, evaluated independently of the parent bucket; a public Access Point is a parallel reach path. This control fires on the bucket, not the AP, because the finding is "this bucket is exposed via an AP it may not know about" — the bucket is the asset at risk. Mirrors the shape of CTL.S3.CDN.BYPASS.001 (the CloudFront-fronted-bucket bypass). Firing requires the bucket-side controls to be clean: if the bucket is already publicly accessible on its own, the bucket-level controls already caught it and this finding would be noise. The derived field storage.exposure.has_public_access_point comes from the EnrichBucketAPExposure derivation — a post-collection join of aws_s3_access_point assets onto aws_s3_bucket assets within the same snapshot; the trace decomposes back to the raw AP and bucket observations that produced it.

Remediation: 1. Identify the offending Access Point from the storage.exposure.public_access_point_names field on this finding, or from the co-occurring AP-side findings. 2. On the Access Point, enable Block Public Access fully (put-access-point-public-access-block with all four flags true) and remove any wildcard-principal Allow from its resource policy — or add a restricting Condition on aws:SourceVpc, aws:SourceVpce, aws:PrincipalOrgID, aws:PrincipalArn, or a fixed aws:SourceIp. 3. Consider whether the Access Point is still needed. Access Points left over from decommissioned services are a common source of this pattern.


CTL.S3.AP.PAB.001

S3 Access Point Must Have Block Public Access Enabled

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(a)(1); soc2: CC6.1;

Single-region S3 Access Points have their own Public Access Block settings, independent of the parent bucket's PAB. A bucket can be hardened with PAB fully enforcing while one of its Access Points has PAB disabled — the Access Point endpoint remains a public path to the same underlying data. Prowler and ScoutSuite treat Access Points as a distinct resource with their own exposure surface precisely because of this overlay semantics.

Remediation: Enable all four PAB flags on the Access Point via aws s3control put-access-point-public-access-block with BlockPublicAcls=true, IgnorePublicAcls=true, BlockPublicPolicy=true, RestrictPublicBuckets=true. If the Access Point is intentionally public (rare), document the exposure and add a Stave exemption.


CTL.S3.AP.PAB.BLOCKPUBLICACLS.001

S3 Access Point Block Public ACLs Flag Must Be Enabled

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(a)(1); soc2: CC6.1;

The BlockPublicAcls flag of a single-region S3 Access Point's Public Access Block configuration rejects any new ACL grant applied through the Access Point endpoint that would make the data publicly accessible. When this specific flag is false, new PUT-ACL calls routed via the Access Point that grant READ, WRITE, READ_ACP, or WRITE_ACP to http://acs.amazonaws.com/groups/global/AllUsers or .../AuthenticatedUsers succeed rather than being rejected at the Access Point boundary. The umbrella CTL.S3.AP.PAB.001 fires when any of the four AP PAB flags is off; this control narrows the finding to the specific flag so remediation is a one-command fix rather than requiring the operator to enumerate which of the four is missing. Prowler and ScoutSuite both report the four flags independently.

Remediation: Enable the BlockPublicAcls flag on the Access Point's Public Access Block configuration. From the CLI:

aws s3control put-access-point-public-access-block \
--account-id <account> \
--name <access-point-name> \
--public-access-block-configuration \
'BlockPublicAcls=true,IgnorePublicAcls=<current>,BlockPublicPolicy=<current>,RestrictPublicBuckets=<current>'

Preserve the other three flag values so enabling this one doesn't silently disable the others.


CTL.S3.AP.PAB.BLOCKPUBLICPOLICY.001

S3 Access Point Block Public Policy Flag Must Be Enabled

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(a)(1); soc2: CC6.1;

The BlockPublicPolicy flag of a single-region S3 Access Point's Public Access Block configuration rejects any new Access Point policy that grants access to a public principal (Principal: "*", Principal: {"AWS": "*"}, or an Allow block with no Principal) without a narrowing Condition. When this specific flag is false, new policies with wildcard principals and no scoping Condition succeed at PUT time rather than being rejected at the Access Point boundary. The umbrella CTL.S3.AP.PAB.001 fires when any of the four AP PAB flags is off; this control narrows the finding to the specific flag so remediation is a one-command fix rather than requiring the operator to enumerate which of the four is missing. Prowler and ScoutSuite both report the four flags independently.

Remediation: Enable the BlockPublicPolicy flag on the Access Point's Public Access Block configuration. From the CLI:

aws s3control put-access-point-public-access-block \
--account-id <account> \
--name <access-point-name> \
--public-access-block-configuration \
'BlockPublicAcls=<current>,IgnorePublicAcls=<current>,BlockPublicPolicy=true,RestrictPublicBuckets=<current>'

Preserve the other three flag values so enabling this one doesn't silently disable the others.


CTL.S3.AP.PAB.IGNOREPUBLICACLS.001

S3 Access Point Ignore Public ACLs Flag Must Be Enabled

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(a)(1); soc2: CC6.1;

The IgnorePublicAcls flag of a single-region S3 Access Point's Public Access Block configuration causes any existing public ACL grants on objects exposed through the Access Point to be ignored at evaluation time. When this specific flag is false, existing ACLs that grant READ, WRITE, READ_ACP, or WRITE_ACP to AllUsers or AuthenticatedUsers remain effective when requests arrive through the Access Point endpoint, preserving public reachability regardless of bucket-level hardening. The umbrella CTL.S3.AP.PAB.001 fires when any of the four AP PAB flags is off; this control narrows the finding to the specific flag so remediation is a one-command fix rather than requiring the operator to enumerate which of the four is missing. Prowler and ScoutSuite both report the four flags independently.

Remediation: Enable the IgnorePublicAcls flag on the Access Point's Public Access Block configuration. From the CLI:

aws s3control put-access-point-public-access-block \
--account-id <account> \
--name <access-point-name> \
--public-access-block-configuration \
'BlockPublicAcls=<current>,IgnorePublicAcls=true,BlockPublicPolicy=<current>,RestrictPublicBuckets=<current>'

Preserve the other three flag values so enabling this one doesn't silently disable the others.


CTL.S3.AP.PAB.RESTRICTPUBLICBUCKETS.001

S3 Access Point Restrict Public Buckets Flag Must Be Enabled

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(a)(1); soc2: CC6.1;

The RestrictPublicBuckets flag of a single-region S3 Access Point's Public Access Block configuration suppresses any existing Access Point policy statement that grants public access; at evaluation time only AWS-service principals (via aws:PrincipalService) and principals in the same AWS account remain able to reach the Access Point. When this specific flag is false, previously-authored public Access Point policies remain effective and the endpoint stays publicly reachable regardless of bucket-level hardening. The umbrella CTL.S3.AP.PAB.001 fires when any of the four AP PAB flags is off; this control narrows the finding to the specific flag so remediation is a one-command fix rather than requiring the operator to enumerate which of the four is missing. Prowler and ScoutSuite both report the four flags independently.

Remediation: Enable the RestrictPublicBuckets flag on the Access Point's Public Access Block configuration. From the CLI:

aws s3control put-access-point-public-access-block \
--account-id <account> \
--name <access-point-name> \
--public-access-block-configuration \
'BlockPublicAcls=<current>,IgnorePublicAcls=<current>,BlockPublicPolicy=<current>,RestrictPublicBuckets=true'

Preserve the other three flag values so enabling this one doesn't silently disable the others.


CTL.S3.AP.POLICY.001

S3 Access Point Policy Must Not Be Public

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure

Single-region S3 Access Points carry their own resource policy that is evaluated independently of the parent bucket policy. A public Access Point policy creates a public access path to the bucket even when the bucket's own policy is scoped correctly. This mirrors the MRAP case (CTL.S3.MRAP.POLICY.001) and is the single-region analogue.

Remediation: Remove the public grant from the Access Point policy, or add a scoping Condition that binds the caller to a fixed VPC, IP range, organization, or ARN. If public reach is intentional, enforce it through a narrower mechanism such as CloudFront with Origin Access Control and keep the Access Point policy private.


CTL.S3.AUDIT.OBJECTLEVEL.001

CloudTrail Object-Level Logging Required

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: AU-2; hipaa: 164.312(b); nist_800_53_r5: AU-2; pci_dss_v4.0: 10.2.1.3; soc2: CC7.1;

CloudTrail S3 object-level data event logging must be enabled for PHI buckets. Server access logging captures bucket-level operations but not individual object access patterns. CloudTrail data events record GetObject, PutObject, and DeleteObject calls required for HIPAA audit controls.

Remediation: Configure a CloudTrail trail with a data event selector for AWS::S3::Object covering this bucket. Use aws cloudtrail put-event-selectors to add the selector.


CTL.S3.AUTH.READ.001

No Authenticated-Users Read Access

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure

S3 buckets must not grant read access to all authenticated AWS users. AuthenticatedUsers scope means any AWS account can read objects, which is nearly as dangerous as fully public access.

Remediation: Remove the ACL grant to AuthenticatedUsers. Replace with specific IAM principals or use bucket policy with explicit account IDs. Enable S3 Public Access Block with IgnorePublicAcls set to true.


CTL.S3.AUTH.WRITE.001

No Authenticated-Users Write Access

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure

S3 buckets must not grant write or delete access to all authenticated AWS users. AuthenticatedUsers scope means any AWS account holder worldwide can upload, overwrite, or delete objects — enabling data injection, ransomware, and supply chain poisoning.

Remediation: Remove the ACL grant or policy statement granting write access to AuthenticatedUsers. Replace with specific IAM principals or use bucket policy with explicit account IDs. Enable S3 Public Access Block with BlockPublicAcls and IgnorePublicAcls set to true.


CTL.S3.BREACH.DETECT.001

PHI Buckets Must Have Complete Detection Infrastructure

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: IR-4; hipaa: 164.400; nist_800_53_r5: IR-4; soc2: CC7.1;

S3 buckets tagged with data-classification=phi must have all four detection components active: server access logging, CloudTrail object-level logging, GuardDuty S3 protection, and AWS Config recording. Missing any one component creates a gap in breach detection and incident investigation capability. HIPAA §§164.400-414 requires the ability to detect and investigate unauthorized access to PHI.

Remediation: Ensure all four components are active for this bucket: 1. Server access logging (aws s3api put-bucket-logging) 2. CloudTrail object-level data events (aws cloudtrail put-event-selectors) 3. GuardDuty S3 protection (aws guardduty update-detector) 4. AWS Config recording (aws configservice put-configuration-recorder)


CTL.S3.BUCKET.TAKEOVER.001

Referenced S3 Buckets Must Exist And Be Owned

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure

Any externally referenced S3 bucket must exist and be owned. Dangling references (missing or unowned buckets) enable bucket takeover and attacker-controlled content delivery.

Remediation: Create the S3 bucket in your AWS account, or remove the DNS record, CDN origin, or application reference pointing to the unclaimed bucket.


CTL.S3.CDN.BYPASS.001

CloudFront-Fronted Bucket Must Not Allow Direct Public Access

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(a)(1); soc2: CC6.1;

A bucket that is both fronted by a CloudFront distribution and publicly readable on its direct S3 endpoint gives an attacker a full bypass of the CloudFront layer. CloudFront typically carries the defensive controls — WAF rules, geographic restrictions, request logging, signed URLs, TLS policy — while the raw S3 URL (bucket.s3.<region>.amazonaws.com) carries none of them. Anyone who learns the bucket name can fetch objects around every CloudFront-layer control.

Remediation: 1. Enable Block Public Access on the bucket and remove any Principal "*" grants from the bucket policy so the direct S3 endpoint is no longer publicly reachable. 2. Restrict the bucket policy to the specific CloudFront distribution via an Origin Access Control and a condition on aws:SourceArn = arn:aws:cloudfront::<account>:distribution/<id>. 3. Verify the CloudFront distribution still serves objects after the lockdown (OAC-signed requests survive; direct anonymous requests fail).


CTL.S3.CDN.EXPOSURE.001

Private Bucket Must Not Be Publicly Exposed Via CloudFront

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(a)(1); soc2: CC6.1;

A bucket with Block Public Access enabled can still serve objects publicly through CloudFront if the bucket policy grants access to the cloudfront.amazonaws.com service principal. This creates a false sense of security — the bucket appears private but objects are accessible via the CloudFront distribution URL.

Remediation: 1. Review whether public CDN access is intentional for this bucket. 2. If not intentional, remove the CloudFront distribution or restrict it with signed URLs/cookies. 3. If intentional, document this as an acknowledged exposure path and add a Stave exemption for this bucket.


CTL.S3.CDN.OAC.001

CloudFront Access Must Use OAC Not Legacy OAI

  • Severity: medium
  • Type: unsafe_state
  • Domain: governance

When S3 objects are served via CloudFront, Origin Access Control (OAC) should be used instead of the legacy Origin Access Identity (OAI). OAC supports SSE-KMS, SigV4, and all S3 features. OAI is a legacy mechanism that does not support KMS encryption and is being deprecated.

Remediation: 1. Create an Origin Access Control for the distribution. 2. Update the distribution origin to use OAC instead of OAI. 3. Update the bucket policy to grant cloudfront.amazonaws.com with a Condition restricting to the distribution ARN. 4. Remove the legacy OAI.


CTL.S3.CDN.TRANSPORT.001

CloudFront to S3 Origin Uses HTTP

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(e)(1); nist_800_53_r5: SC-8; pci_dss_v4.0: 4.2.1; soc2: CC6.1;

CloudFront distribution fetches objects from S3 via HTTP, not HTTPS. Traffic between CloudFront and S3 is unencrypted. The user sees HTTPS in the browser but the backend fetch is plaintext — the same false-HTTPS pattern as Cloudflare Flexible SSL mode.

Remediation: Configure the CloudFront distribution origin to use HTTPS-only or match-viewer with HTTPS. Set the S3 origin protocol policy to https-only in the CloudFront distribution configuration.


CTL.S3.CLASSIFY.COVERAGE.001

All S3 Buckets Must Have a Data Classification Tag

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws_v3.0: 2.1.3; fedramp_moderate: CM-8; gdpr: Art.30; hipaa: 164.308(a)(1)(ii)(A); nist_800_53_r5: CM-8; pci_dss_v4.0: 12.5.2; soc2: CC6.1;

Every S3 bucket must have a data-classification tag with a value from the recognized taxonomy (phi, pii, confidential, internal, public, non-sensitive). The data-classification tag is the gating condition for the majority of Stave's sensitive data controls — PHI encryption, Object Lock, Macie scanning, lifecycle retention, and access scoping are all conditional on this tag. A bucket without the tag silently passes all tag-conditional controls regardless of its actual contents. CIS 2.1.3 requires that all S3 data is discovered, classified, and secured. This control establishes the classification baseline — it does not verify what classification was applied, only that every bucket has been explicitly classified so downstream controls can evaluate it.

Remediation: Apply a data-classification tag to the bucket with a value from the recognized taxonomy: phi, pii, confidential, internal, public, or non-sensitive. Use AWS Tag Editor or the S3 PutBucketTagging API to apply tags. Establish a tagging policy requiring classification at bucket creation time. Use AWS Config rules or SCPs to enforce mandatory tagging.


CTL.S3.CLOUDTRAIL.PUBLIC.001

S3 Bucket Storing CloudTrail Logs Must Not Be Public

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws: 3.3; mitre_attack: T1562.008; nist_800_53_r5: AU-9;

The S3 bucket storing CloudTrail logs must not be publicly accessible. A public CloudTrail bucket exposes the complete API activity log and allows log tampering.

Remediation: aws s3api put-public-access-block --bucket --public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true, BlockPublicPolicy=true,RestrictPublicBuckets=true"


CTL.S3.CONTROLS.001

Public Access Block Must Be Enabled

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws_v1.4.0: 2.1.5; cis_aws_v3.0: 2.1.4; fedramp_moderate: AC-3; ffiec: ISH-4; gdpr: Art.32; iso_27001_2022: A.8.3; nist_800_53_r5: AC-3; nist_csf_2.0: PR.PS; pci_dss_v3.2.1: 1.3.6; pci_dss_v4.0: 2.2.1; soc2: CC6.1;

S3 buckets must have the public access block fully enabled. When disabled, the bucket has no safety net against accidental public exposure from policy or ACL changes. This detects the enabling condition for public access, not the exposure itself.

Remediation: Enable all four Public Access Block settings on the bucket: BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, RestrictPublicBuckets.


CTL.S3.CORS.001

S3 CORS Wildcard Origin Must Be Explicitly Intended

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: soc2: CC6.6;

S3 bucket CORS configurations that set AllowedOrigins to "*" expose the bucket's CORS-permitted methods to every web origin. For buckets serving genuinely public static assets this may be intentional; for buckets holding tenant data, authenticated user content, or signed URLs it widens the attack surface for cross-origin abuse. S3 does not expose an AllowCredentials field on CORS rules — browsers refuse the wildcard+credentials combination — so the unsafe state for S3 is wildcard origin on a bucket that is not tagged as intentionally public. The observation shape mirrors the raw "aws s3api get-bucket-cors" response.

Remediation: Replace "*" in AllowedOrigins with the specific origins that need cross-origin access. If the bucket is a public CDN or static asset origin where wildcard CORS is intentional, add the tag cors_wildcard_intended=true to declare intent. To remove CORS entirely, run "aws s3api delete-bucket-cors --bucket ".


CTL.S3.DANGLING.ORIGIN.001

CDN S3 Origins Must Not Be Dangling

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure

CloudFront distributions must not reference S3 origins that do not exist. A missing/unclaimed origin bucket enables takeover and CDN content poisoning.

Remediation: Create the S3 bucket in your AWS account to claim the name, or remove the dangling origin from the CloudFront distribution. Update the distribution to use an Origin Access Control (OAC).


CTL.S3.DELEGATION.ESCALATION.001

Vendor Can Make S3 Bucket Public

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: AC-6; hipaa: 164.312(e)(1); nist_800_53_r5: AC-6; owasp_nhi: NHI3, NHI5, NHI6; pci_dss_v4.0: 1.3.1; soc2: CC6.6;

A delegated vendor principal has permissions that could make this bucket publicly accessible — s3:PutBucketPolicy with the ability to add a public Principal, or s3:PutBucketAcl with the ability to grant public-read. The vendor is the weakest link in the access chain: if the vendor is compromised, the attacker can expose the bucket to the internet through the vendor's delegated permissions without ever touching the customer account. This is the supply-chain version of the public-bucket finding — the bucket is not public today, but one compromised vendor credential away from being public. This is a distinct check from S3 Block Public Access (the backstop): ESCALATION.001 asks whether the vendor CAN escalate; the BPA control asks whether the backstop is in place. Both are needed.

Remediation: Remove the vendor's ability to modify bucket accessibility: remove s3:PutBucketPolicy and s3:PutBucketAcl from the vendor's allowed actions. As defense-in-depth, enable S3 Block Public Access at the account level (separate backstop check) and add an SCP denying s3:PutBucketPolicy and s3:PutBucketAcl for the vendor's account. Re-run the collector to refresh delegation.vendor_can_make_public.


CTL.S3.DELEGATION.KNOWN.001

S3 Bucket Has Unregistered External Principal With Control Rights

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: AC-6; nist_800_53_r5: AC-6; owasp_nhi: NHI3; pci_dss_v4.0: 7.2.1; soc2: CC6.1;

This S3 bucket's policy grants control-level actions (PutObject, DeleteObject, PutBucketPolicy, PutBucketAcl, or equivalent) to an external AWS principal that is not registered in the vendor registry (controldata/taxonomy/vendor_registry.yaml). The access may be legitimate but undeclared — or it may be residual from a decommissioned integration. Unknown external principals with control rights are the supply-chain entry point: if the external account is compromised, the attacker inherits whatever permissions the bucket policy grants, and the security team does not know the principal exists.

Remediation: Identify the unknown principal by reading the bucket policy. Determine whether the access is still needed. If needed, add the vendor to controldata/taxonomy/vendor_registry.yaml with a declared purpose, scope, and review date. If not needed, remove the principal from the bucket policy. Re-run the collector to refresh delegation.has_unknown_external_principal.


CTL.S3.DELEGATION.LIFECYCLE.001

Vendor Access Review Date Has Passed

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: AC-2; nist_800_53_r5: AC-2; owasp_nhi: NHI1, NHI3; pci_dss_v4.0: 7.2.4; soc2: CC6.3;

This bucket has delegated access to a vendor whose review_date in the vendor registry (controldata/taxonomy/vendor_registry.yaml) has passed. The access has not been formally reviewed since the scheduled date. Vendor access without periodic review becomes abandoned access: the vendor's needs may have changed, their security posture may have degraded, the team that managed the integration may have left, or the integration may no longer be needed. Time degrades assurance, and a passed review date means the operator's re-verification commitment was not kept.

Remediation: Conduct the overdue vendor access review. Confirm the vendor relationship is still active, verify the vendor's current security posture, confirm the declared scope still matches operational need, and update review_date in controldata/taxonomy/vendor_registry.yaml. If the relationship has ended, remove the vendor entry from the registry and remove the corresponding principal from the bucket policy.


CTL.S3.DELEGATION.REVOCABLE.001

Customer Cannot Revoke Vendor's S3 Access

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: AC-6; nist_800_53_r5: AC-6; owasp_nhi: NHI3, NHI6; pci_dss_v4.0: 7.2.5; soc2: CC6.1;

The customer account does not retain the ability to revoke this vendor's access to the bucket. This can occur when the vendor owns the bucket in the customer's namespace (cross-account bucket ownership), when the vendor's IAM role has s3:PutBucketPolicy on this bucket (the vendor can restore their own access after revocation), or when the bucket policy uses a condition the customer cannot modify. Irrevocable vendor access means the customer cannot contain a vendor-side breach: if the vendor is compromised, the customer must wait for the vendor to remediate, or delete the bucket entirely.

Remediation: Verify bucket ownership is with the customer account (not the vendor's account). Remove s3:PutBucketPolicy and s3:PutBucketAcl from the vendor's allowed actions. Ensure the customer's IAM retains s3:PutBucketPolicy on this bucket so customer-side changes can override vendor changes. Re-run the collector to refresh delegation.customer_can_revoke.


CTL.S3.DELEGATION.SCOPE.001

Vendor's Actual S3 Permissions Exceed Declared Scope

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: AC-6; nist_800_53_r5: AC-6; owasp_nhi: NHI3, NHI5; pci_dss_v4.0: 7.2.1; soc2: CC6.1;

A registered vendor's actual permissions on this bucket exceed the scope declared for them in the vendor registry. The vendor was approved for specific actions (e.g., PutObject to raw-data/*) but the bucket policy grants additional actions (e.g., DeleteObject, PutBucketPolicy) beyond the declared purpose. Scope creep in vendor permissions is the supply-chain equivalent of IAM privilege creep: the vendor's declared purpose says "ingest data"; the actual permissions say "ingest data, delete data, and rewrite the bucket policy." The gap is the attack surface.

Remediation: Compare the vendor's actual_actions against allowed_actions in vendor_registry.yaml. For each exceeded action: if it is still needed, update the vendor registry to reflect the new scope and obtain approval; if not, remove the action from the bucket policy. The exceeded actions are listed in the finding evidence under delegation.external_principals[].exceeded_actions.


CTL.S3.DETECT.MACIE.001

Sensitive Data Buckets Must Have Macie Enabled

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: RA-5; gdpr: Art.30; hipaa: 164.312(b); iso_27001_2022: A.8.12; nist_800_53_r5: RA-5; pci_dss_v4.0: 11.5.1; soc2: CC7.2;

S3 buckets tagged with a non-public data classification (phi, pii, confidential, internal) must be monitored by Amazon Macie. Macie uses machine learning and pattern matching to discover and classify sensitive data, detecting PII, PHI, and credentials that may have been stored without proper controls. Without Macie, sensitive data can accumulate undetected in buckets that were not originally intended for it.

Remediation: Enable Amazon Macie in the account and region, then add this bucket to a Macie classification job. Use aws macie2 create-classification-job to configure automated scanning. For organization-wide coverage, enable Macie via AWS Organizations delegated administrator.


CTL.S3.DETECT.MACIE.002

Macie Automated Sensitive Data Discovery Must Be Active

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: SI-4; hipaa: 164.308(a)(1)(ii)(D); nist_800_53_r5: SI-4; soc2: CC7.2;

Buckets monitored by Macie must have automated sensitive data discovery actively running, not just enabled. A Macie classification job can exist but be paused, cancelled, or have never completed a scan. Without active discovery, new sensitive data uploaded after the last scan goes undetected. Automated discovery continuously samples bucket contents to find sensitive data as it arrives.

Remediation: Verify the Macie classification job for this bucket is in RUNNING status. If paused, resume it with aws macie2 update-classification-job. Enable automated sensitive data discovery at the account level with aws macie2 update-automated-discovery-configuration to ensure continuous sampling of all monitored buckets.


CTL.S3.ENCRYPT.001

Encryption at Rest Required

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws_v1.4.0: 2.1.1; fedramp_moderate: SC-28; ffiec: ISH-4; gdpr: Art.32; hipaa: 164.312(a)(2)(iv); iso_27001_2022: A.8.24; nist_800_53_r5: SC-28; nist_csf_2.0: PR.DS; pci_dss_v3.2.1: 3.4; pci_dss_v4.0: 3.4.1; soc2: CC6.1;

S3 buckets must have server-side encryption enabled. Unencrypted storage is the top audit finding in regulated industries.

Remediation: Enable default bucket encryption using SSE-S3 (AES256) or SSE-KMS. Use aws s3api put-bucket-encryption to set the default encryption configuration. For sensitive data, use SSE-KMS with a customer-managed key.


CTL.S3.ENCRYPT.002

Transport Encryption Required

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws_v1.4.0: 2.1.2; cis_aws_v3.0: 2.1.1; fedramp_moderate: SC-8; ffiec: ISH-4; gdpr: Art.32; hipaa: 164.312(e)(2)(ii); iso_27001_2022: A.8.24; nist_800_53_r5: SC-8; nist_csf_2.0: PR.DS; pci_dss_v3.2.1: 4.1; pci_dss_v4.0: 4.2.1; soc2: CC6.1;

S3 buckets must enforce HTTPS via a deny policy on aws:SecureTransport=false. Without this, data transfers occur in plaintext.

Remediation: Add a bucket policy statement that denies all actions when aws:SecureTransport is false. This forces all API calls to use HTTPS.


CTL.S3.ENCRYPT.003

PHI Buckets Must Use SSE-KMS with Customer-Managed Key

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: SC-28; ffiec: ISH-4; gdpr: Art.32; iso_27001_2022: A.8.24; nist_800_53_r5: SC-28; nist_csf_2.0: PR.DS; pci_dss_v4.0: 3.5.1; soc2: CC6.7;

S3 buckets tagged with data-classification=phi must use SSE-KMS encryption with a customer-managed key (CMK), not the default AWS-managed key or SSE-S3. This ensures the organization controls key rotation, access policies, and audit logging for PHI data at rest.

Remediation: Change the bucket default encryption to SSE-KMS and specify a customer-managed KMS key ARN. Ensure the KMS key policy grants access only to authorized principals. Enable KMS key rotation.


CTL.S3.ENCRYPT.004

Sensitive Data Requires KMS Encryption

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure

S3 buckets with any non-public data classification must use SSE-KMS encryption with a customer-managed key, not SSE-S3 (AES256). AES256 uses AWS-managed keys with no customer control over key rotation, access policies, or audit trails. This fires on all classified data except explicitly public or non-sensitive buckets.

Remediation: Change the bucket default encryption to SSE-KMS with a customer-managed key. Re-encrypt existing objects by copying them in place with the new encryption settings.


CTL.S3.ENCRYPT.KMS.OWNERSHIP.001

S3 Bucket Encrypted with External Account KMS Key

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: nist_800_53_r5: SC-12; pci_dss_v4.0: 3.5.1; soc2: CC6.1;

S3 bucket uses a KMS key owned by a different AWS account. The external account controls the key — they can revoke access, disable the key, or modify the key policy at any time. Data encrypted with an externally-owned key is accessible only as long as the external account permits. This is a supply chain risk for encryption.

Remediation: Use a KMS key owned by the same account as the bucket. If cross-account key usage is required, ensure the key policy grants only the minimum necessary permissions and the key owner is within the organization.


CTL.S3.ENCRYPT.KMS.POLICY.001

KMS Key Policy for S3 Encryption Is Overly Broad

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: nist_800_53_r5: SC-13; pci_dss_v4.0: 3.6.1; soc2: CC6.1;

The KMS key used for S3 encryption has an overly broad key policy — kms:* to root, or kms:Decrypt to principals beyond the intended S3 service. An overly broad KMS key policy undermines encryption. If the key policy grants kms:Decrypt to many principals, the encryption is nominal — anyone with decrypt access can read the data regardless of S3 bucket policies.

Remediation: Scope the KMS key policy to the specific principals and services that need access. Use kms:ViaService condition to restrict usage to s3.amazonaws.com. Remove kms:* grants and limit kms:Decrypt to authorized principals.


CTL.S3.GLACIER.RESTORE.POLICY.001

Glacier Restore Creates Temporarily Accessible Copy

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(a)(1); nist_800_53_r5: AC-3; soc2: A1.2;

Glacier-archived objects that are restored inherit the bucket's access policy without additional restrictions. If the bucket policy allows broad access, the restored copy is accessible to anyone the bucket policy permits during the restore window. Glacier archival is often treated as a security boundary but restored objects become standard S3 objects for the duration of the restore window.

Remediation: Add a bucket policy condition restricting access to restored objects. Use s3:ExistingObjectTag or scoped IAM policies to limit who can read restored Glacier objects during the restore window.


CTL.S3.GLACIER.RESTORE.WINDOW.001

Glacier Restore Window Exceeds Acceptable Duration

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: nist_800_53_r5: AC-3; soc2: A1.2;

Glacier restore window exceeds 7 days. Extended restore windows leave formerly-archived data in a standard-accessible state longer than necessary. The restore window should be the minimum duration required for the restore purpose.

Remediation: Reduce the Glacier restore window to the minimum duration required. Use 1-3 day restore windows for operational restores. Extended restore windows leave archived data in a standard-accessible state unnecessarily.


CTL.S3.GOVERNANCE.001

Data Classification Tag Required

  • Severity: low
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: nist_800_53_r5: CM-8;

S3 buckets must have a data-classification tag. Without this tag, tag-conditional controls for PHI, PII, confidential data, backup integrity, and compliance retention cannot evaluate — the bucket silently passes all sensitivity-gated checks regardless of actual content.

Remediation: Add a data-classification tag to the bucket with an appropriate value (e.g., phi, pii, confidential, internal, public, non-sensitive). Update your tagging policy to require this tag on all S3 buckets.


CTL.S3.INCOMPLETE.001

Complete Data Required for Safety Assessment

  • Severity: low
  • Type: unsafe_duration
  • Domain: storage
  • Compliance: nist_800_53_r5: SI-12;

S3 bucket safety cannot be proven when policy or ACL data is missing from the snapshot.

Remediation: Re-run the observation collector with full permissions to read bucket policies and ACLs. Ensure the collector IAM role has s3:GetBucketPolicy, s3:GetBucketAcl, and s3:GetBucketPolicyStatus permissions.


CTL.S3.INTELLIGENT.TIERING.EXPOSURE.001

S3 Batch Operations Must Not Copy Objects to External Accounts

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: mitre_attack: T1074; nist_800_53_r5: AC-3;

S3 Batch Operations can copy millions of objects across buckets in a single job. An attacker with s3:CreateJob permission can initiate a batch copy of all objects in a bucket to an external account — staging data for exfiltration without triggering per-object API calls. Batch operations generate a single CloudTrail event rather than millions of GetObject events — making large-scale data collection harder to detect via API call volume monitoring.

Remediation: Restrict s3:CreateJob to approved IAM roles. Add bucket policy conditions preventing cross-account batch operations. Deny s3:CreateJob when the destination account does not match the source account.


CTL.S3.INVENTORY.001

S3 Inventory Must Be Enabled for Visibility

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: CM-8; hipaa: 164.312(b); nist_800_53_r5: CM-8; soc2: CC7.2;

S3 buckets must have S3 Inventory configured to provide a complete manifest of all objects, their storage classes, encryption status, and optionally their ACL grants. Without Inventory, organizations have no baseline visibility into what data exists in a bucket, making it impossible to detect misplaced sensitive files, verify encryption coverage, or audit object-level access. S3 Inventory is essential when Amazon Macie is not deployed, as it provides the only mechanism for systematic bucket content auditing at scale.

Remediation: Configure S3 Inventory on the bucket using aws s3api put-bucket-inventory-configuration. Include optional fields for encryption status and ACL grants. Set the inventory to report daily or weekly to a secured destination bucket. Use the inventory reports to audit for misplaced sensitive data, unencrypted objects, and objects with public ACL grants.


CTL.S3.LIFECYCLE.001

Retention-Tagged Buckets Must Have Lifecycle Rules

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: nist_800_53_r5: SI-12; soc2: C1.2;

S3 buckets tagged with data-retention must have at least one enabled lifecycle rule configured. HIPAA requires defined data retention policies for protected health information (PHI), audit logs, and billing records. Without lifecycle rules, data persists indefinitely, increasing exposure surface and violating retention policy requirements.

Remediation: Add S3 lifecycle rules to manage object expiration and transitions. Configure rules matching the retention period specified in the data-retention tag. Use lifecycle transitions to move data to cheaper storage classes before expiration.


CTL.S3.LIFECYCLE.002

PHI Buckets Must Not Expire Data Before Minimum Retention

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: nist_800_53_r5: SI-12;

S3 buckets tagged with data-classification=phi must not have lifecycle expiration rules that delete data before the minimum HIPAA retention period. HIPAA requires medical records to be retained for a minimum of 6 years (2190 days). This control detects PHI buckets with expiration rules set below this threshold, which could result in premature deletion of protected health information.

Remediation: Increase the lifecycle expiration period to at least the configured min_retention_days value. If the current rule is for storage class transition, ensure the expiration rule is separate and meets the minimum retention period.


CTL.S3.LIST.RESTRICT.001

S3 Buckets Must Not Allow Unauthenticated Bucket Listing

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: mitre_attack: T1619; nist_800_53_r5: AC-3;

S3 buckets must not allow anonymous s3:ListBucket access. Anonymous bucket listing exposes all object keys to unauthenticated users, enabling reconnaissance of stored data. Attackers use object key enumeration to identify sensitive files, backup archives, and configuration artifacts before attempting data exfiltration.

Remediation: Enable S3 Public Access Block with BlockPublicPolicy and RestrictPublicBuckets set to true. Remove any bucket policy statements granting s3:ListBucket to Principal "*". Remove any ACL grants to the AllUsers group.


CTL.S3.LOCK.001

Compliance-Tagged Buckets Must Have Object Lock Enabled

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.316(b)(2); nist_800_53_r5: AU-9; soc2: CC6.1;

S3 buckets tagged with any compliance framework (soc2, gdpr, hipaa, pci-dss, etc.) must have S3 Object Lock enabled. Object Lock provides WORM (Write Once Read Many) protection, preventing objects from being deleted or overwritten for a specified retention period. Regulatory frameworks require immutable storage for audit logs, compliance records, and protected data.

Remediation: Enable S3 Object Lock on the bucket. Note: Object Lock can only be enabled at bucket creation. If the bucket already exists, create a new bucket with Object Lock enabled and migrate objects. Set a default retention period appropriate for your compliance framework.


CTL.S3.LOCK.002

PHI Buckets Must Use COMPLIANCE Mode Object Lock

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure

S3 buckets tagged with data-classification=phi that have Object Lock enabled must use COMPLIANCE mode, not GOVERNANCE mode. COMPLIANCE mode prevents ANY user, including the root account, from deleting or overwriting protected objects during the retention period. GOVERNANCE mode allows users with special permissions to override retention, which is insufficient for HIPAA-regulated PHI data where tamper-proof storage is required.

Remediation: Change the Object Lock default retention mode from GOVERNANCE to COMPLIANCE. In COMPLIANCE mode, no user (including root) can delete or modify protected objects during the retention period.


CTL.S3.LOCK.003

PHI Object Lock Retention Must Meet Minimum Period

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure

S3 buckets tagged with data-classification=phi that have Object Lock enabled must have a default retention period of at least 2190 days (6 years) to meet HIPAA minimum retention requirements. Shorter retention periods risk premature expiration of WORM protection, allowing deletion or modification of PHI data before the regulatory retention period has elapsed.

Remediation: Increase the Object Lock default retention period to at least 2190 days. Use aws s3api put-object-lock-configuration to update the default retention settings.


CTL.S3.LOCK.LEGALHOLD.001

S3 Object Lock Without Legal Hold Capability

  • Severity: medium
  • Type: unsafe_state
  • Domain: governance
  • Compliance: hipaa: 164.530(j); soc2: CC7.4;

S3 Object Lock is enabled but no objects have legal hold applied. For organizations subject to litigation hold requirements, legal hold prevents object deletion or modification regardless of retention period. Without legal hold, objects can be deleted after the retention period expires — even if a litigation hold requires indefinite preservation.

Remediation: Apply legal hold to objects that must be preserved for litigation, regulatory investigation, or eDiscovery obligations. Use s3:PutObjectLegalHold to apply legal hold to specific objects or object prefixes.


CTL.S3.LOG.001

Access Logging Required

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws_v1.4.0: 2.1.3; fedramp_moderate: AU-2; ffiec: ISH-4; gdpr: Art.30; hipaa: 164.312(b); iso_27001_2022: A.8.15; nist_800_53_r5: AU-2; pci_dss_v3.2.1: 10.2.1; pci_dss_v4.0: 10.2.1.3; soc2: CC7.2;

S3 buckets must have server access logging enabled for audit trail and visibility into data access patterns.

Remediation: Enable S3 server access logging and specify a target bucket for log delivery. Ensure the target bucket has appropriate access controls and is in the same region.


CTL.S3.LOG.BUCKET.LIFECYCLE.001

Log Destination Bucket Must Have Lifecycle Policy

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(b); nist_800_53_r5: AU-11;

When server access logging is enabled, the log destination bucket must have a lifecycle policy configured. Without lifecycle management, log storage grows unbounded, increasing costs and making audit analysis impractical.

Remediation: Configure a lifecycle policy on the log destination bucket to manage log retention. Transition older logs to cheaper storage classes and expire logs after the required retention period.


CTL.S3.LOG.BUCKET.LOCK.001

Log Destination Bucket Must Have Object Lock Enabled

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: AU-9; hipaa: 164.312(b); nist_800_53_r5: AU-9;

When server access logging is enabled, the log destination bucket must have Object Lock enabled. Without Object Lock, log files can be deleted by any principal with s3:DeleteObject permission, even with versioning enabled.

Remediation: Enable Object Lock on the log destination bucket with a retention policy. Object Lock prevents log file deletion for the retention period, ensuring audit trail immutability. Note that Object Lock must be enabled at bucket creation time.


CTL.S3.LOG.BUCKET.PUBLIC.001

Log Destination Bucket Must Not Be Publicly Accessible

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: AU-9; hipaa: 164.312(b); nist_800_53_r5: AU-9;

When server access logging is enabled, the log destination bucket must not be publicly accessible. Public log buckets expose audit trail contents to external actors, enabling reconnaissance and detection evasion.

Remediation: Block all public access on the log destination bucket using the S3 Block Public Access settings. Enable all four block public access options to prevent any public access configuration.


CTL.S3.LOG.BUCKET.VERSIONING.001

Log Destination Bucket Must Have Versioning Enabled

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(b); nist_800_53_r5: AU-9;

When server access logging is enabled, the log destination bucket must have versioning enabled. Without versioning, deleted or overwritten log files cannot be recovered, allowing attackers to destroy audit evidence.

Remediation: Enable versioning on the log destination bucket to preserve all versions of log objects. This ensures that even if log files are deleted or overwritten, previous versions remain recoverable.


CTL.S3.LOG.PREFIX.001

Log Prefix Required When Logging Enabled

  • Severity: low
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(b); nist_800_53_r5: AU-3;

When server access logging is enabled, a target prefix must be configured to organize log files and enable efficient search and analysis of audit records.

Remediation: Set a target prefix for S3 server access logging to organize log files by source bucket. Use a prefix that identifies the source bucket, such as the bucket name followed by a slash.


CTL.S3.LOG.RETENTION.001

Log Retention Must Be At Least 90 Days

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: AU-11; hipaa: 164.530(j); nist_800_53_r5: AU-11;

When server access logging is enabled, the log destination bucket must retain logs for at least 90 days. Short retention periods allow attackers to wait out the retention window, after which evidence of unauthorized access is automatically destroyed.

Remediation: Set the lifecycle expiration on the log destination bucket to at least 90 days. Many compliance frameworks require longer retention; consult your compliance team for the appropriate retention period.


CTL.S3.MALWARE.001

PHI Buckets Must Have Malware Scanning Enabled

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: SI-3; hipaa: 164.308(a)(5)(ii)(B); nist_800_53_r5: SI-3; soc2: CC6.8;

S3 buckets tagged with data-classification=phi must have malware scanning enabled via GuardDuty S3 Malware Protection or an equivalent scanning pipeline. Without scanning, uploaded files containing malware can persist in PHI storage indefinitely, creating both a security risk (malware distribution) and a compliance violation (HIPAA §164.308(a)(5)(ii)(B) requires protection against malicious software).

Remediation: Enable GuardDuty S3 Malware Protection for the bucket. Navigate to GuardDuty > S3 Protection > Enable. Alternatively, deploy a Lambda-based AV scanning pipeline triggered by S3 PutObject events.


CTL.S3.MARKER.PHI.001

S3 Bucket Tagged data-classification=phi (Marker)

  • Severity: low
  • Type: marker
  • Domain: governance
  • Compliance: hipaa: 164.312(c)(1);

Fact-recording marker for S3 buckets tagged data-classification=phi. Emits an informational finding (NOT a violation) on every PHI-tagged bucket so cross-resource chains can compose it with violation findings on related identities or resources. Examples: a Cognito unauth role with s3:GetObject targeting a PHI bucket; an IAM role outside the covered entity that can AssumeRole to a principal that reaches a PHI bucket. PHI tagging on its own is the desired state — never a finding to triage. The marker exists so the chain engine can join "violation on the access side" with "data classification on the storage side" without forcing the collector to denormalise every join shape into a per-asset boolean.

Remediation: None. This marker exists as a chain-detection ingredient. To suppress noise on dashboards, filter findings by control_id when rendering Findings — markers should appear in a separate "facts" panel rather than the violation list.


CTL.S3.MFADELETE.001

MFA Delete Must Be Enabled on S3 Buckets

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws_v3.0: 2.1.2; nist_800_53_r5: CP-9; soc2: CC6.1;

S3 buckets should have MFA Delete enabled on versioned buckets. MFA Delete requires a second factor to permanently delete object versions, preventing unauthorized or accidental data destruction.

Remediation: Enable MFA Delete (requires root credentials): aws s3api put-bucket-versioning --bucket --versioning-configuration Status=Enabled,MFADelete=Enabled --mfa "arn:aws:iam:::mfa/root-account-mfa-device "



CTL.S3.MRAP.PAB.001

Multi-Region Access Point Must Have Block Public Access Enabled

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(a)(1); soc2: CC6.1;

MRAPs have their own PAB settings independent of bucket PAB. A bucket can have PAB enabled while the MRAP has PAB disabled.

Remediation: Enable all four PAB flags on the MRAP.


CTL.S3.MRAP.POLICY.001

Multi-Region Access Point Policy Must Not Be Public

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure

MRAPs can have their own resource policy evaluated independently of the bucket policy. A public MRAP policy creates a public access path.

Remediation: Remove public access from the MRAP policy.


CTL.S3.NETWORK.001

Public-Principal Policies Must Have Network Conditions

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: nist_800_53_r5: AC-3;

S3 bucket policies that grant access to Principal * (any AWS principal) must include network-scoping conditions such as aws:SourceIp, aws:sourceVpce, aws:SourceVpc, or aws:PrincipalOrgID. Without these conditions, the bucket is accessible to anyone on the internet. This control detects policies where wildcard principals are used without network restrictions.

Remediation: Add network-scoping conditions to the bucket policy: aws:SourceIp for IP range restrictions, aws:SourceVpce for VPC endpoint restrictions, aws:SourceVpc for VPC restrictions, or aws:PrincipalOrgID for organization-only access.


CTL.S3.NETWORK.POLICY.001

VPC Endpoint Policy Must Restrict Access

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(e)(1);

VPC endpoint policy must be attached and must not be the default full-access policy (Allow * on *). The default policy allows any principal on the VPC to reach any S3 bucket in any account via the endpoint, bypassing firewall controls. A restrictive endpoint policy limits which bucket ARNs and actions are reachable.

Remediation: Replace the default endpoint policy with one that restricts Resource to specific bucket ARNs and Action to required S3 operations only.


CTL.S3.NETWORK.VPC.001

VPC Endpoint or IP Condition Required

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(e)(1); nist_800_53_r5: SC-7;

S3 bucket access must be restricted by a VPC endpoint condition (aws:SourceVpce) or an IP address condition (aws:SourceIp) in the bucket policy. Without network-level restrictions, the bucket is reachable from any network path. This control enforces transmission security for PHI workloads.

Remediation: Add a VPC gateway endpoint for S3 and route bucket traffic through it, or add an IP condition (aws:SourceIp) to the bucket policy to restrict access to known CIDR ranges.


CTL.S3.NOTIFICATION.GHOST.001

S3 Event Notifications Must Not Target Deleted Resources

  • Severity: high
  • Type: unsafe_state
  • Domain: detection
  • Compliance: nist_800_53_r5: SI-4; soc2: CC7.1;

S3 bucket event notification configurations must not target deleted SNS topics, SQS queues, or Lambda functions. Object events (PutObject, DeleteObject) go undelivered when the target is absent. If notifications feed security monitoring, the monitoring stops.

Remediation: Update the notification configuration to reference existing resources.


CTL.S3.OBJECT.LAMBDA.GHOST.001

S3 Object Lambda Access Point References Missing Lambda

  • Severity: high
  • Type: unsafe_state
  • Domain: governance
  • Compliance: nist_800_53_r5: CM-8; soc2: CC6.1;

S3 Object Lambda Access Point references a Lambda function that no longer exists. GetObject calls against the access point fail silently or fall back to insecure paths depending on the caller's retry logic. The access point exists in the inventory, is reachable, and produces non-deterministic behavior on access.

Remediation: Either delete the orphaned access point or repoint it at an existing Lambda function. Validate the function ARN against the live IAM inventory before re-attaching.


CTL.S3.OBJECT.LAMBDA.POLICY.BYPASS.001

S3 Object Lambda Access Point Exposes More Than The Underlying Bucket

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: AC-3; nist_800_53_r5: AC-3; pci_dss_v4.0: 7.2.1; soc2: CC6.1;

S3 Object Lambda Access Point grants external invocation while the underlying bucket's policy explicitly does not permit external access. The access point is a privilege-escalation bypass of the bucket policy: external callers who would be denied at the bucket layer reach the bucket's data indirectly by invoking the transformation function whose Lambda role does have bucket access.

Remediation: Either tighten the access point policy to match the bucket policy (deny the same external accounts), or relax the bucket policy to match the access point's intentional exposure (rarely the right answer for sensitive data). The two policies should never disagree in the direction of "access point looser than bucket." If the access point is intentionally cross- account, document the bucket-policy carve-out explicitly so auditors don't read the bucket policy as the source of truth.


CTL.S3.OBJECT.LAMBDA.POLICY.EXTERNAL.001

S3 Object Lambda Access Point Allows External Account Invocation

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: AC-3; nist_800_53_r5: AC-3; pci_dss_v4.0: 7.2.1; soc2: CC6.1;

S3 Object Lambda Access Point's resource policy grants invocation to principals outside the access point's own AWS account. External callers reach the transformation function — which in turn reads the underlying bucket data on their behalf — bypassing any account-boundary controls the bucket policy itself enforces.

Remediation: Restrict the access point resource policy's Principal to accounts inside the organization. If cross-account access is intentional, pair it with explicit Condition keys naming the expected aws:SourceAccount and aws:SourceArn values and verify the underlying bucket policy also permits the same accounts directly — never let the access point be the looser layer.


CTL.S3.OBJECT.LAMBDA.ROLE.OVERREACH.001

S3 Object Lambda Function Role Reaches Buckets Beyond The Access Point

  • Severity: high
  • Type: unsafe_state
  • Domain: identity
  • Compliance: nist_800_53_r5: AC-6; pci_dss_v4.0: 7.2.1; soc2: CC6.1;

The Lambda function backing an S3 Object Lambda Access Point has an execution role with S3 read permission on more buckets than the access point's underlying bucket. An attacker who can invoke the access point gets the Lambda's full reach, not the access point's narrower promise — the transformation function reads buckets the access-point policy never exposes.

Remediation: Scope the Lambda's execution role to the single underlying bucket the access point exposes. Use a resource-bounded s3:GetObject policy with the access point's underlying bucket ARN as the only resource. Audit the role's other bucket references and remove any that aren't required for the transformation logic.


CTL.S3.OWNERSHIP.001

S3 Object Ownership Must Be Bucket Owner Enforced

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws_v3.0: 2.1.2; fedramp_moderate: AC-3; iso_27001_2022: A.8.3; nist_800_53_r5: AC-3; soc2: CC6.1;

S3 buckets must have Object Ownership set to BucketOwnerEnforced, which disables ACLs entirely. When ACLs are disabled, the bucket owner automatically owns every object regardless of who uploaded it, and access is controlled exclusively through IAM and bucket policies. This eliminates the entire class of ACL-based exposure: public grants, privilege escalation via WRITE_ACP, and object-level ACL overrides. Since April 2023 new buckets default to BucketOwnerEnforced, but buckets created before this date may still have ACLs enabled.

Remediation: Set Object Ownership to BucketOwnerEnforced using aws s3api put-bucket-ownership-controls. This disables all ACLs on the bucket. Before enabling, audit existing ACL grants and migrate any legitimate access to bucket policies or IAM policies. All existing ACL-based access will stop working once BucketOwnerEnforced is set.


CTL.S3.PAB.BLOCKPUBLICACLS.001

S3 Bucket Block Public ACLs Flag Must Be Enabled

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws_v1.4.0: 2.1.5; cis_aws_v3.0: 2.1.4; fedramp_moderate: AC-3; ffiec: ISH-4; gdpr: Art.32; iso_27001_2022: A.8.3; nist_800_53_r5: AC-3; nist_csf_2.0: PR.PS; pci_dss_v3.2.1: 1.3.6; pci_dss_v4.0: 2.2.1; soc2: CC6.1;

The BlockPublicAcls flag of the bucket's Public Access Block configuration rejects any new ACL grant that would make the bucket or its objects publicly accessible. When this specific flag is false, new PUT-ACL calls that grant READ, WRITE, READ_ACP, or WRITE_ACP to http://acs.amazonaws.com/groups/global/AllUsers or .../AuthenticatedUsers succeed rather than being rejected at the API boundary. The umbrella CTL.S3.CONTROLS.001 fires when any of the four PAB flags is off; this control narrows the finding to the specific flag so remediation is a one-command fix rather than requiring the operator to enumerate which of the four is missing. Prowler and ScoutSuite both report the four flags independently.

Remediation: Enable the BlockPublicAcls flag on the bucket's Public Access Block configuration. From the CLI:

aws s3api put-public-access-block \
--bucket <name> \
--public-access-block-configuration \
'BlockPublicAcls=true,IgnorePublicAcls=<current>,BlockPublicPolicy=<current>,RestrictPublicBuckets=<current>'

Preserve the other three flag values so enabling this one doesn't silently disable the others.


CTL.S3.PAB.BLOCKPUBLICPOLICY.001

S3 Bucket Block Public Policy Flag Must Be Enabled

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws_v1.4.0: 2.1.5; cis_aws_v3.0: 2.1.4; fedramp_moderate: AC-3; ffiec: ISH-4; gdpr: Art.32; iso_27001_2022: A.8.3; nist_800_53_r5: AC-3; nist_csf_2.0: PR.PS; pci_dss_v3.2.1: 1.3.6; pci_dss_v4.0: 2.2.1; soc2: CC6.1;

The BlockPublicPolicy flag of the bucket's Public Access Block configuration rejects any new bucket-policy update that would evaluate as public under AWS PolicyStatus.IsPublic. When this specific flag is false, a PutBucketPolicy call with Principal: "*" and no restricting Condition succeeds rather than being rejected at the API boundary. The umbrella CTL.S3.CONTROLS.001 fires when any of the four PAB flags is off; this control narrows the finding to the specific flag so remediation targets the policy-write path rather than the full PAB tuple. Prowler and ScoutSuite both report the four flags independently.

Remediation: Enable the BlockPublicPolicy flag on the bucket's Public Access Block configuration. Preserve the other three flag values so enabling this one doesn't silently disable the others.


CTL.S3.PAB.IGNOREPUBLICACLS.001

S3 Bucket Ignore Public ACLs Flag Must Be Enabled

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws_v1.4.0: 2.1.5; cis_aws_v3.0: 2.1.4; fedramp_moderate: AC-3; ffiec: ISH-4; gdpr: Art.32; iso_27001_2022: A.8.3; nist_800_53_r5: AC-3; nist_csf_2.0: PR.PS; pci_dss_v3.2.1: 1.3.6; pci_dss_v4.0: 2.2.1; soc2: CC6.1;

The IgnorePublicAcls flag of the bucket's Public Access Block configuration causes S3 to disregard any existing ACL grant that would make the bucket or its objects publicly accessible, even if the grant is already in place. When this specific flag is false, historical ACL grants to AllUsers or AuthenticatedUsers stay effective — enabling BlockPublicAcls alone does not neutralize grants that were made before the flag was enabled. The umbrella CTL.S3.CONTROLS.001 fires when any of the four PAB flags is off; this control narrows the finding to the specific flag so the operator knows that the fix is not "block new ACLs" but "ignore the ones already there." Prowler and ScoutSuite both report the four flags independently.

Remediation: Enable the IgnorePublicAcls flag on the bucket's Public Access Block configuration. Pair it with BlockPublicAcls for full ACL-path coverage — the pair prevents new public ACL grants and neutralizes existing ones.


CTL.S3.PAB.RESTRICTPUBLICBUCKETS.001

S3 Bucket Restrict Public Buckets Flag Must Be Enabled

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws_v1.4.0: 2.1.5; cis_aws_v3.0: 2.1.4; fedramp_moderate: AC-3; ffiec: ISH-4; gdpr: Art.32; iso_27001_2022: A.8.3; nist_800_53_r5: AC-3; nist_csf_2.0: PR.PS; pci_dss_v3.2.1: 1.3.6; pci_dss_v4.0: 2.2.1; soc2: CC6.1;

The RestrictPublicBuckets flag of the bucket's Public Access Block configuration restricts evaluation of any existing public bucket policy: when the flag is on, public grants in the bucket policy are limited to AWS service principals and authorized AWS services (e.g., CloudFront OAC). When this specific flag is false, historical Principal: "*" grants stay fully effective — enabling BlockPublicPolicy alone does not neutralize policies that were in place before the flag was turned on. The umbrella CTL.S3.CONTROLS.001 fires when any of the four PAB flags is off; this control narrows the finding to the specific flag so the operator knows the fix is not "block new public policies" but "restrict the ones already there." Prowler and ScoutSuite both report the four flags independently.

Remediation: Enable the RestrictPublicBuckets flag on the bucket's Public Access Block configuration. Pair it with BlockPublicPolicy for full policy-path coverage — the pair prevents new public policy writes and limits the effect of any historical public grants to service principals.


CTL.S3.POLICY.DENY.BYPASS.001

Bucket Policy Must Not Allow Access Despite IAM Deny

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: nist_800_53_r5: AC-3; soc2: CC6.1;

An IAM policy explicitly denies S3 actions for a principal, but the bucket policy allows the same actions for the same principal. In AWS policy evaluation for same-account access, an explicit Deny in any policy always wins — so this is NOT an active bypass. However, the configuration creates a maintenance hazard: if the IAM Deny is later removed (policy update, detach, or role change), the bucket policy Allow silently activates. The latent grant is invisible to IAM auditing because the Deny masks it. This control flags the conflicting policy pair so operators can remove the bucket policy Allow rather than relying on a Deny that may be transient.

Remediation: Remove the Allow statement from the bucket policy. Do not rely on the IAM Deny as the sole enforcement mechanism — Deny statements are often transient (attached to temporary policies, permission boundaries, or SCPs that change during reorganization).


CTL.S3.POLICY.DISCLOSURE.001

No Public Read of Bucket Policy

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure

S3 bucket policies must not grant s3:GetBucketPolicy to an anonymous or wildcard principal. A publicly readable bucket policy is a reconnaissance primitive: anyone can retrieve the policy, enumerate which specific object ARNs are granted public read, and use that map to target the exposed content. Reju Kole's disclosure (January 2026) exploited exactly this: the target bucket granted s3:GetBucketPolicy to Principal "*", the researcher read the policy to discover that backup.xlsx was publicly granted, downloaded the file, cracked its Office password offline, and used the recovered credentials for further compromise. This control is distinct from controls that fire on the exposed objects themselves (CTL.S3.PUBLIC.001, .004) — the policy-disclosure primitive is what makes object-scoped public grants discoverable, and closing it removes the reconnaissance step even when other public grants remain.

Remediation: Remove any policy statement granting s3:GetBucketPolicy to Principal "" or to AWS: "". If the policy must remain readable for tooling, restrict the grant with a Condition on aws:PrincipalOrgID, aws:SourceVpc, or a fixed aws:SourceIp CIDR. Enable S3 Public Access Block with BlockPublicPolicy set to true to reject future policies that grant public access to bucket metadata.


CTL.S3.POLICY.EXISTS.001

S3 Bucket Must Have an Explicit Bucket Policy Attached

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(a)(1); nist_800_53_r5: AC-3; soc2: CC6.1;

S3 buckets must have an explicit resource-based bucket policy. Without a policy, access controls rely entirely on IAM and ACLs — there is no resource-level enforcement of encryption requirements, VPC restrictions, or transport security.

Remediation: Attach a bucket policy that restricts access to known principals, VPC endpoints, or organization IDs (aws:PrincipalArn, aws:PrincipalOrgID, aws:SourceVpc, aws:SourceVpce). The scoped Allow is the primary control: it defines who can reach the bucket at all and is the line of defence that would still hold if every other guard were removed. Layered Deny statements (deny aws:SecureTransport=false to enforce TLS, deny PutObject without server-side encryption headers) are valuable secondary guards but do not replace a scoped Allow — they harden a bucket whose Allow is already narrow, they do not rescue one whose Allow is broad. Apply them after the Allow has been scoped, not instead of scoping it. Warning: do not add Deny statements before scoping the Allow. A Deny that references your own principals (e.g. denying access unless aws:PrincipalOrgID matches and SourceIP is on a missing list) can lock the account out of its own bucket if the Allow has not been verified to admit the operator first. Order matters: scope the Allow, verify access still works, then add Deny guards.


CTL.S3.POLICY.GHOSTREF.001

S3 Bucket Policy Must Not Reference Deleted Principals

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: nist_800_53_r5: AC-3; pci_dss_v4.0: 7.2.1; soc2: CC6.1;

S3 bucket policies must not grant access to principal ARNs that don't exist in the IAM inventory. Unlike IAM trust policies (which replace deleted ARNs with unique IDs), resource-based policies evaluate ARN strings directly. A new entity created with the same name as a deleted principal inherits every permission the bucket policy grants. An attacker with iam:CreateRole can claim the deleted principal's name and gain bucket access.

Remediation: Remove the ghost principal ARN from the bucket policy or recreate the intended principal.


CTL.S3.POLICY.OBJECTSCOPED.001

Public Read Grants Must Not Target Specific Objects

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: AC-3; iso_27001_2022: A.8.3; nist_800_53_r5: AC-3; pci_dss_v4.0: 7.2.1; soc2: CC6.1;

Bucket policy contains at least one Allow statement with a non-narrow principal whose Resource field names one or more specific object keys (e.g. arn:aws:s3:::bucket/backup.xlsx) rather than the whole bucket (arn:aws:s3:::bucket/*) or a logical prefix (bucket/assets/*). Object-scoped public grants are a distinct attack surface from bucket-wide public grants: the specific object ARN in the policy readback (exposed by iam:GetBucketPolicy if that permission is also public — see CTL.S3.POLICY.DISCLOSURE.001) hands an attacker a targeting list. The January 2026 Reju Kole disclosure chained exactly this pattern: a public GetBucketPolicy grant revealed that backup.xlsx was individually granted to Principal: "*", the researcher fetched it, cracked its Office password offline, and reused the recovered credentials for further compromise. Prowler and Pacu treat object-scoped grants as a distinct finding class for the same reason. Severity is medium, not high, because object-scoped public grants are a legitimate pattern for individually-published documents (PDFs, binaries, static assets pinned to specific keys). Operators triage based on the specific object keys listed in the finding's diagnostic context. When the target bucket also carries storage.tags.data-classification in [phi, pii, confidential], CTL.S3.PUBLIC.002 already fires at high severity on the composite signal — this control does not duplicate that coverage; it catches the untagged case where the object's sensitivity is not expressed in the contract.

Remediation: Review each object listed as a public target. For intentionally published documents, move to a published-assets prefix pattern (bucket/public/*) so the policy no longer enumerates individual keys. For anything not intentionally public, remove the grant. Consider moving published documents to a dedicated bucket so the host bucket never has object-scoped public grants at all. If the bucket also has s3:GetBucketPolicy open to the public (see CTL.S3.POLICY.DISCLOSURE.001), close that first — it is the discovery primitive that makes object-scoped grants exploitable without prior knowledge of key names.


CTL.S3.POLICY.SCOPING.001

Non-Narrow Bucket Policy Grants Must Carry a Scoping Condition

  • Severity: medium
  • Type: unsafe_state
  • Domain: governance
  • Compliance: hipaa: 164.312(a)(1); nist_800_53_r5: AC-3; soc2: CC6.1;

S3 bucket policies with Allow statements whose Principal is non-narrow (Principal: "*", Principal: {"AWS": "*"}, or an Allow block with no Principal) should constrain every such statement with at least one scoping Condition: aws:PrincipalOrgID, aws:SourceVpc, aws:SourceIp with a fixed CIDR, or aws:SourceArn. Without a scoping Condition, the effective principal set is the full internet (anonymous) or every AWS account on Earth (AWS: "*"). Scoping Conditions do not fix the name of the principal but they collapse the reachable principal set to callers routed through a known org, VPC, IP range, or service — a posture hardening step that prevents a future policy edit from silently expanding exposure. This control does not fire on buckets with no policy, nor on buckets whose Allow statements all name specific accounts or role ARNs; both states are captured by policy_has_scoping_condition being absent or null.

Remediation: For every Allow statement with Principal: "*", Principal: {"AWS": "*"}, or no Principal block, add a Condition that binds the request to a fixed value: aws:PrincipalOrgID to limit to the organization, aws:SourceVpc to limit to a VPC endpoint, aws:SourceIp with a CIDR to limit to a known range, or aws:SourceArn to limit to a specific caller. If the statement is supposed to be globally reachable (CDN origin, public data distribution), replace the bucket policy grant with a narrower mechanism — Origin Access Control, Access Points, or signed URLs.


CTL.S3.POLICY.SHADOW.ALLOW.001

Bucket Policy Must Not Grant Access Bypassing IAM Restrictions

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: nist_800_53_r5: AC-3; pci_dss_v4.0: 7.2.1; soc2: CC6.1;

The S3 bucket policy grants access to a principal whose IAM policy does not include the corresponding S3 actions. In AWS policy evaluation, a resource policy Allow with a specific principal ARN grants access even when the principal's IAM policy has no S3 permissions — the resource policy acts as a standalone grant for same-account principals. This creates a "shadow allow" invisible to IAM-centric auditing: scanning the principal's attached/inline policies shows no S3 access, but the bucket policy silently grants it. For cross-account principals, both IAM and resource policy must allow the action; for same-account principals, either policy alone is sufficient. This control flags same-account shadow allows where the bucket policy is the sole grant source.

Remediation: Review the bucket policy statements that name specific principal ARNs. For each, verify the principal's IAM policies also grant the same S3 actions. If the bucket policy is the sole source of access, either add the permission to the principal's IAM policy (making it visible) or remove the bucket policy statement.


CTL.S3.POLICY.WRITE.001

No Public Write via Bucket Policy

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure

S3 bucket policies must not grant write access to an anonymous or wildcard principal. Policy-based write access — a Statement with Effect Allow, Principal "" or AWS: "", and an Action such as s3:PutObject or s3:DeleteObject without a restricting Condition — enables attackers to upload malicious objects, overwrite existing content, or hold data ransom. This is distinct from CTL.S3.PUBLIC.003, which fires on the composite public_write signal regardless of mechanism; this control narrows to the policy path so the finding points the operator at the bucket policy specifically. For ACL-based public write, see CTL.S3.ACL.FULLCONTROL.001 (FULL_CONTROL grants) and CTL.S3.ACL.ESCALATION.001 (WRITE_ACP grants).

Remediation: Remove or constrain the policy statement granting write actions to Principal "" or AWS: "". If broad write access is genuinely required, add a restricting Condition on aws:PrincipalOrgID, aws:SourceVpc, or a fixed aws:SourceIp CIDR. Enable S3 Public Access Block with BlockPublicPolicy set to true to reject future policies that grant public write.


CTL.S3.PRESIGNED.001

Presigned URL Access Must Be Restricted

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(a)(1); soc2: CC6.1;

PHI bucket policy must restrict presigned URL access using s3:signatureAge (maximum age in milliseconds) or s3:authType (require REST-HEADER to block presigned URLs). Without these guardrails, presigned URLs can provide long-lived unauthenticated access to PHI data.

Remediation: Add a Deny statement with Condition NumericGreaterThan s3:signatureAge (e.g., 600000 for 10 minutes) or StringNotEquals s3:authType REST-HEADER to block presigned URL access.


CTL.S3.PUBLIC.001

No Public S3 Bucket Read

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws_v1.4.0: 2.1.5; fedramp_moderate: AC-3; ffiec: ISH-4; gdpr: Art.32; hipaa: 164.312(a)(1); iso_27001_2022: A.8.3; nist_800_53_r5: AC-3; pci_dss_v3.2.1: 1.2.1; pci_dss_v4.0: 7.2.1; soc2: CC6.1;

S3 buckets must not allow public read access. Detects buckets with anonymous read exposure via policy or ACL.

Remediation: Enable S3 Public Access Block (all four settings). Remove any bucket policy statements granting access to Principal "*". Remove any ACL grants to AllUsers or AuthenticatedUsers.


CTL.S3.PUBLIC.002

No Public S3 Buckets With Sensitive Data

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure

S3 buckets tagged with sensitive data classifications (PHI, PII, confidential) must not allow any public access.

Remediation: Immediately enable S3 Public Access Block (all four settings). Remove bucket policy statements granting access to Principal "*". Remove ACL grants to AllUsers or AuthenticatedUsers. Audit CloudTrail logs for unauthorized access during the exposure window.


CTL.S3.PUBLIC.003

No Public Write Access

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure

S3 buckets must not allow public write or delete access. Public write enables data injection, ransomware, and policy takeover.

Remediation: Remove bucket policy statements that grant s3:PutObject or s3:DeleteObject to Principal "*". Remove ACL grants that allow WRITE or FULL_CONTROL to AllUsers or AuthenticatedUsers. Enable S3 Public Access Block.


CTL.S3.PUBLIC.004

No Public Read via Bucket Policy

  • Severity: medium
  • Type: unsafe_duration
  • Domain: storage

S3 bucket policies must not grant read access to an anonymous or wildcard principal. A Statement with Effect Allow, Principal "" or AWS: "", and an Action such as s3:GetObject without a restricting Condition enables any unauthenticated caller to read objects. This is distinct from CTL.S3.PUBLIC.001, which fires on the composite public_read signal regardless of mechanism; this control narrows to the bucket-policy path so the finding points the operator at the policy specifically. For ACL-based public read, see CTL.S3.ACL.FULLCONTROL.001 (FULL_CONTROL grants) and CTL.S3.ACL.ESCALATION.001 (WRITE_ACP grants).

Remediation: Remove or constrain the policy statement granting read actions to Principal "" or AWS: "". If broad read access is genuinely required, add a restricting Condition on aws:PrincipalOrgID, aws:SourceVpc, or a fixed aws:SourceIp CIDR. Enable S3 Public Access Block with BlockPublicPolicy set to true to reject future policies that grant public read.


CTL.S3.PUBLIC.005

No Latent Public Read Exposure

  • Severity: medium
  • Type: unsafe_state
  • Domain: storage

S3 buckets must not have latent public read exposure where a public mechanism (policy or ACL) is masked only by Public Access Block. Removing PAB would immediately expose the bucket.

Remediation: Remove the underlying public-granting policy statement or ACL entry so the bucket does not depend solely on PAB for protection. Then verify PAB remains enabled as defense-in-depth.


CTL.S3.PUBLIC.006

No Latent Public Bucket Listing

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure

S3 bucket has a policy or ACL that would allow public listing if the public access block were removed. The public access block is currently the only control preventing directory enumeration. This is a latent vulnerability — one configuration change away from exposing all object keys.

Remediation: Remove the underlying policy statement or ACL entry that grants s3:ListBucket to Principal "*" or AllUsers. Do not rely solely on PAB to prevent directory enumeration.


CTL.S3.PUBLIC.007

No Public Read via Identity Policy

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure

IAM identity-based policies attached to users or roles must not grant broad read access to S3 buckets that is effectively public — for example, a policy attached to a widely-assumed role or to a role trusted by a public federated identity provider. This is distinct from CTL.S3.PUBLIC.004 (resource-based bucket policy granting public read); the identity-policy path is a separate AWS evaluation branch and deserves its own finding so the operator fixes the right artifact.

Remediation: Identify the identity-based policy statement granting read access and either remove it, scope it to specific bucket/object ARNs, or add conditions restricting the requesting principal. If a widely-trusted role is the root cause, tighten that role's trust policy first.


CTL.S3.PUBLIC.008

No Public List via Identity Policy

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure

IAM identity-based policies attached to users or roles must not grant broad list access (s3:ListBucket) that is effectively public. Anonymous-or-near-anonymous listing enables enumeration of the bucket contents, which typically precedes targeted object exfiltration. This is distinct from the ACL-based list exposure covered by CTL.S3.PUBLIC.LIST.001 / .002 and from resource-based list grants; the identity-policy path is its own finding.

Remediation: Identify the identity-based policy statement granting s3:ListBucket and remove it, scope it to specific bucket ARNs, or add conditions restricting the requesting principal. If a widely-trusted role is the root cause, tighten that role's trust policy first.


CTL.S3.PUBLIC.LIST.001

No Public S3 Bucket Listing

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure

S3 buckets must not allow anonymous listing of objects. Public listing exposes object keys, enabling targeted data exfiltration.

Remediation: Remove bucket policy statements that grant s3:ListBucket to Principal "*". Remove ACL grants that allow READ to AllUsers. Enable S3 Public Access Block.


CTL.S3.PUBLIC.LIST.002

Anonymous S3 Listing Must Be Explicitly Intended

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure

Anonymous bucket listing increases exposure surface even when objects are public by design. Listing must be explicitly intended via tag.

Remediation: If listing is intentional, add the tag public_list_intended=true to the bucket. Otherwise, remove the policy or ACL granting s3:ListBucket to Principal "*" or AllUsers.


CTL.S3.PUBLIC.PREFIX.001

Protected Prefixes Must Not Be Publicly Readable

  • Severity: high
  • Type: prefix_exposure
  • Domain: exposure

S3 bucket prefixes marked as protected must not be publicly readable. Evaluates bucket policies, ACL grants, and public access block settings to determine effective public read access for each protected prefix. Customize the prefix lists below to match your bucket layout.

Remediation: 1. Review the protected_prefixes and allowed_public_prefixes lists in this control and adjust them to match your bucket layout. 2. Enable S3 Public Access Block to restrict policy and ACL exposure. 3. Remove bucket policy statements granting s3:GetObject to Principal "*" for protected prefixes. 4. Remove ACL grants to AllUsers or AuthenticatedUsers.


CTL.S3.PUBLIC.RECUR.001

S3 Bucket Must Not Become Publicly Accessible Repeatedly

  • Severity: critical
  • Type: unsafe_recurrence
  • Domain: exposure
  • Compliance: fedramp_moderate: IR-5; hipaa: 164.308(a)(1)(ii)(D); nist_800_53_r5: IR-5; pci_dss_v4.0: 12.10; soc2: CC7.1;

S3 bucket has oscillated between private and publicly accessible more than twice within 30 days. Repeated public exposure indicates a broken deployment process, operational workaround, or an attacker re-enabling public access. The response is investigation, not remediation — determine who is making the bucket public and how they still have access.

Remediation: Investigate the root cause of the repeated oscillation. Determine whether the pattern indicates a broken process, operational workaround, or active compromise. Review CloudTrail for the API calls that triggered each transition.


CTL.S3.REGION.001

S3 Buckets Must Be in Approved Regions

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: gdpr: Art.44;

S3 buckets containing personal data must be located in approved regions as determined by data residency requirements (e.g., EU/EEA regions for GDPR). Storing data outside approved regions may violate data transfer restrictions.

Remediation: Create a new bucket in an approved region and migrate data. Use S3 replication to move data, then delete the original bucket.


CTL.S3.REPLICATION.001

Compliance-Tagged Buckets Must Have Replication Enabled

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: CP-9; hipaa: 164.308(a)(7); iso_27001_2022: A.8.13; nist_800_53_r5: CP-9; soc2: A1.1;

S3 buckets tagged with a compliance framework (soc2, gdpr, hipaa, pci-dss, etc.) must have replication configured. Without replication, a regional outage or accidental bucket deletion can cause permanent data loss for regulated data. Replication provides an independent copy that survives single-region failures and supports disaster recovery objectives.

Remediation: Configure S3 replication on the bucket using aws s3api put-bucket-replication. Use cross-region replication (CRR) for disaster recovery or same-region replication (SRR) for compliance copies. Ensure versioning is enabled on both source and destination buckets.


CTL.S3.REPLICATION.002

PHI Replication Must Be Cross-Region

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: CP-6(1); hipaa: 164.308(a)(7)(ii)(A); nist_800_53_r5: CP-6(1); soc2: A1.2;

S3 buckets tagged with data-classification=phi that have replication enabled must replicate to a different AWS region. Same-region replication (SRR) does not protect against regional outages, AZ-wide failures, or region-scoped service disruptions. HIPAA contingency planning requires data to survive regional disasters.

Remediation: Update the replication configuration to use a destination bucket in a different AWS region. Ensure the destination bucket has versioning enabled, appropriate encryption, and a bucket policy that permits the replication role.


CTL.S3.REPLICATION.003

Replication Destination Must Be Encrypted

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: fedramp_moderate: SC-28; gdpr: Art.32; hipaa: 164.312(a)(2)(iv); nist_800_53_r5: SC-28; pci_dss_v4.0: 3.4.1; soc2: CC6.1;

When S3 replication is enabled, the destination bucket must have server-side encryption configured. Replicating data to an unencrypted destination creates a shadow copy that bypasses the source bucket's encryption controls. This is especially dangerous for sensitive data where the source meets encryption requirements but the replica does not.

Remediation: Configure default encryption on the destination bucket using SSE-S3 or SSE-KMS. For replication of encrypted objects, add a ReplicaKmsKeyID to the replication rule so objects are re-encrypted with a key in the destination region.


CTL.S3.REPLICATION.DEST.DELETE.001

Replication Delete Marker Replication Enabled

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: nist_800_53_r5: SC-28; soc2: A1.2;

Delete marker replication is enabled. When objects are deleted in the source, the deletion replicates to the destination. An attacker who gains delete access to the source can destroy both copies simultaneously — the destination is not an independent backup, it is a synchronized mirror that replicates deletions.

Remediation: Disable DeleteMarkerReplication in the replication rule unless both copies are intentionally synchronized. For disaster recovery, the destination should retain objects independently of source deletions.


CTL.S3.REPLICATION.DEST.PUBLIC.001

Replication Destination Bucket Allows Public Access

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: hipaa: 164.312(e); nist_800_53_r5: SC-28; pci_dss_v4.0: 3.4.1; soc2: CC6.1;

S3 replication is configured to a destination bucket that has public access enabled or weaker Public Access Block settings than the source. Data replicated from a private, locked-down source bucket arrives at a publicly accessible destination. Replication copies objects — it does not copy the source bucket's access controls, Public Access Block settings, or encryption configuration.

Remediation: Verify the destination bucket has Public Access Block fully enabled and no public bucket policy. Apply the same PAB settings as the source bucket to the destination.


CTL.S3.REPO.ARTIFACT.001

Public Buckets Must Not Expose VCS Artifacts

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure

Buckets that serve public content must not expose version control artifacts such as .git/ or .svn/. Presence of these paths enables repo reconstruction and can leak secrets.

Remediation: Remove .git/, .svn/, and other VCS directories from the bucket. Add a lifecycle rule or deployment script that excludes VCS artifacts from uploads. If the bucket is a static website, configure your build pipeline to strip VCS files before deployment.


CTL.S3.TENANT.ISOLATION.001

Shared-Bucket Tenant Isolation Must Enforce Prefix

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure

When a shared S3 bucket uses prefix-based tenant isolation, every app-signer identity that produces presigned URLs must enforce the tenant prefix. An identity that allows path traversal (../) or disables prefix enforcement lets one tenant read or overwrite another tenant's objects.

Remediation: Update the app-signer configuration to enforce tenant prefix restrictions (enforce_prefix=true) and block path traversal (allow_traversal=false) on all presigned URL signers.


CTL.S3.VERSION.001

Versioning Required

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: cis_aws_v1.4.0: 2.1.3; hipaa: 164.312(c)(1); nist_800_53_r5: CP-9; soc2: CC6.1;

S3 buckets must have versioning enabled to protect against accidental deletion and enable recovery from negligent operations.

Remediation: Enable versioning on the bucket using aws s3api put-bucket-versioning. Once enabled, configure lifecycle rules to manage noncurrent versions and control storage costs.


CTL.S3.VERSION.002

Backup Buckets Must Have MFA Delete Enabled

  • Severity: medium
  • Type: unsafe_state
  • Domain: exposure

S3 buckets tagged with backup=true must have MFA delete enabled. MFA delete requires multi-factor authentication to permanently delete object versions, protecting against ransomware attacks and accidental mass deletion of backup data. Without MFA delete, any principal with s3:DeleteObject permission can permanently destroy backup versions.

Remediation: Enable MFA delete on the bucket using aws s3api put-bucket-versioning with the MFA flag. This requires the root account credentials and an MFA device. Only the root account can enable or disable MFA delete.


CTL.S3.WEBSITE.PUBLIC.001

No Public Website Hosting with Public Read

  • Severity: critical
  • Type: unsafe_state
  • Domain: exposure
  • Compliance: nist_800_53_r5: AC-3;

S3 buckets with static website hosting enabled must not also have public read access. Website hosting combined with public read serves content directly to the internet.

Remediation: If public hosting is not intended, disable static website hosting and remove public read access. If hosting is intended, move content behind CloudFront with an Origin Access Control (OAC) and remove direct public access from the bucket.


CTL.S3.WRITE.CONTENT.001

S3 Signed Upload Must Restrict Content Types

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure

Signed upload policies must restrict allowed content types. Unrestricted content types enable attackers to upload SVGs with embedded JavaScript or HTML files, causing stored XSS when served from the bucket's domain.

Remediation: Add an exact content-type condition to the signed upload policy (e.g., eq $Content-Type image/jpeg). Avoid starts-with with empty prefix, which allows any content type.


CTL.S3.WRITE.SCOPE.001

S3 Signed Upload Must Bind To Exact Object Key

  • Severity: high
  • Type: unsafe_state
  • Domain: exposure

Signed upload policies must restrict write permission to a single exact object key. Prefix-wide permissions (e.g., starts-with $key files/) enable arbitrary overwrite and cross-tenant tampering.

Remediation: Change the signed upload policy to use an exact key condition (eq instead of starts-with) that binds each upload to a specific object path. Generate unique object keys server-side.