"Least privilege" is one of those security principles that everyone agrees with and almost nobody implements in a way that actually restricts anything.
The principle is simple: give each component only the access it needs to do its job. Nothing more. Nothing less.
In practice, it rarely looks like that. It looks like one long-lived access key stuck in an environment variable. It looks like a service account with broad permissions because "it might need them later." It looks like the production database being writable by a service that only writes to it during deployment.
The problem is not that people are careless. It's that least privilege is framed as a technical policy when it's actually an operational decision. You cannot implement it from a spec. You implement it from the blast radius you are willing to carry when something goes wrong.
The usual framing is backwards
Most teams reach for least privilege the wrong way. They start by asking what the system is allowed to do, then hand out permissions accordingly. That puts the model into a permissive posture by default: anything that is not explicitly denied is allowed.
The right model starts from the other direction. You ask what damage a compromised component can cause, and then you constrain the system so that damage stays bounded. Anything that is not explicitly required is removed.
These two approaches look similar until they are not. The permissive model produces a system that works until the moment it breaks, at which point the permissions have already amplified the failure. The restrictive model produces a system that works only for the operations it was designed to support, and fails closed when something falls outside that design.
That difference is the point of least privilege: not preventing every attack, but ensuring the most common one causes acceptable damage.
Privilege is the blast radius
Permission sets do not tell you how much damage a component can do. They tell you what paths the component can reach. Those are related but not identical.
A service account with read-only access to a large table can exfiltrate everything in it. A read-only service account with a small result set cannot. The permission looks the same. The blast radius is completely different.
A deployment pipeline that can restart services can take the system offline. A deployment pipeline that can only restart its own services cannot. The permission looks similar. The operational risk diverges sharply.
Permission sets describe what a component can do. The blast radius describes what happens when that component is compromised, misconfigured, or simply doing its job in a way nobody expected.
Least privilege is useful when you measure it against the blast radius, because that is the metric that matters in production. A permission set that looks narrow on paper may have a wide blast radius through a single misconfiguration. A permission set that looks broad on paper may have a narrow blast radius because every path it opens terminates at a read-only endpoint.
This is the same lesson as "caching is a consistency decision" or observability being runtime architecture. A component's behavior is defined by its permissions, but its impact is defined by what those permissions can reach when the component behaves unexpectedly.
The operational reality of permission creep
Every system starts with narrow permissions. The service was written from scratch, deployed for the first time, and needs access to one API or one database table. The team has just enough to ship the feature.
Then something needs to change.
The service calls a new endpoint. A teammate suggests using the same service account because it already has most of what's needed. That decision is small and practical on its own. It becomes a pattern six months later, twelve months later, and the account that started with one purpose now has broad access across three services and two environments.
Each expansion is small enough to be reasonable. The accumulation is not.
The fix is not to stop expanding. It is to make every expansion an explicit decision.
Making privilege visible
A useful privilege audit starts by collecting every permission assignment and mapping it to the component it serves. Which service account owns which access? What endpoints can it reach? What databases can it read or write? Which operations can it perform?
From there, you ask the operational question: if this component were compromised or misbehaving, what damage could it do with these permissions?
That asks you to reason about failure modes, which is exactly the kind of reasoning that the most expensive bugs are decisions that people stop making once they get comfortable with the system working.
What least privilege looks like in practice
The concrete pattern that works best is a three-layer approach:
Scope the service to its own boundaries. A service that manages users should only have access to user data. It should not be able to read logs from another service, delete infrastructure provisioned by a different team, or write to a database owned by a third party. Scope is the easiest constraint to enforce and the one that most systems skip because "they share the same namespace."
Time-bound access for administrative operations. Deployment pipelines, backfill scripts, and emergency runbooks often need elevated permissions. That is fine, as long as those permissions expire. A token that lasts for five minutes and is automatically revoked is a different operational risk than a long-lived access key that never rotates.
Separate read and write paths. Many services only need read access to production data. They need write access to deploy, but not to operate. Treating read and write paths as separate permission sets lets you give a service the broad read access it needs for normal operation while restricting write access to deployment windows or explicit approval.
These three patterns reduce the blast radius of the most common failure modes without blocking normal operations. They are not revolutionary. They are the operational version of least privilege, and they are the version that actually works in production.
The continuous audit
Permissions change as systems evolve. The audit needs to evolve with them. That means tracking who owns each permission set, when it was granted, and whether the access it provides still matches what the component does today. An access key issued six months ago for a feature that no longer exists is not security hygiene. It is an open door nobody remembers opening.
The mapping from component to permission to blast radius should be visible to everyone who operates the system, not buried in a security spreadsheet. This is the same principle as "tool selection is an interface contract": the boundary between what a component can do and what it should be allowed to do needs a clear owner, a testable contract, and visible evidence that the boundary holds.
The question
Most teams ask whether they have implemented least privilege. The answer is usually "sort of, we followed the checklist." That is not a useful question.
The useful question is simpler: if this service were compromised right now, what could it do with its permissions, and would I be satisfied?
If the answer is "I don't know," you do not have least privilege. You have a hope that the permissions are narrow enough, and hope is not an operational strategy.
The one-line version
Privilege isn't a permission set — it's the blast radius you are willing to carry when something goes wrong, and that is always an operational decision.