Last updated: April 26, 2026
S3 misconfiguration is the single most-cited cloud breach pattern in the press, and the pattern repeats stubbornly. Across 30 Indian startup audits in the last year, roughly one in three had at least one S3 bucket containing customer data accessible without authentication. This article walks the misconfiguration patterns we actually find, the detection that catches each, and the policy-as-code workflow that prevents recurrence.
What “public S3” really means in 2026
“Public” is not a single setting. S3 access is governed by a layered system:
- Block Public Access at the account level (4 toggles)
- Block Public Access at the bucket level (4 toggles)
- Bucket policy (resource-based JSON policy)
- ACL at the bucket level (legacy, mostly deprecated)
- ACL at the object level
- IAM policies on principals accessing the bucket
A bucket can be “public” through any one of these. The 2018 era of s3://my-bucket-public/ directly accessible has narrowed but not closed — newer misconfigurations are subtler.
The five misconfigurations we actually find
1. Block Public Access disabled at the account level
The single highest-impact toggle. When account-level BPA is on, no bucket in the account can be made public regardless of its individual settings. Most enterprises now have this enabled — but startups and old AWS accounts often do not.
aws s3control get-public-access-block --account-id 123456789012
If any of the four flags is false, that is the first remediation.
2. Bucket policy with overly broad Principal
Even with BPA on, a bucket policy can grant access to specific external accounts or roles. The pattern we see most often:
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*",
"Condition": {
"StringEquals": {
"aws:SourceVpc": "vpc-1234"
}
}
}
The condition seems to limit access to one VPC. But “Principal: *” combined with a condition only works as expected if the request authenticates AS something that has the matching context. Anonymous public requests do not have aws:SourceVpc set, so the policy denies them. But many other contexts that the developer did not consider are also empty — and the policy can be matched in unexpected ways.
The right pattern is to specify the principal explicitly:
"Principal": { "AWS": "arn:aws:iam::123456789012:role/MyAppRole" }
3. Pre-signed URL leakage
Pre-signed URLs are time-limited HTTPS URLs that grant access to a specific S3 object. They look like:
https://my-bucket.s3.amazonaws.com/customer-doc.pdf?X-Amz-Algorithm=...&X-Amz-Expires=604800&X-Amz-Signature=...
The default expiry is 7 days, and applications often set even longer. If a pre-signed URL leaks (server logs, browser history, email, Slack), anyone with it gets the data. We routinely find pre-signed URLs in:
- Server access logs (the URL is logged with every request)
- WAF logs
- Email confirmations (“download your receipt”)
- Mobile app crash reports (if the URL was being processed)
- Customer support tickets where the URL was shared as evidence
Mitigation: short expiry (5 minutes for sensitive content, 1 hour max), bind to client IP via condition, use VPC-only buckets where the application is the consumer.
4. Object-level ACL public-read
Even with bucket-level BPA on, individual objects can have ACLs that grant public access. Legacy applications using older S3 SDKs sometimes set x-amz-acl: public-read on uploads.
aws s3api list-objects-v2 --bucket my-bucket --query 'Contents[].Key' \
| xargs -I {} aws s3api get-object-acl --bucket my-bucket --key {} \
| grep -B5 AllUsers
Run this against your buckets. Any results are public-read objects. The fix is either to remove the ACL or to enable Object Ownership = bucket-owner-enforced (which disables ACLs entirely).
5. Cross-account bucket access via misconfigured AssumeRole
This is the subtler pattern. Your S3 bucket trusts a role from another AWS account. That account is over-permissioned, or has been compromised. The trust relationship then becomes the path.
# Bucket policy:
"Principal": { "AWS": "arn:aws:iam::222222222222:role/PartnerAccessRole" }
# Account 222 has a role that can be assumed by anyone in the account
# Account 222 has been compromised
# Attacker now has access to your bucket via the trust chain
Mitigation: tighten the bucket policy to specific role ARNs (not wildcards), and require aws:PrincipalOrgID matching your organisation in the condition where possible.
How to find every misconfiguration in your account
The free toolchain that works:
- AWS Config rules —
s3-bucket-public-read-prohibited,s3-bucket-public-write-prohibited,s3-bucket-server-side-encryption-enabled. Enable them all. - AWS Macie — discovers and classifies sensitive data in S3, alerts on PII findings in publicly-accessible buckets. Costs scale with data volume; budget accordingly.
- Prowler (open source) — runs CIS AWS Foundation Benchmark checks including comprehensive S3 audits.
- ScoutSuite (open source) — multi-cloud security audit; detailed S3 findings.
- aws-allowlister — generates a tight bucket policy from an allow-list of principals.
Run all four against your account weekly. Treat findings as P1 tickets.
Prevention as code
The strategic answer is policy-as-code in the deployment pipeline:
- Terraform — every S3 bucket resource carries an explicit
aws_s3_bucket_public_access_blockresource. Linter (tflint,checkov) blocks PRs that omit it. - CloudFormation Guard / Conftest — policy rules that fail CI if a template tries to make a bucket public.
- OPA Rego — for multi-cloud or beyond-IAC validation.
- Service Control Policy at AWS Organizations level — block
s3:PutBucketPolicywith statements containing"Principal": "*"outside designated workloads.
The goal: no developer can accidentally create a public bucket. The PR workflow catches it; the SCP catches it; AWS Config detects it within minutes if the first two miss.
How to think about it as a defender
S3 misconfiguration is an organisational hygiene problem more than a security skill problem. The patterns are well-known, the detections are off-the-shelf, the prevention is code-based. The reason it persists is operational drift — buckets created in 2019 that were “temporary” never got migrated; the engineer who created them is no longer at the company; the new IT lead does not know it exists; the audit checklist did not include “list every S3 bucket and confirm BPA settings.”
The discipline is quarterly:
- Inventory every S3 bucket across every AWS account in the organisation.
- For each, who owns it, what data is in it, what access level is required, what is the current configuration?
- Anomalies: bucket with no clear owner, bucket with public access not justified, bucket with sensitive data unencrypted.
- Remediation tickets, owned, tracked, closed.
- Repeat next quarter.
Compliance angle
- DPDP §8(5) — public S3 with personal data is the textbook reasonable-security failure case. Recent enforcement actions in similar jurisdictions (GDPR-driven fines for public S3 leaks) have hit 8-figure euro penalties.
- RBI Cloud Guidelines — payments data on public S3 is a localisation and confidentiality breach simultaneously.
- SEBI CSCRF — investor data exposure via S3 misconfiguration is a reportable cyber incident.
- ISO 27017 / SOC 2 CC6 — logical access controls; relevant audit finding.
The takeaway
S3 public-bucket breaches in 2026 are not technical sophistication failures. They are operational discipline failures. Run Prowler against your accounts this week. The findings will be your prioritised remediation backlog. Adopt Block Public Access at organisation level, enforce it via SCP, and bake bucket-policy validation into your CI. After two quarters, the recurrence rate drops to near zero. After two more, the buckets you have are explicitly known and intentionally configured. That is the difference between “lucky” and “secure.”
Get a cloud posture review
IAM hardening, public-exposure mapping, IaC review, K8s audit. We map your actual blast radius — not what a CSPM dashboard guesses at.