Safeguard
Cloud Security

AWS S3 Bucket Security: The Complete 2026 Guide

S3 is the single most common source of cloud data leaks. This guide covers block public access, encryption, bucket policies, and how to enforce all three in Terraform.

Marcus Chen
Cloud Security Engineer
5 min read

No AWS service has caused more data-leak headlines than S3. The pattern is always the same: a bucket that should have been private is reachable by anyone with the URL, and a scraper finds it before the owner does. AWS has spent years making this harder — new buckets have blocked public access and default encryption since 2023, and ACLs are disabled by default — but the leaks continue, because existing buckets carry old settings, cross-account grants bypass the obvious controls, and a single overly broad bucket policy undoes every default. This guide is a complete, practical walkthrough of S3 security, ending with Terraform you can drop into a module so every bucket is born secure.

The Anatomy of an S3 Leak

An S3 bucket can become public through several independent paths, which is why "I didn't make it public" is so often wrong:

  • A bucket policy with "Principal": "*" and no restricting condition.
  • A legacy ACL granting AllUsers or AuthenticatedUsers (mostly mitigated now that ACLs are off by default, but present on older buckets).
  • An account or bucket that disabled Block Public Access to serve a static site and never re-scoped.
  • A cross-account grant that's technically not "public" but exposes data to an untrusted account.

Securing S3 means closing all of these, not just the one you remember.

Layer 1: Block Public Access

Block Public Access (BPA) is the master switch. Enable it at the account level so no bucket, existing or future, can be made public by accident, then leave it on at the bucket level too. If a specific bucket genuinely must serve public content (a static website), scope the exception to that one bucket rather than disabling BPA account-wide.

# Enable account-level Block Public Access
aws s3control put-public-access-block \
  --account-id 111122223333 \
  --public-access-block-configuration \
  BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true

Layer 2: Encryption at Rest

New buckets apply SSE-S3 encryption by default, but for sensitive data use SSE-KMS with a customer-managed key so you control rotation and can revoke access by disabling the key. This also gives you an audit trail — every decrypt is a CloudTrail event tied to the KMS key.

Layer 3: A Least-Privilege Bucket Policy

The bucket policy is where leaks are most often introduced and most often fixed. Two rules cover most of the risk: deny any request not using TLS, and never grant to * without a tight condition. This policy enforces TLS and denies unencrypted uploads:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyInsecureTransport",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": ["arn:aws:s3:::acme-data", "arn:aws:s3:::acme-data/*"],
      "Condition": { "Bool": { "aws:SecureTransport": "false" } }
    },
    {
      "Sid": "DenyUnencryptedUploads",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::acme-data/*",
      "Condition": { "StringNotEquals": { "s3:x-amz-server-side-encryption": "aws:kms" } }
    }
  ]
}

Layer 4: Logging and Versioning

Enable server access logging or CloudTrail data events so you can reconstruct who accessed what during an incident. Enable versioning plus MFA delete on critical buckets so an attacker (or a mistake) can't silently overwrite or delete objects. Add lifecycle rules to expire old versions and control cost.

Put It Together: Secure-by-Default in Terraform

The reliable way to keep every bucket secure is to make secure the only option — a module that bundles all four layers so a developer can't create an insecure bucket:

resource "aws_s3_bucket" "this" {
  bucket = var.name
}

resource "aws_s3_bucket_public_access_block" "this" {
  bucket                  = aws_s3_bucket.this.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
  bucket = aws_s3_bucket.this.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = var.kms_key_arn
    }
  }
}

resource "aws_s3_bucket_versioning" "this" {
  bucket = aws_s3_bucket.this.id
  versioning_configuration { status = "Enabled" }
}

Scanning this module in CI guarantees the settings can't be weakened in a future edit — which is exactly what infrastructure-as-code scanning enforces.

S3 Security Checklist

LayerControlEnforcement
Public accessAccount-level BPA onDeny at org level
EncryptionSSE-KMS with CMKBucket policy denies unencrypted PUT
TransportTLS requiredaws:SecureTransport deny
PolicyNo * without tight conditionIaC scan in CI
LoggingAccess logging / CloudTrail data eventsOn for sensitive buckets
IntegrityVersioning + MFA deleteOn for critical buckets

How Safeguard Helps

Safeguard catches the S3 misconfigurations that cause leaks in the pull request that introduces them. Its IaC scanning runs Trivy's misconfiguration engine over your Terraform and CloudFormation to flag buckets missing Block Public Access, buckets without encryption, policies that grant to *, and missing TLS enforcement — mapped to the exact line so a developer fixes it before merge, not after a scraper finds the data. Because it runs through the pipeline-native CLI, you can gate merges so an insecure bucket definition can't ship at all. That build-time prevention complements runtime posture tools that would only flag the same bucket after it's already public; the Safeguard vs Wiz comparison explains how the two vantage points fit together.

S3 leaks are almost never sophisticated — they're a defaulted setting or an over-broad policy. Enable Block Public Access account-wide, encrypt with your own keys, keep bucket policies tight, and enforce all of it in code so secure is the only way to create a bucket.

Ready to stop S3 misconfigurations before they ship? Create a free Safeguard account or read the documentation to scan your S3 infrastructure-as-code.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.