S3 Bucket Policy Mistakes Engineers Make (And How to Fix Them)

S3 bucket policies are a specific circle of hell in AWS permission management. They’re different enough from identity-based policies that people forget they work differently, yet similar enough that people think they understand them. This is part of a broader problem: most AWS IAM policies are overpermissioned because they’re hard to verify. But S3 bucket policies take the confusion to another level.”

The reason? Most engineers write S3 policies without a proper S3 bucket policy generator or visualizer, relying instead on guesswork and copy-paste patterns. The result is a steady stream of misconfigurations…

The Fundamental Difference Nobody Internalizes

An identity policy (attached to a user, role, or group) says: “Here’s what YOU can do.”

A bucket policy says: “Here’s what ANY principal can do TO ME.”

This distinction matters for how wildcard evaluation works, how cross-account access is evaluated, and what actually happens when you say “Allow *” in the principal field.

Most engineers treat bucket policies like identity policies with different JSON syntax. They’re not. The evaluation logic is different. The common mistakes follow directly from this misunderstanding.

Common Bucket Policy Mistakes

The Over-Permissioned Principal

The classic pattern: you need to let one specific service (like Lambda) access your bucket. You write:

{
  "Effect": "Allow",
  "Principal": "*",
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::my-bucket/*"
}

Your reasoning: “It’s just GetObject, that’s safe. And I’m restricting the bucket.”

But now anyone on the internet can read your bucket contents. You’ve made a public dataset by accident. I’ve seen this in production on buckets containing PII, API keys, and internal documents. The team didn’t think it was a problem because “it only grants GetObject.” They didn’t think about what “anyone” means.

The fix should be obvious (use a principal condition), but it’s not obvious in the moment because bucket policies don’t feel as “dangerous” as identity policies. This is a dangerous intuition.

The Wildcard Action in a Bucket Policy

Similar problem, different angle:

{
“Effect”: “Allow”,
“Principal”: { “Service”: “lambda.amazonaws.com” },
“Action”: “s3:“, “Resource”: “arn:aws:s3:::my-bucket/
}

Your reasoning: “Lambda needs to access the bucket. I’ll just let it do anything. It’s an AWS service, so it’s trusted.”

Now Lambda has DeleteBucket permissions. If a Lambda function is compromised (or if any Lambda function in the account is compromised), it can delete your entire bucket. This has happened multiple times in real incident reports I’ve read.

The engineer’s thinking: “I’m restricting it to Lambda, so it’s safe.” They’re not considering that the principal restriction doesn’t matter if the actions are too broad.

The Implicit Deny Assumption

You write a bucket policy that denies a specific action. You assume everything else is denied. But bucket policies don’t work that way. A Deny is absolute, yes, but the absence of a statement doesn’t mean Deny—it means the policy is silent. Other policies (identity policies on the principal) might Allow.

Engineers come from application security backgrounds where you’re used to “deny by default, allow explicitly.” IAM doesn’t work that way. A Deny in a bucket policy is overridden by an Allow in an identity policy. The evaluation is more complex than you’d expect.

I’ve seen bucket policies that tried to prevent public access by explicitly denying actions to the principal “*”. But the bucket had an identity policy that allowed those same actions, so the Deny was pointless. The engineer thought they’d secured something they hadn’t.

The Cross-Account Nightmare

You need to let another AWS account access your bucket. You write:

{
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::ACCOUNT-ID:root" },
  "Action": "s3:*",
  "Resource": "arn:aws:s3:::my-bucket/*"
}

Your reasoning: “I’m trusting the whole account. That’s their problem to lock down internally.”

But you’ve just given that entire account’s root user access to your bucket. If their account is compromised, your bucket is compromised. This is the same mistake that happens with overpermissioned IAM trust relationships and role assumptions, where you grant broad access instead of specific access. If they have a rogue admin, your data is exposed. You’re relying on their security posture without actually understanding it.

The better approach is specifying exactly which role in their account should have access. But that requires a conversation with the other team, and it’s easier to just trust the root user. Right up until it’s not.

Why These Mistakes Persist

1. Visual Verification Is Hard

Bucket policies live in the AWS console. The console shows you JSON. Reading JSON to understand what it does is just hard. Your brain doesn’t naturally convert "s3:*" to “oh, that includes DeleteBucket.” You have to mentally execute the permission matching logic.

If someone showed you a visual representation—”This policy allows: GetObject, PutObject, DeleteObject, DeleteBucket, etc.”—you’d immediately see the problem with DeleteBucket being included when you only wanted read-write.

2. Context Switching

Developers write S3 policies while context-switching. They’re working on multiple services, switching between identity policies and bucket policies, moving between the console and infrastructure-as-code. In that mental state, the distinction between “what this principal can do” and “what any principal can do” gets blurred.

3. Trial and Error

When a policy doesn’t work, people incrementally widen permissions until it does. They increase from s3:GetObject to s3:Get* to s3:*. Each increment is a small step. They stop testing the moment it works. They don’t think about the implications of the final state.

If they saw the final policy explained in plain language—”This grants delete, modify, and admin actions on the bucket”—they might realize they overshoot.

4. Nobody Reviews These

Bucket policies are often written once and never reviewed. Unlike application code, which goes through PR review, policies are deployed by whoever’s working on infrastructure that day. There’s no standard security review. Mistakes compound.

What Prevents These Mistakes

The nuclear option is automating bucket security. Tools check that public access is blocked, that bucket policies conform to company standards, that wildcard actions aren’t used carelessly. This helps.

But the simpler prevention is making it obvious what the policy does. When a policy’s implications are clearly stated—”This allows anyone with an AWS account to read files in this bucket”—people often realize they need to tighten it. This is where an S3 bucket policy visualizer becomes essential. Paste the policy, get back plain-language warnings and a breakdown of what’s actually being granted. The mistakes I listed above become immediately obvious instead of hidden in JSON.

Templates help here too. If your baseline is a template that only grants what’s necessary (like a template for “read-only access to specific S3 bucket”), and you have to explicitly choose to widen permissions, you’re more likely to understand why you’re doing it. The best approach is having both a visualizer to catch existing problems and a policy generator with S3 templates to prevent new ones.

The Real Problem

S3 is old. Bucket policies predate a lot of AWS’s permission improvements. They’re powerful but they’re also a legacy mechanism. They work, but they require understanding their specific semantics.

Most engineers don’t have a mental model of bucket policies. They have a loose understanding of “how to let another account access my bucket” or “how to make something public,” and they copy-paste patterns. This is a Phase 2 problem: fixing individual bucket policies instead of building a least privilege maturity framework. You can’t just teach people ‘correct S3 policies’—you need organizational systems that make secure policies the default.”

Those patterns often work just enough to deploy but are subtly wrong in ways that matter for security.

This is why manual bucket policy writing is dangerous. It’s not because the syntax is hard (it’s not). It’s because understanding what the policy actually does requires holding multiple concepts in your head simultaneously, and that’s where mistakes happen.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top