I have never been in a conversation where somebody defended wildcard IAM policies on principle. Everybody agrees. The policies are still there.
That tells you the problem is not a knowledge problem or a values problem. It is a scheduling problem, and it should be attacked as one.
The mechanics of how it happens
A workload needs to read from an S3 bucket. The engineer is three days behind. They attach a policy allowing s3:* on *, confirm the workload runs, and move on. A ticket is created to narrow it later.
That ticket is now competing for attention against features. It will lose every prioritisation conversation it enters, forever, because narrowing a working policy has no visible outcome. Nothing gets faster. No user notices. The only observable result is risk that did not materialise, which is indistinguishable from having done nothing.
Meanwhile the broad policy is load-bearing. Six months on, three workloads share the role, nobody knows which actions each one needs, and narrowing it is now a research project with a real chance of breaking production.
The window in which this was cheap closed on day one.
Do it during the build, not after
The only approach that held during the Adex apprenticeship was to write the narrow policy as part of the initial build. Not as a follow-up. Not as a hardening pass. Part of "the workload is done".
The argument that wins with a delivery lead is not a security argument. It is that narrowing later costs more and carries deployment risk, whereas narrowing now costs an hour and carries none. That is a scheduling argument, and scheduling arguments are the ones that get accommodated.
Start from deny and let the workload tell you
Guessing at the permission set produces either a wildcard or a broken workload. Derive it instead.
Start with a role that grants nothing. Run the workload in a non-production environment. It fails, and the failure names the action it wanted. Add that action, scoped to the specific resource. Repeat until it stops failing.
CloudTrail is the record. Rather than reasoning about what a service might need, you read what it actually called. This is slower than typing s3:* and it is not slow in absolute terms: most workloads settle in well under an hour, and you finish holding a policy that documents the workload.
There is a real failure mode worth naming. Rare code paths do not run during this exercise. An error handler that writes to a dead-letter queue once a month will not appear, and it will fail in production later. The mitigation is to read the code for its external calls rather than relying purely on observed traffic, and to keep the non-production run going long enough to cover scheduled work.
Resource scoping is where the value is
Narrowing actions gets the attention. Narrowing resources matters more.
s3:GetObject on * is a small improvement over s3:* on *, and it still permits reading every object in every bucket in the account. s3:GetObject on arn:aws:s3:::specific-bucket/prefix/* is a different security posture entirely.
Actions describe what a workload does. Resources describe how far a compromise reaches. If you only have time for one, scope the resources.
No long-lived keys
Adjacent and non-negotiable: compute gets instance roles, not access keys.
A long-lived key with no expiry, in an environment variable, on a host, is a different class of incident from a temporary credential. It survives the instance, it survives the deployment, it survives the engineer who created it leaving, and it will be found in a repository eventually.
Instance roles remove the artefact. There is no key to leak.
The part that is genuinely cultural
Everything above is mechanical, and mechanics only get you so far. Somebody has to be willing to say that a workload is not finished when it works.
That is easier to hold when it is written down as a definition of done rather than defended per-workload by whoever cares most. Put it in the checklist and it becomes the default. Leave it to individual judgement and it becomes a thing the most junior person on the team loses an argument about on a Friday afternoon.