AWS IAM Policy Validator & Analyzer

Paste an AWS IAM policy, bucket policy or role trust policy. First: is it valid JSON, and if not, exactly which character is wrong. Then: how much does it really grant, including the escalation paths that do not look like wildcards.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Validate & analyze. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

Action * on Resource *

Full administrative access, which is what a policy written to unblock a deploy usually becomes

{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]}

A service wildcard

s3:* looks scoped because the resource is, but it includes DeleteBucket and PutBucketPolicy

{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::my-bucket/*"}]}

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Granting s3:* because the resource is scoped

    The service wildcard includes DeleteBucket, PutBucketPolicy and PutBucketAcl. Scoping the resource does not stop the bucket being made public.

    Instead:List the actions the application calls. CloudTrail shows exactly which those are.

  2. Reading Deny as a filter

    An explicit Deny always wins and cannot be overridden by any Allow, in any policy, including a resource policy. It is not a precedence hint.

    Instead:Use Deny for guardrails you never want bypassed, and scope Allow properly for everything else.

  3. Assuming a wildcard resource is fine on a read-only action

    Read-only across every resource still exposes every secret, parameter and bucket the account holds.

    Instead:Scope the resource ARN even for reads.

What it checks

Least privilege fails quietly. A policy that looks scoped can still hand over the account through one action nobody read carefully.

JSON errors, with the line and column

A strict JSON parser written for this, rather than a wrapper around JSON.parse, whose messages vary by browser and lately stopped reporting positions at all. Trailing commas, single quotes, unquoted keys, comments, missing commas, bad escapes and Python's None each get named for what they are, with a caret under the character.

Policy shape

Duplicate keys (where parsers disagree about which one wins), missing or misspelled Effect, statements with no Action, actions written without a service prefix, a missing Version that silently selects the 2008 policy language, duplicate Sids, and documents over the 6,144 character managed-policy limit.

Wildcards, graded

Action "*" on Resource "*" is not the same problem as s3:Get* on one bucket, and the results say so. Service-level wildcards on iam, sts, kms and organizations are treated as account takeover, because that is what they are.

Privilege escalation paths

Twenty-plus known escalation routes: rewriting a policy version, attaching a managed policy, minting access keys, updating a trust policy, and passing a role to Lambda, EC2, CloudFormation, Glue or CodeBuild. Each finding says what the holder could actually do.

Trust policies

Wildcard principals with and without conditions, cross-account trust with no sts:ExternalId, and conditions that rest on aws:SourceIp alone, which does not apply when the request arrives through a VPC endpoint or another AWS service.

Inverted logic

Allow with NotAction or NotResource grants everything except a short list, including services AWS has not launched yet. It is the most reliable way to write a policy that is far wider than it reads.

Data and key access

Unrestricted s3:GetObject, kms:Decrypt, secretsmanager:GetSecretValue, ssm:GetParameter and dynamodb:Scan are called out separately, because a leaked credential for one of those is a breach rather than an incident.

Policies that will not work

Object actions scoped to a bucket ARN with no /*, s3:ListBucket scoped to object ARNs, actions missing a service prefix, a missing Version that silently selects the 2008 policy language, duplicate Sids, and documents over the 6,144 character limit.

Why iam:PassRole keeps showing up

PassRole is the action that lets an identity hand a role to an AWS service. On its own it does nothing. Combined with permission to create a Lambda function, launch an EC2 instance, or start a CloudFormation stack, it means the identity can run code as any role it is allowed to pass.

With Resource set to "*", that is every role in the account, including the ones with AdministratorAccess. This is why a CI policy that looks like a deployment policy is very often an administrator policy wearing a disguise, and why the analyzer treats the combination as critical rather than as two separate medium findings.

The fix is narrow: scope PassRole to the exact role ARNs, and add a condition on iam:PassedToService so the role can only go to the service you intended.

How does AWS decide whether a request is allowed?

Every API call is evaluated against every policy that applies to it, and the result comes from a fixed order of precedence rather than from whichever policy is most specific. Once you know the order, most 'why is this denied' questions answer themselves, and so do the more alarming 'why is this allowed' ones.

Deny wins, then allow, then deny again

Evaluation starts from an implicit deny: with no policy at all, nothing is permitted. An explicit Allow anywhere flips that. An explicit Deny anywhere flips it back, permanently: there is no policy that can override a Deny, no matter how specific or how privileged the principal. This is what makes a Deny in a service control policy or a permissions boundary an absolute ceiling.

1. explicit Deny        -> denied, and nothing can override it
2. explicit Allow       -> allowed, if no Deny matched
3. nothing matched      -> denied (the implicit default)

specificity is irrelevant: a Deny on * beats an Allow on an exact ARN

The parts of a statement

Effect is Allow or Deny. Action is what can be done, Resource is what it can be done to, and Principal is who can do it: present in resource policies, absent in identity policies where the principal is whoever the policy is attached to. Condition narrows the whole statement and is the only part that can make a wildcard safe.

{
  "Effect": "Allow",
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::my-bucket/*",
  "Condition": {
    "Bool": { "aws:SecureTransport": "true" }
  }
}

Identity policies and resource policies are different questions

An identity policy is attached to a user, group or role and says what that principal may do. A resource policy is attached to the thing itself: a bucket, a queue, a KMS key, and says who may touch it. Within one account, an Allow in either is usually enough. Across accounts you need both: the resource policy in the owning account and an identity policy in the calling one. A trust policy is a resource policy on a role, which is why it has a Principal and no Resource.

Why a wildcard is worse than it looks

Action: "*" with Resource: "*" is administrator access however it is labelled. Less obvious is that s3:* includes s3:PutBucketPolicy, which lets the holder rewrite the very policy restricting them, and that iam:PassRole without a Resource constraint lets someone attach any role to a service they can launch, which is a straight path to privilege escalation. Those are the patterns worth finding before they ship, and what the analyzer above grades.

"Action": "iam:PassRole", "Resource": "*"

  lets the holder pass the admin role to a new EC2 instance
  and read credentials from the instance metadata endpoint