All Tools View Categories About Contact Privacy

IAM Unused Statement Detector

Find statements that are structurally inert — missing an Action, missing a Resource/Principal, or an empty array. Not a shadowing or runtime-match analysis.

Runs entirely in your browser. This checks document structure only — it does not detect statements shadowed by other statements or that never match a real request.
0
statements
0
flagged
StatementSidReason

About IAM Unused Statement Detector

Not every broken IAM statement announces itself with a validation error. Some policies parse as perfectly valid JSON and technically satisfy IAM's schema, yet contain a statement that structurally cannot do anything — one with no Action at all, one with no Resource or Principal to act on, or one where an Action or Resource array was left empty by a generator or a careless edit. Unused Statement Detector finds exactly this narrow, well-defined category of structurally inert statement and reports it — nothing more, and nothing that requires guessing about runtime behavior.

The word "unused" is deliberately scoped tightly in this tool, and it is worth being precise about what it does and does not mean, because the term could easily be misread as something far more ambitious. This tool flags a statement when one of three things is true: it has neither an Action nor a NotAction field at all; it has neither a Resource, NotResource, Principal, nor NotPrincipal field at all; or it has an Action or Resource field present but set to an empty array ([]). Each of these three cases describes a statement that is inert by construction — there is nothing for AWS to match against a real request, regardless of what that request looks like. A statement missing its Action field cannot ever apply to any API call, full stop; a statement with "Action": [] is functionally identical, just less obviously broken to read.

What this tool deliberately does not do is policy-conflict or shadowing analysis. A perfectly well-formed statement that grants s3:GetObject on a bucket, but is always overridden in practice by an explicit Deny statement elsewhere in the same policy (or in a separate SCP, permission boundary, or resource-based policy AWS also evaluates), is not flagged here — it has a real Action and a real Resource, it is structurally complete, and this tool has no way of knowing (and does not attempt to determine) whether some other statement always wins the evaluation before it matters. Likewise, a statement can be structurally perfect and still never match a single real-world request — a typo'd action name that happens to still be syntactically valid, or a Resource ARN pointing at a resource that was deleted months ago — and this tool will not flag either of those, because both require knowing something about the world beyond the shape of the JSON in front of it. Detecting those requires either a live AWS account to test against, or much deeper reasoning about policy evaluation order across multiple attached policies — out of scope for a client-side, single-document structural check.

Within its scope, the checks are precise and priority-ordered: a statement is reported with exactly one reason, checked in this order — missing Action/NotAction first, then missing Resource/NotResource/Principal/NotPrincipal, then the empty-array case. Principal and NotPrincipal are accepted as valid targets in place of Resource, since resource-based policy statements (like an S3 bucket policy statement) commonly specify a Principal instead of a Resource. The empty-array check looks specifically at Action and Resource (matching how this shows up in practice — NotAction/NotResource being empty arrays is a rarer pattern this check does not separately special-case).

The most common real source of these findings is not hand-written policy JSON but generated or templated policies: a script that builds an actions list from some other data source and, on an edge case, produces an empty list; a policy-as-code tool that conditionally adds resources and ends up with none when a condition fails silently. Because these statements are still syntactically valid IAM JSON, they pass ordinary JSON and structural validation cleanly and can sit in a policy for a long time before anyone notices the statement never did anything. This tool is a fast, honest, narrowly-scoped pass to catch exactly that — nothing about runtime evaluation, nothing about cross-policy conflicts, just the specific structural dead-weight this tool is built to find.

Features

  • Flags statements with no Action/NotAction at all.
  • Flags statements with no Resource/NotResource/Principal/NotPrincipal at all.
  • Flags empty Action or Resource arrays ([]).
  • One clear reason per flagged statement, priority-ordered.
  • Precise scope — structural inertness only, not shadowing or runtime-match analysis.
  • Accepts Principal/NotPrincipal as a valid target in place of Resource for resource-based statements.
  • Handles multi-statement documents of any size.
  • Sample policy with a mix of flagged and clean statements.
  • One-click clear.
  • 100% client-side — nothing is uploaded.

How to Use

  1. Paste your policy JSON, or click the sample.
  2. Click Detect unused statements.
  3. Review the summary — total statements checked and how many were flagged.
  4. Read the reason next to each flagged statement.
  5. Fix or remove statements that are genuinely dead weight.
  6. Re-run after editing to confirm the fix.

Examples

Example 1 — missing Action. A statement with only Effect and Resource is flagged with reason "no Action/NotAction present".

Example 2 — empty Action array. "Action": [] alongside a real Resource is flagged with reason "Action or Resource is an empty array".

Example 3 — missing target. A statement with an Action but no Resource, NotResource, or Principal is flagged with reason "no Resource/NotResource/Principal present".

Example 4 — resource-based statement, not flagged. A bucket-policy-style statement with an Action and a Principal but no Resource is not flagged, since Principal satisfies the target requirement.

Example 5 — complete statement, not flagged. {"Effect":"Allow","Action":"s3:GetObject","Resource":"arn:aws:s3:::my-bucket/*"} passes cleanly with zero findings.

Benefits

  • Catches structurally dead statements left behind by generators or careless edits.
  • Precisely scoped — never overclaims detection of shadowing or runtime non-matches.
  • One clear reason per finding, not a vague warning.
  • Works on documents of any size.
  • Fast, local first pass before deeper policy review.
  • Private — policy content never leaves your browser.

Frequently Asked Questions

What does "unused" mean in this tool specifically?
It means structurally inert — a statement that is missing an <code>Action</code>/<code>NotAction</code> entirely, missing a <code>Resource</code>/<code>NotResource</code>/<code>Principal</code>/<code>NotPrincipal</code> entirely, or one where <code>Action</code> or <code>Resource</code> is present but set to an empty array (<code>[]</code>). These are documents that are broken by construction — the statement literally has nothing to act on, or nothing that grants/denies anything, regardless of any request AWS ever evaluates against it.
Does this detect statements that are shadowed or overridden by another statement?
No. This tool does not perform policy-conflict analysis. A statement that grants an action but is always overridden by an explicit Deny elsewhere in the same policy (or a different policy entirely) is not flagged — it is structurally complete and does something; it just may never be the deciding statement in practice. That is a different, much harder analysis this tool does not attempt.
Does it detect statements that never match any real request?
No. A statement can be structurally complete — it has an Action, a Resource, everything IAM requires — and still never match anything in the real world, for example if the Resource ARN references a resource that was deleted, or the Action name has a typo that happens to still be syntactically valid. This tool cannot know that; it only checks the document's shape, not runtime behavior.
Why would a statement end up with an empty Action array in the first place?
Usually from generated or programmatically assembled policies, where a list of actions was filtered or computed and happened to end up empty — for example, a template that adds actions conditionally and none of the conditions matched, leaving <code>"Action": []</code> behind.
What is the "reason" field for each flagged statement?
One of three fixed reasons: <code>no Action/NotAction present</code>, <code>no Resource/NotResource/Principal present</code>, or <code>Action or Resource is an empty array</code> — whichever specific structural gap the statement has. Only the first matching reason is reported per statement, in that priority order.
Does a statement with a Principal but no Resource get flagged?
No — <code>Principal</code> or <code>NotPrincipal</code> counts as a valid target for a resource-based policy statement (like an S3 bucket policy), so a statement with an Action and a Principal but no Resource is not flagged as missing a target.
What about NotAction or NotResource — do those count the same as Action/Resource?
Yes for presence — a statement with <code>NotAction</code> instead of <code>Action</code> is not flagged as missing an action. However, the empty-array check specifically looks at <code>Action</code> and <code>Resource</code> (not <code>NotAction</code>/<code>NotResource</code>), matching the exact check this tool's underlying logic performs.
Can a valid, working policy still have zero flagged statements?
Yes — most real, working policies will show zero results here, since a normally hand-written or console-generated policy always has a complete Action and Resource/Principal on every statement. This tool is aimed at catching mistakes from hand-editing or scripted policy generation, not at flagging normal policies.
Does it check whether the Action or Resource values are spelled correctly?
No. It only checks presence and array-emptiness — not whether the action name is a real AWS action or the resource is a valid ARN.
Is my policy JSON sent anywhere?
No — the whole check runs in your browser.