aws-s3-static-website-public

S3 static website hosting publishes a public HTTP endpoint serving bucket objects, independent of the bucket policy's API access controls.

derived aws emits ExposedToInternet

match

A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.

{'bucket': None} HasPolicy {'public_grant': None}

where

node_type(?bucket) == ObjectStorage ?bucket.provider_type == 'AWS::S3::Bucket' ?bucket.properties.WebsiteConfiguration.IndexDocument is set (website hosting enabled) ?public_grant represents a bucket policy or bucket ACL granting s3:GetObject to Principal:* (or allUsers grantee in ACL) ?bucket.properties.PublicAccessBlockConfiguration.BlockPublicAcls == false AND ?bucket.properties.ObjectOwnership != 'BucketOwnerEnforced' OR access is via bucket policy (not ACL)

emit

source typeObjectStorage
target typeAnonymousIdentity
source?bucket
target<AnonymousIdentity>
conditionsresource_policy
state logicACTIVE when BOTH conditions hold: (1) website hosting is confirmed (GetBucketWebsite succeeds with IndexDocument set) AND (2) the bucket allows object reads to unauthenticated principals via bucket policy OR via ACL when ObjectOwnership != BucketOwnerEnforced. CONDITIONAL(resource_policy) when website hosting is enabled but the access control configuration is not yet resolved. BLOCKED when BlockPublicAcls=true AND the only public grant is via ACL (policy-based grants are separate). BLOCKED when ObjectOwnership=BucketOwnerEnforced AND no bucket-policy public allow exists.

Narrative

{bucket.name} has S3 static website hosting enabled and allows unauthenticated public read - the bucket's contents are served publicly via the S3 website endpoint URL.

Raw rule rules/derived/aws/s3.yaml

id: aws-s3-static-website-public
emits: ExposedToInternet
description: S3 static website hosting publishes a public HTTP endpoint serving bucket objects, independent
  of the bucket policy's API access controls.
match:
- - bucket: null
  - HasPolicy
  - public_grant: null
where:
- node_type(?bucket) == ObjectStorage
- ?bucket.provider_type == 'AWS::S3::Bucket'
- ?bucket.properties.WebsiteConfiguration.IndexDocument is set (website hosting enabled)
- ?public_grant represents a bucket policy or bucket ACL granting s3:GetObject to Principal:* (or allUsers
  grantee in ACL)
- ?bucket.properties.PublicAccessBlockConfiguration.BlockPublicAcls == false AND ?bucket.properties.ObjectOwnership
  != 'BucketOwnerEnforced' OR access is via bucket policy (not ACL)
emit:
  source_type: ObjectStorage
  target_type: AnonymousIdentity
  source: ?bucket
  target: <AnonymousIdentity>
  permissions: []
  conditions:
  - resource_policy
  state_logic: 'ACTIVE when BOTH conditions hold: (1) website hosting is confirmed (GetBucketWebsite succeeds
    with IndexDocument set) AND (2) the bucket allows object reads to unauthenticated principals via bucket
    policy OR via ACL when ObjectOwnership != BucketOwnerEnforced. CONDITIONAL(resource_policy) when website
    hosting is enabled but the access control configuration is not yet resolved. BLOCKED when BlockPublicAcls=true
    AND the only public grant is via ACL (policy-based grants are separate). BLOCKED when ObjectOwnership=BucketOwnerEnforced
    AND no bucket-policy public allow exists.'
  confidence: min(contributing_confidences) * 0.90
  derived_from:
  - ?bucket HasPolicy ?public_grant (GetBucketPolicy + bucket ACL evidence)
  - ?bucket.properties.WebsiteConfiguration.IndexDocument (GetBucketWebsite)
  false_positive_note: "Static website hosting alone does not guarantee objects are publicly readable;\
    \ the bucket policy or ACL must also permit GetObject to Principal:* or allUsers. If ObjectOwnership=BucketOwnerEnforced,\
    \ ACL grants are silently ignored; in that case only a bucket-policy public allow makes objects readable\
    \ \u2014 do NOT emit based on ACL when ObjectOwnership is enforced. Block Public Access BlockPublicAcls=true\
    \ blocks ACL-based grants (the engine should BLOCK the output when only ACL permits access). The website\
    \ endpoint serves objects over HTTP (not HTTPS) unless a CloudFront distribution fronts it; the ExposedToInternet\
    \ fact is still valid. Do NOT emit if the only public path is via an authenticated S3 API call (signed\
    \ URL)."
  narrative: "{bucket.name} has S3 static website hosting enabled and allows unauthenticated public read\
    \ \u2014 the bucket's contents are served publicly via the S3 website endpoint URL."
move · open · esc close