A real before/after case study: scoping down an overly permissive IAM role to the exact permissions it actually needs — then proving it still works.
This uses the IAM role from my serverless daily report pipeline project as the real-world example.
My Lambda function's execution role started with the AWS-managed policy AmazonS3FullAccess attached — the fastest way to get the pipeline working. But "fastest" and "correct" aren't the same thing.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:*", "s3-object-lambda:*"],
"Resource": "*"
}
]
}See the full file: policies/before-overly-permissive.json
"Resource": "*" means this Lambda function could act on every S3 bucket in the entire AWS account — not just the one it was built for. It could read, write, delete, or even change permissions on buckets belonging to completely unrelated projects.
Looking at what the function's code actually does with S3, there's exactly one interaction:
s3.put_object(
Bucket=bucket_name,
Key=file_name,
Body=report
)The function only ever:
- Writes — never reads, lists, or deletes
- Touches one bucket — not the whole account
- Writes to one folder path within that bucket —
weather-reports/
Full breakdown: notes/permission-breakdown.md
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::daily-weather-demo/weather-reports/*"
}
]
}See the full file: policies/after-least-privilege.json
This restricts the role to a single action (s3:PutObject) on a single resource path — nothing else.
Rather than assuming the scoped-down policy would work, I tested it in two stages:
Stage 1 — with both policies attached, confirming the new policy doesn't break anything:
Stage 2 — after removing AmazonS3FullAccess entirely, to prove the broad access was never actually necessary:
The function ran successfully both times. The second result is the real proof: the broad permissions were never required in the first place.
If this function were ever compromised — through a vulnerable dependency, a leaked credential, or a misconfiguration — the difference in blast radius is significant:
| Before | After | |
|---|---|---|
| Buckets affected | Every bucket in the account | One bucket |
| Actions possible | Read, write, delete, change permissions | Write only |
| Scope within bucket | Entire bucket | One folder path |
This is the core idea behind least privilege: a role should only ever be able to do the exact thing it needs to do. It doesn't eliminate every risk, but it shrinks the potential damage dramatically.
- Reading and interpreting IAM policy JSON
- Identifying the gap between granted permissions and actual usage
- Writing scoped-down, resource-specific IAM policies
- Verifying changes empirically rather than assuming correctness
- Understanding the real-world security impact of "quick fix" permissions like
AmazonS3FullAccess
This case study builds directly on my Serverless Daily Report Pipeline project, which uses this exact IAM role.





