Here’s a statement that’ll annoy some people: most organizations that think they’re practicing least privilege aren’t actually doing it.
The path forward isn’t better policy documentation—it’s building a real system. That means having tools, frameworks, and an AWS least privilege policy builder that makes the right choice the easy choice. But before that, you need to understand where your organization actually is on the maturity spectrum.
They’re doing “less privilege than admin.” That’s not the same thing.
What Actually Is Least Privilege?
The principle is simple in theory: a principal should have the minimum permissions necessary to do its job. No extra permissions. No “just in case” access. No “for future proofing.”
In practice, this is a spectrum. Complete least privilege would mean a Lambda function could only call the exact AWS API calls it needs, on the exact resources it accesses, during the specific hours it runs. That’s auditable, but it’s not how people operate at scale.
Real least privilege is a balance. You need to be specific enough that a breach is contained, but practical enough that you’re not updating policies constantly.
The problem is knowing where that balance is. Most organizations either:
- Too permissive: “This service needs to access S3, so it gets
s3:*on all buckets.” This is lazy and dangerous. - Too restrictive: “This service needs to access one bucket, but we’re going to give it GetObject on that specific bucket and nothing else, and we’re going to restrict to specific resource paths.” This is secure but it’s also fragile. One new requirement breaks everything.
- Inconsistent: Different services have wildly different permission scopes because each was written by a different person with a different understanding of what’s appropriate.
The engineers I know who get this right don’t have a perfect system. They have a framework: they reason about blast radius.
If this role is compromised, what damage can it do? For a read-only role accessing non-critical data, the damage is disclosure. For a write-capable role accessing production data, the damage is much worse. For an admin role, the damage is catastrophic.
The permissions you grant should be proportional to that blast radius.
Where Organizations Actually Go Wrong
1. Services That Are “Just” Backends
You have a backend API that reads from a database and writes logs. The engineer says: “This needs database access and CloudWatch access, so I’ll give it dynamodb:* and logs:*.”
Their reasoning: it’s internal infrastructure, it’s behind authentication, and it’s not handling sensitive data (oh, but it actually is—it’s accessing financial records).
What’s really needed: read access to one table, write access to one log group. The difference is huge.
But the engineer didn’t write it that way because overspecifying permissions took more effort. Writing a policy is easy. Writing a targeted policy requires thinking about what the service actually needs.
2. Development Accounts That Never Get Cleaned Up
Dev AWS accounts start with loose permissions. That makes sense—developers need to be able to experiment. But these permissions never get reviewed and tightened when the resources move to production.
An EC2 instance in dev has a role that allows ec2:*, s3:*, and iam:*. This was fine in dev. When the workload moves to production and that role is reused, suddenly you have a production compute instance that can create new IAM users. The S3 wildcard is particularly dangerous—you’re giving production compute unrestricted access to potentially every bucket. This is the same over-permissioning pattern that shows up in S3 bucket policies, where people grant s3:* because it’s faster than specifying exact actions. If that instance is compromised, the attacker can create new admin accounts.
This happens constantly because there’s no forcing function to review permission scope when moving environments.
3. “Break Glass” Roles That Become Permanent
A team needs to investigate a production issue urgently. They create a role with broad permissions: logs:*, ec2:DescribeInstances, s3:ListBucket on all buckets, etc. This is the emergency access role.
The issue gets resolved. The role stays. Three years later, that role is still being used for routine operations. An emergency measure becomes the standard operating procedure.
Now imagine that role has been shared (informally) across the team. Multiple people have credentials. One person leaves, takes the credentials, and uses them six months later for something nefarious. You’ve got a broad-access credential floating out in the wild.
4. The “Future-Proofing” Fallacy
Someone writes: “This Lambda function reads from S3, but we might add more buckets later, so I’ll use * as the resource.” This ‘future-proofing’ mindset creates automatic access to resources you don’t know about yet. If you’re not careful, this becomes the bucket policy wildcard trap where one policy automatically applies to every new bucket created.
The problem: they just created a permission that will automatically apply to future resources they don’t know about yet. When a new bucket is created, this Lambda automatically has access to it. That’s not a feature; that’s a security debt.
Better approach: the policy is specific to known buckets. When a new bucket is added, the policy is explicitly updated. This creates friction, which is the point. The friction is the security.
What Least Privilege Actually Requires
1. Tooling That Shows What Permissions Cost You
When you grant s3:*, you should immediately see a list of what that actually includes. There are hundreds of S3 actions. Most engineers don’t know half of them. If the tool said “This includes DeleteBucket, DeleteBucketPolicy, PutBucketPolicy, etc.”, more people would realize they’re granting too much. An AWS IAM policy visualizer that breaks down the true scope of your permissions changes behavior. So does an IAM policy generator with templates for common scenarios—read-only S3 access, least privilege database roles, service-to-service permissions. When the safe path is also the easy path, people take it.
2. A Decision Framework
Your team should have a standard: “For a read-only service role, we grant specific read actions on specific resources. For a write-capable service role, we grant specific read and write actions on specific resources. For a managed service like Lambda, we use service-specific policies from AWS where available.”
This doesn’t have to be complex. But it has to exist, and it has to be followed.
3. Regular Audits
Someone should be looking at active policies quarterly. Not for compliance theater—genuinely looking. Are there permissions that aren’t being used? Are there roles that have more access than they should? Are there patterns (like excessive wildcard use) that suggest lazy permission creep?
This doesn’t scale without automation, but most organizations don’t even try.
4. Clear Resource Ownership
The person who wrote the policy should be reachable. Not always possible, but important. If you find a suspicious policy, you need to be able to ask “was this intentional?”
The Hard Truth
Least privilege sounds like a binary: either you’re doing it or you’re not. It’s actually a journey, and most organizations are somewhere in the middle.
The journey looks like:
- Phase 1: Permissions are wildly overpermissioned. Admin roles are used for everything. Blast radius is massive.
- Phase 2: You adopt IAM best practices. Service roles are restricted to specific AWS services. Permissions are tighter. Blast radius decreases.
- Phase 3: You get granular. Specific actions, specific resources. Policies are verbose but targeted. Blast radius is small.
- Phase 4 (rare): You’re continually reducing blast radius. Old permissions are reviewed and tightened. Policies are automated. Blast radius is proportional to actual need.
Most organizations are somewhere between Phase 2 and 3, thinking they’re at Phase 4.
The companies that are actually at Phase 4 have two things: they’ve automated permission auditing, and they’ve made permission writing easy enough that engineers don’t cut corners.
If writing a specific permission takes five minutes and writing a wildcard permission takes thirty seconds, people will write wildcards. Then you’re stuck in Phase 2 forever.
The way forward isn’t better policy. It’s better tools. Tools that let you paste a policy and understand it in seconds (visualizer). Tools that give you proven templates so you’re not starting from blank (policy generator). Tools that output both JSON and Terraform so your infrastructure-as-code is actually compliant. When the right thing is also the easy thing, behavior changes. Your blast radius shrinks. Your security improves without requiring more process overhead.
