Testing Policies with the Simulator
Before you attach a new policy to a real user or role, you want to know one thing: will it actually allow the actions you intend, and will it block the ones you don’t? The IAM (Identity and Access Management, the AWS service that controls who can do what) Policy Simulator answers that question without making a single real API call. You give it a principal (a user, group, or role) and a list of actions, and it tells you “allowed” or “denied” for each one, plus which statement made the decision. This page shows you how to use the simulator in both the console and the CLI, how to read its results, and the important cases where it can quietly disagree with reality.
Why test policies at all
IAM policy evaluation is more subtle than it looks. A single request is judged against several layers at once: the identity-based policies on the principal, any permission boundary, Service Control Policies (SCPs, account-wide guardrails set by an organization), and resource-based policies. An explicit Deny anywhere wins. It is genuinely hard to predict the outcome by reading JSON.
The naive way to test is to attach the policy and try the real action. That is risky: you might grant too much (a security hole) or break a running workload (an outage). The simulator lets you check the outcome safely, in seconds, before anything is live.
When to use this: any time you write or change a policy that touches production, money, or sensitive data. When NOT to over-think it: in a throwaway sandbox where a quick “Access Denied” trial-and-error is harmless, the simulator is optional.
What the simulator evaluates
The simulator combines the principal’s attached managed and inline policies with any permission boundary, and (optionally) SCPs and resource policies you supply, then runs the same evaluation logic AWS uses for a live request. For each action you ask about, it returns one of:
- allowed — some statement allows it and nothing denies it.
- implicitDeny — nothing allowed it (the default when no policy grants the action).
- explicitDeny — a statement with
"Effect": "Deny"blocked it (this always wins).
It also tells you the matched statement — the exact policy and statement index that produced the decision. That is the most useful part: it turns “why is this denied?” into a precise answer.
Using the console simulator
The console version is the friendliest way to explore.
- Open the IAM Policy Simulator at
https://policysim.aws.amazon.com/. - In the Users, Groups, and Roles panel on the left, pick the principal you want to test (for example the role
app-worker-role). - Its attached policies load automatically. You can also paste a draft policy under Policies to test something not yet attached.
- Under Action Settings and Results, choose a service (e.g. Amazon S3) and the actions to test (e.g.
GetObject,DeleteObject). - (Optional) Expand a row and set the Resource ARN (Amazon Resource Name, the unique ID of a resource, e.g.
arn:aws:s3:::app-uploads/report.csv) and any condition keys like the source IP. - Click Run Simulation. Each action shows allowed or denied, and clicking it reveals the matched statement.
Using the CLI
For automation and CI pipelines, use aws iam simulate-principal-policy. It takes the principal’s ARN and the actions to test.
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/app-worker-role \
--action-names s3:GetObject s3:DeleteObject \
--resource-arns arn:aws:s3:::app-uploads/report.csv
Output:
{
"EvaluationResults": [
{
"EvalActionName": "s3:GetObject",
"EvalResourceName": "arn:aws:s3:::app-uploads/report.csv",
"EvalDecision": "allowed",
"MatchedStatements": [
{
"SourcePolicyId": "AppWorkerS3Read",
"StartPosition": { "Line": 3, "Column": 17 },
"EndPosition": { "Line": 9, "Column": 10 }
}
]
},
{
"EvalActionName": "s3:DeleteObject",
"EvalResourceName": "arn:aws:s3:::app-uploads/report.csv",
"EvalDecision": "implicitDeny",
"MatchedStatements": []
}
]
}
Here GetObject is allowed by the statement named AppWorkerS3Read, while DeleteObject is an implicitDeny — no statement granted it, so it is blocked by default. Empty MatchedStatements is the signature of an implicit deny.
Testing a draft policy that isn’t attached yet
To check a policy before attaching it, simulate the JSON directly with simulate-custom-policy:
aws iam simulate-custom-policy \
--policy-input-list file://draft-policy.json \
--action-names s3:PutObject \
--resource-arns arn:aws:s3:::app-uploads/new.csv
Output:
{
"EvaluationResults": [
{
"EvalActionName": "s3:PutObject",
"EvalResourceName": "arn:aws:s3:::app-uploads/new.csv",
"EvalDecision": "allowed",
"MatchedStatements": [ { "SourcePolicyId": "PolicyInputList.1" } ]
}
]
}
Simulating condition keys
Many policies allow an action only under conditions (for example, only from a trusted IP range). Pass those with --context-entries so the simulator evaluates them:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/app-worker-role \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::app-uploads/report.csv \
--context-entries ContextKeyName=aws:SourceIp,ContextKeyType=ip,ContextKeyValues=203.0.113.10
If the policy requires that IP and you supply a different one, the result flips from allowed to implicitDeny, showing you the condition is working.
The big gotcha: the simulator is not reality
This is the single most important thing to remember. The simulator is a model of policy evaluation, and the model is incomplete in a few important ways:
| Scenario | Does the simulator handle it well? |
|---|---|
| Identity-based policies on the principal | Yes — this is its core strength |
| Permission boundaries | Yes, when the principal has one attached |
| Condition keys you explicitly supply | Yes, if you pass them via context entries |
| SCPs (organization guardrails) | Partially — it does not pull live SCPs; results can differ |
| Resource-based policies (e.g. S3 bucket policy, KMS key policy) | No — it does not evaluate them automatically |
| Cross-account access | Often wrong — both accounts’ policies must allow, which the simulator can’t fully model |
Because of this, a cross-account request, or an action governed by a bucket policy or a KMS (Key Management Service) key policy, can show “allowed” in the simulator but fail in real life — or the reverse. Condition keys that depend on live request context (like aws:PrincipalOrgID or tag values) can also evaluate differently.
Warning: treat the simulator as a fast first check, not a guarantee. For anything cross-account, resource-policy-gated, or security-critical, also test the real action with a low-stakes call (for example a
GetObjecton a throwaway key) in a staging account before you trust it in production.
Best practices
- Run the simulator on every new or changed policy before attaching it to a production principal.
- Read the matched statement to understand why a decision was made, not just whether it was allowed.
- Use
simulate-custom-policyto vet draft JSON in code review or CI, so bad policies never reach the account. - Always pass relevant condition keys with
--context-entries; without them, conditional rules won’t be evaluated. - Never rely on the simulator alone for cross-account, resource-based-policy, or SCP-affected paths — verify those with a real low-risk request.
- Combine the simulator with least-privilege policy generation so you both tighten and confirm permissions.