S3 security beyond "block public access": the eight checks we run

Public buckets are the headline and the easy part. The leaks we find now come from bucket policies, presigned URLs, replication and logs nobody reads. Here is the full pass.

Block Public Access solved the 2017 problem. Buckets are not accidentally world-readable much any more, and every scanner catches the ones that are. The incidents we see now are quieter: a bucket policy trusting an entire account, a presigned URL with a 7-day expiry sitting in a Slack channel, replication to a bucket in an account nobody remembers owning.

This is the full pass we run on S3 in an assessment, in order.

1. Account-level Block Public Access, everywhere

Not per bucket — at the account level, in every account, enforced by an SCP that denies s3:PutAccountPublicAccessBlock when it would weaken the setting. Per-bucket settings are a config item that can drift; the account setting is a wall. If you genuinely need a public bucket for static assets, put it in its own account with its own rules, and front it with CloudFront and an origin access control rather than public reads.

2. Read every bucket policy, not just the public ones

The finding we report most often is not a public bucket, it is a bucket policy with "Principal": {"AWS": "arn:aws:iam::123456789012:root"} — which trusts every principal in that account, including the intern's role and anything that gets compromised there. Scope to specific role ARNs. Where a third party needs access, require sts:ExternalId and a condition on aws:SourceAccount or aws:SourceArn.

Also add a aws:SecureTransport deny and, where you can, an aws:PrincipalOrgID condition, which turns "anyone with the ARN" into "anyone inside our organisation".

3. Encryption, and who holds the key

SSE-S3 is on by default now and is fine for most data. SSE-KMS matters when you want an audit trail of decryption and the ability to revoke access by key policy rather than by bucket policy. Use a customer-managed key for anything regulated, enable bucket keys to cut the KMS request cost, and — the part people miss — review the key policy, because a permissive KMS key policy undoes a careful bucket policy.

4. Presigned URLs and their lifetime

This is the current leak vector. A presigned URL carries the signer's permissions for its whole validity, and it is a bearer token: whoever has the link has the object. Two rules: cap expiry at 15 minutes for anything sensitive, and generate them with a dedicated role whose permissions are scoped to one prefix, never with an application role that can read the whole bucket.

5. Versioning, object lock and the delete path

Versioning on, MFA delete or object lock for logs and backups, and a lifecycle rule to expire noncurrent versions — otherwise versioning is a silent cost leak. For backups that must survive a compromised account, replicate to a bucket in a separate account with object lock in compliance mode. That combination is what turns "we have backups" into "we have backups an attacker with admin cannot delete".

6. Access logging that someone can query

Server access logs or, better, CloudTrail data events for the buckets that matter. Data events cost money at volume, so enable them selectively on the buckets holding customer data, ship to the log archive account, and make sure someone can actually query them — Athena over the log bucket with a partition projection table, set up once.

7. Replication and where the copies live

Enumerate every replication rule and write down the destination account. In estates older than three years we almost always find a replication target in an account with no owner, no monitoring and a stale bucket policy. A copy of your data in an unmonitored account is your data, with none of your controls.

8. What is actually in there

Macie, run as a one-off scan rather than continuously, tells you which buckets contain card numbers, national IDs or credentials. It is expensive to leave running; it is cheap and revealing as a quarterly job. Most teams find at least one bucket with data they did not know was classified — which usually changes the retention conversation entirely.

We fold all eight into the security assessment, and the open-source scanners in our posture tooling piece cover checks one through three without any licence.

What to do this week

Pull every bucket policy in production with aws s3api get-bucket-policy in a loop and grep for :root". Every hit is a bucket trusting a whole account. That grep takes ten minutes and it is the highest-yield S3 check there is.

ConsultorIA

Want this done on your cloud?

A ten-day read-only assessment is free, and Skyline lets you see your estate on a map before you write to us.

Related articles