The question this answers
What is the blast radius if this identity is compromised?
A worker needs to read one object. The policy it was given grants administrator. Nothing about the application behaves differently, which is exactly why nobody noticed — and why the question has to be asked deliberately.
A concrete, statable bound on any single compromise: which resources are reachable, which actions are possible, whether the damage is reversible, and how long containment takes.
Need one object, granted the account
Put the two side by side and the argument makes itself. The worker needs storage:GetObject on one prefix of one bucket. Its policy says * on *. The application is bit-for-bit identical under both — same latency, same success rate, same logs. The only difference appears on the day the credential leaks, and then the difference is the entire company.
The reason this happens is not carelessness, it is friction asymmetry. Granting administrator takes ten seconds and always works. Determining the four exact actions the SDK will call takes forty minutes, some of it reading provider documentation, and the first deployment fails on something you missed. Every incentive in the moment points one direction, and the cost lands months later on someone else.
So least privilege has to be measured by an outcome that is uncomfortable enough to be worth the forty minutes. That outcome is the blast radius: if this credential were posted publicly right now, what would we be doing for the next twelve hours? For the narrow policy the answer is "revoke it and check the access logs for one prefix". For * on * the answer is "assume total compromise, rotate everything, notify the regulator, and hope the backups are in an account the attacker could not reach".
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
// blast radius: read every bucket, read every secret, drop every database,
// delete every backup, create new identities, disable the audit trail,
// and spin up compute in every region on your account.{
"Effect": "Allow",
"Action": "storage:GetObject",
"Resource": "user-media/uploads/*",
"Condition": { "StringEquals": { "aws:SourceVpc": "vpc-app" } }
}
// blast radius: read user uploads, from inside the application network only.
// Serious. Bounded. Containable in minutes by revoking one role.Both run the application identically. They differ only in what happens after a compromise — which is the only dimension least privilege is measured on.
The blast-radius question, asked properly
Asking "is this least privilege?" produces the answer "yes, roughly", every time. Asking the blast-radius question produces a list, and a list can be argued with. Four sub-questions make it concrete.
What can it read? Name the data classes, not the services. "Customer PII and payment tokens" is an answer; "S3" is not.
What can it destroy, and is that reversible? Deletion with versioning and a separate backup account is a bad day. Deletion of the backups too is an extinction event. This is the question that decides whether Backup Strategy holds under an attacker rather than under a disk failure.
Can it escalate? Any permission to create or modify identities and policies is a permission to grant itself everything else. iam:* is not one permission among many; it is the permission to obtain all the others, and it belongs behind a guardrail deny for every workload identity.
Can it hide? Permission to modify or delete the audit trail turns a contained incident into an unknowable one. It is the difference between "we know what they touched" and "we assume they touched everything".
Run those four against the identities you have, and the answers sort themselves into a ladder. Most workloads should sit on the bottom two rungs, and the ones that do not should be a short, deliberate, named list.
| Rung | Grant | Ten minutes after compromise | Containment | Reversible? |
|---|---|---|---|---|
| 1 | Read one prefix, network-conditioned | One data class exposed, only from inside the network | Revoke one role; read one access log | Yes — nothing was changed |
| 2 | Read + write one resource | That resource poisoned or exfiltrated | Revoke, restore that object from a version | Yes, if versioning is on |
| 3 | Delete on a data store | Data destroyed | Restore from backup — if the backup is out of reach | Only with a tested restore |
| 4 | Read every secret | Every downstream credential is now suspect | Rotate everything, everywhere, under time pressure | Days of work |
| 5 | Identity and policy administration | Attacker grants themselves anything, persists, disables logging | Assume total compromise of the account | Rebuild in a clean account |
The over-privileged worker, written as a panel
The panel makes the gap unarguable in a way prose does not. Two needed lines; one allowed line that covers every action on every resource in the account. This exact shape is what an access review is for, and it is depressingly common in accounts that have never had one.
The practical path out is not a rewrite of everything at once. It is: guardrail denies first — identity administration, audit-trail modification, backup deletion — applied account-wide, because those three block escalation, concealment and irreversibility regardless of what any individual policy says. Then narrow the identities with the widest grants, using the audit trail to see what they have actually used. Then keep the granted-versus-used report running, because the ratchet only turns one way unless someone turns it back.
Security Engineering teaches least privilege as a principle. What this domain adds is the measurement: privilege is only meaningfully "least" relative to a stated blast radius, and a policy review that does not produce that statement has not finished.
- * on *
- storage:GetObject on user-media/uploads/*
Blast radius: Read every bucket and every secret; connect to every database; delete production data and the backups alongside it; create new administrator identities to persist; disable the audit trail so none of it can be reconstructed. Containment is not "revoke a role" — it is "assume the account is lost and rebuild". The workload needed one read.
Key points
- Least privilege is measured by blast radius, not by how tidy the policy looks.
- Ask it concretely: what can it read, what can it destroy irreversibly, can it escalate, can it hide?
- The application behaves identically under a narrow policy and an administrator policy — which is why the gap survives for years.
- Identity-administration permissions are not one permission among many; they are the permission to obtain all the others.
- Audit-trail modification rights turn a contained incident into an unknowable one, and belong behind an account-wide deny.
- Fix guardrails first, then narrow the widest identities using what the audit trail says they actually used.
The loop, answered
Every field is required, which is why no lesson here can recommend something without saying what it costs and what simpler thing to consider first.
- • Enumerate every identity, including the workload identities nobody remembers creating.
- • For each, list what the workload genuinely does — from the code, and from 90 days of audit-trail evidence.
- • Compare against what the policy grants, and place the identity on the blast-radius ladder.
- • Apply account-wide guardrail denies for escalation, concealment and backup deletion, which no team policy can override.
- • Narrow from the widest grant downward, and re-run the comparison on a schedule because permissions only accumulate.
- • Own the periodic access review, with a named owner and a date. Without both it does not happen.
- • Own the granted-versus-used report; it converts an argument about principles into a list of specific grants to delete.
- • Own the emergency-widening process *including the revert*, because the widening is what gets done and the revert is what gets forgotten.
- • Own the guardrail set, and keep it small enough that people remember what is in it.
- • Own the blast-radius statement for every privileged identity, written down where an incident responder can find it at 03:00.
- • A leaked credential with administrator rights, where containment means rebuilding the account rather than revoking a role.
- • Backups deleted by the same identity that deleted production, because both were covered by one wildcard.
- • An attacker creating a second identity to persist, so revoking the original credential changes nothing.
- • The audit trail disabled early in an incident, leaving no way to bound what was touched.
- • A narrow policy that breaks a legitimate new code path in production — the correct behaviour of the control, and the reason teams abandon it.
- • An access review that produced a spreadsheet and no revocations.
- • The number of identities grows faster than review capacity, so manual review stops working and evidence-driven narrowing has to replace it.
- • Guardrails scale well because they are few and apply everywhere; per-identity policies scale badly and must be generated.
- • Blast radius compounds across identities: two moderately-privileged identities that can reach each other may be jointly equivalent to an administrator.
- • What runs out is not policy capacity but the ability to state, quickly, what any given identity can do.
- • This is the control that decides whether a compromise is an incident or a breach.
- • Guardrail denies on identity administration, audit-trail modification and backup deletion are worth more than any amount of per-policy tuning.
- • Keep backups in a separate account or with separate credentials, so the identity that can destroy production cannot destroy the recovery path.
- • Condition privileged actions on network origin and require multi-factor for human privileged access — see Human vs Workload Identity.
- • Time-bound elevation beats standing privilege: a role assumable for an hour with an alert on every use is a much smaller target than a permanent grant.
- • Free in infrastructure terms; the expenditure is engineering time, concentrated at design and at review.
- • The avoided cost is an incident whose scope is unbounded, and unbounded scope is what makes incidents expensive.
- • A real, honest cost: deployments fail more often on missing permissions, and that friction is paid by the team every week.
- • Audit-log retention and analysis for evidence-driven narrowing is a recurring line item worth budgeting deliberately.
- • Permissions granted but unused over 90 days, per identity — the highest-signal report in this module.
- • Every use of a privileged identity, alerted rather than logged, because it should be rare.
- • Identity and policy modification events, which are both an escalation signal and a drift signal.
- • Access-denied errors, which show where policies are tight and where someone is probing.
- • The signal that lies: application health. It is perfect under both policies, right up until the credential leaks.
- • Provider-managed predefined roles: broader than ideal, reviewed by the provider, and enormously better than a wildcard written during an incident.
- • Guardrails only, for a very small team: three account-wide denies plus default roles bounds the worst outcomes at a fraction of the effort of full least privilege.
- • Time-bound elevation instead of narrowing: keep a broad role but make it assumable for an hour, with approval and an alert. Sometimes more practical and nearly as effective.
- • Separate accounts or projects per environment, which bounds blast radius structurally without requiring any policy to be perfect — often the highest-return move available.
- • Buys a bounded, statable incident; costs deployment friction every time a workload legitimately needs something new.
- • Guardrails buy irreversibility in the safe direction; they cost the ability to make a fast exception at 03:00, which will eventually hurt.
- • Evidence-driven narrowing buys accuracy; it costs audit-log retention and query spend, and it can only see what has already run.
- • Account separation buys structural isolation; it costs cross-account plumbing, more identities and a more complex deployment path.
Blast radius of a compromised identity
identity ci-deploy@build (workload, static key, no expiry) policy Allow GetObject on uploads/*
What people believe, and what is true
It is an internal service, so a broad policy is acceptable.
Internal is a network property. A compromised internal service holds the same credential an external one would, and uses it just as effectively.
We will tighten the permissions after launch.
Nothing forces it and no user complains about excess privilege. Tighten before launch, or schedule it with an owner and a date.
Least privilege means the smallest possible policy.
It means the smallest policy that lets the workload do its job. A policy so tight that engineers routinely bypass it has made things worse.