Skip to main content
emnode
Compliance High severity

AWS Security Hub · CloudFront

CloudFront.1: No default root object, exposing the distribution listing

Written and reviewed by Emnode · Last reviewed

What does AWS Security Hub CloudFront.1 check?

CloudFront.1 checks whether a distribution with an S3 origin has a default root object configured. It reports FAILED when the `DefaultRootObject` field is empty, so a request to the bare distribution URL has nothing to map to. Custom-origin-only distributions are out of scope, because those origins handle root requests themselves.

Why does CloudFront.1 matter?

With no default root object, a request to the root path can surface whatever the S3 origin returns there: in the worst case an enumeration of object keys that leaks build artefacts, backups, or config files never meant to be public. The gap is silent: nobody hits the bare URL in normal use, so it stays open until someone curious or hostile trims the address back to the domain.

How do I fix CloudFront.1?

  1. Confirm the intended root object (usually `index.html`) actually exists at the root of the origin bucket: pointing at a missing object just 404s the home page.
  2. Pull the full distribution config and its ETag with `get-distribution-config`, set `DefaultRootObject` on the complete config, and push it back with `update-distribution --if-match <ETag>`.
  3. Wait for the distribution to return to `Deployed`, then curl the bare domain to confirm it serves the home page.
  4. Layer on S3 Block Public Access and origin access control so the root object isn't your only line of defence.

Remediation script · bash

# 1. Lock an S3 origin: only THIS distribution may read the bucket.
cat > bucket-policy.json <<'JSON'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowCloudFrontServicePrincipalReadOnly",
    "Effect": "Allow",
    "Principal": { "Service": "cloudfront.amazonaws.com" },
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::my-downloads-bucket/*",
    "Condition": { "StringEquals": {
      "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/E2QWRUHAPOMQZL"
    } }
  }]
}
JSON
aws s3api put-bucket-policy --bucket my-downloads-bucket --policy file://bucket-policy.json

# 2. Raise the minimum TLS version (the field is nested in ViewerCertificate, so send the whole config back).
aws cloudfront get-distribution-config --id E2QWRUHAPOMQZL > dist.json
ETAG=$(python3 -c "import json;print(json.load(open('dist.json'))['ETag'])")
# ... edit dist.json: ViewerCertificate.MinimumProtocolVersion = TLSv1.2_2021, ViewerProtocolPolicy = redirect-to-https ...
aws cloudfront update-distribution --id E2QWRUHAPOMQZL \
  --distribution-config file://distribution-config.json --if-match "$ETAG"

# 3. Confirm the second door is shut: a raw S3 GET should now return 403.
curl -s -o /dev/null -w '%{http_code}\n' https://my-downloads-bucket.s3.amazonaws.com/file.pdf

Full walkthrough (console steps, edge cases and verification) in the lesson Protect CloudFront distributions and origins.

Is CloudFront.1 a false positive?

The default root object only applies to the root path. Setting it does not make `/blog/` resolve to `blog/index.html`, so teams expecting directory-style behaviour are sometimes surprised, but that limitation does not affect whether CloudFront.1 passes.

Part of the learning path Lock down access