I'm reviewing internal cloud accounts and keep finding roles with broad permissions like s3:* or AdministratorAccess, usually added to get past deployment or debugging problems. I understand that deadlines and repeated permission errors can make this tempting, but temporary wildcards often become permanent.
What makes this especially frustrating is that I recently reviewed a web portal delivered by an external team where every Lambda execution role was scoped to the exact DynamoDB resources and S3 prefixes it needed. Meanwhile, some of our own senior engineers still grant internal tools excessive EC2 access "just in case."
How are other teams enforcing least privilege in a way that reduces risk without making engineers feel blocked or resentful?
5 Answers
Treat environments differently, but don’t let development shortcuts reach production. A development account can have broader access with an explicitly accepted blast radius, while staging and production should use production-like, tightly scoped roles. Enforce that boundary with policy validation, permission boundaries, and organization-level controls such as SCPs.
Use observed access as a starting point, but don’t blindly turn logs into the final policy. Access Analyzer and CloudTrail can reveal the APIs a role actually used, which is useful for narrowing permissions. However, they can miss exception paths, indirect permissions, KMS dependencies, and actions that weren’t exercised during testing. Generated policies also need environment-aware resource patterns rather than hard-coded development ARNs.
Exactly. A policy generated from development activity may contain dev-specific bucket names and omit rarely used error-handling paths, so it still needs human review and proper parameterization.
Assign clear ownership and make managers accountable for excessive access, but avoid creating a central approval bottleneck. Security teams can provide templates, review rules, and organization-wide limits, while application teams own the narrowly scoped permissions for their services. Automated checks should reject unrestricted wildcards or administrative policies in protected environments, with documented exceptions for genuinely necessary cases.
A lot of this is an incentive and developer-experience problem, not simply laziness. When every permission change requires several slow deployment cycles and opaque error messages, people under deadline pressure will choose the wildcard that works. Make the secure path fast: use infrastructure as code, provide reusable policy patterns, add automated checks in CI, and make permission failures easier to diagnose.
A pre-deployment check that could identify likely permission failures would get much better buy-in than asking developers to guess action names and ARNs manually.
The tradeoff is real: a small team with a tight deadline will usually optimize for delivery unless security tooling is built into the workflow.
AWS IAM is genuinely difficult to get right. Service documentation often leaves out required actions, denial messages can be unhelpful, policy size limits create pressure to combine permissions, and some operations involve indirect identities or dependent services. That complexity explains why teams need guardrails and standard modules instead of expecting every developer to design policies from scratch.
Reusable IaC grants help a lot. For example, a construct that grants a function read access to a specific queue or table is safer than having every team hand-write a policy.

Staging needs to match production closely, otherwise permission problems often appear only during the final release. Automation is what makes that practical.