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.