Reducing a policy to the permissions one job uses can break another job sharing the same role. Start with the complete set of declared workload requirements, then test both the allowed work and the access you want to remove.
Worked example available now. Copy the policies and download the tests below. Cotton’s hosted reviewer and ChatGPT plugin are not yet published; see reviewer availability.
Authored synthetic example. Every policy, resource name, request test, and expected result on this page is fictional. The displayed outcomes are not observed AWS results. No policy is deployed and no application is tested by reading or copying this example.
The current policy permits more than the jobs need
The current identity policy grants s3:* on the synthetic reporting bucket and all its objects. Within this supplied policy, that covers the required reads and writes, but also the unrelated read and object deletion we want denied.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SharedReportingAccess",
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::cotton-synthetic-reporting",
"arn:aws:s3:::cotton-synthetic-reporting/*"
]
}
]
}Label this document Current when comparing. Its broad grant is the baseline to narrow, not a recommended starting policy.
Write down the requests that define success
The tests below distinguish a policy that preserves the declared work from one that merely removes permissions. The reporting-role report-read test represents the export job’s read of a generated report. Labels describe the fixture; the IAM evaluator sees the action and resource, not separate workload identities.
{
"schemaVersion": "1.0.0",
"policyKind": "iam_identity",
"permissionTests": [
{
"label": "Reporting reads inputs",
"action": "s3:GetObject",
"resource": "arn:aws:s3:::cotton-synthetic-reporting/inputs/source.csv",
"expectedBehavior": "allow"
},
{
"label": "Reporting writes reports",
"action": "s3:PutObject",
"resource": "arn:aws:s3:::cotton-synthetic-reporting/reports/daily.csv",
"expectedBehavior": "allow"
},
{
"label": "Reporting reads reports",
"action": "s3:GetObject",
"resource": "arn:aws:s3:::cotton-synthetic-reporting/reports/daily.csv",
"expectedBehavior": "allow"
},
{
"label": "Exporter writes exports",
"action": "s3:PutObject",
"resource": "arn:aws:s3:::cotton-synthetic-reporting/exports/delivery.csv",
"expectedBehavior": "allow"
},
{
"label": "Unrelated reads stay denied",
"action": "s3:GetObject",
"resource": "arn:aws:s3:::cotton-synthetic-reporting/unrelated/private.csv",
"expectedBehavior": "deny"
},
{
"label": "Object deletion stays denied",
"action": "s3:DeleteObject",
"resource": "arn:aws:s3:::cotton-synthetic-reporting/reports/daily.csv",
"expectedBehavior": "deny"
}
]
}
Download permission tests (JSON)
This file records the six example requests in Cotton AWS Policy Review’s requirements format, with schema version 1.0.0 and policy kind iam_identity. Read or adapt it for your review; it is not an IAM policy or a deployment template.
The first proposal forgets the export write
This draft replaces the broad grant with input reads and report reads and writes. It leaves out s3:PutObject on exports/*. A narrower action list alone would miss the service requirement.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadInputs",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::cotton-synthetic-reporting/inputs/*"
},
{
"Sid": "ReportsOutput",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::cotton-synthetic-reporting/reports/*"
}
]
}Compare this draft with the current policy and all six request tests. The authored expectation is that the export write becomes implicitly denied within the supplied policy. The actual application effect still requires a live service check; the example does not claim an observed outage.
The corrected draft preserves the declared union
Add a bounded export-write statement. The corrected draft keeps input reads, report reads and writes, and export writes. It does not restore the broad s3:* permission.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadInputs",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::cotton-synthetic-reporting/inputs/*"
},
{
"Sid": "ReportsOutput",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::cotton-synthetic-reporting/reports/*"
},
{
"Sid": "ExportOutput",
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::cotton-synthetic-reporting/exports/*"
}
]
}The unrelated-read and deletion requests have no matching Allow in this draft. They are expected to be implicitly denied when evaluating this supplied policy alone; no explicit Deny statement has been added. Another applicable grant in a real environment could change effective access.
Compare expectations before running the checks
Scroll across to compare all columns.
| Request | Required | Current | First proposal | Corrected draft |
|---|---|---|---|---|
| Read input object | Allow | Allow | Allow | Allow |
| Write report object | Allow | Allow | Allow | Allow |
| Read report object | Allow | Allow | Allow | Allow |
| Write export object | Allow | Allow | Implicit deny: requirement missed | Allow |
| Read unrelated object | Deny | Allow: excessive access | Implicit deny | Implicit deny |
| Delete report object | Deny | Allow: excessive access | Implicit deny | Implicit deny |
The corrected draft satisfies the six authored expectations in this example. That does not prove a globally minimal policy, complete application coverage, workload isolation, or effective access in an AWS account.
Use Cotton’s IAM policy review workflow to keep validation findings, comparisons, request tests, and unresolved checks together. When running the checks with AWS, preserve the actual returned evidence and any incomplete checks.
Turn the example into an accountable change review
- Confirm the workload inventory. Find every job sharing the role, including infrequent export, recovery, and maintenance work.
- Expand the requirements. Add the actual actions, resources, condition values, and negative tests the service owners need. Observed activity can help; an observation window is not a complete specification.
- Review the full access context. Account for other applicable identity and resource policies, boundaries, organization and session controls, and encryption permissions. A supplied-policy simulation is a bounded check.
- Approve and deploy the exact change. Use your authorized change process, confirm current state, and prepare a separately governed recovery decision.
- Verify security and service outcomes. Confirm the unwanted access is denied and both workloads complete their required tasks. Missing evidence or a failed job keeps the remediation open.
If a job returns AccessDenied after deployment, retain the caller, action, resource, error context, and policy revisions. Compare the change with the requirement, then investigate the applicable controls. Restoring broad permissions without review can restore the original exposure.
The AccessDenied review questions and remediation evaluation checklist help carry the policy review into the wider workflow.
Sources and scope
Reviewed 7 October 2026. Cotton authored the example and expected outcomes. The AWS sources below explain policy evaluation, simulator limitations, and how observed activity can inform policy generation. They do not report a test of these fixtures.
DESIGN PARTNER PROGRAM
Bring one permission-change review to Cotton
We’re building the workflow from a finding to a reviewed change and verified outcome. Share the decision your team needs to make, the workload access it must preserve, and the evidence your reviewers require.
Explore a design partnership