The free way to review a GitHub Actions OIDC trust policy is to open the JSON and read the
Condition block: does sub look pinned, is there an obvious
* sitting somewhere it shouldn't. That catches the case where nothing at all
restricts the role, and costs nothing but a few minutes of careful reading.
This checker runs the same read, but resolves every statement against IAM's
actual condition-operator semantics before judging it -- rather than treating a trailing
* as always meaning the same thing. A sub value under
StringLike is a real wildcard; the identical string under StringEquals
is a literal character that no real GitHub token can match, and reporting it as an exposure would
send the reader to fix a role that is actually failing closed. It also checks aud
against the one audience every GitHub/AWS OIDC integration actually uses, walks every value in a
multi-value condition rather than stopping at the first tightly-scoped one, and flags a
repo:org/name:* pattern for including pull_request -- a context GitHub
issues to fork-triggered workflows opened by strangers.
| What you are trying to do | Where to do it |
|---|---|
Spot-check a trust policy you just wrote for an obvious, unconditioned *. |
Reading it by eye -- fast, and good enough to catch the plainest case. |
Know whether a sub value ending in * is a real wildcard or a literal character that fails closed, without holding IAM's operator semantics in your head. |
This checker -- it resolves each statement's actual condition operator automatically. |
Confirm aud being pinned actually restricts who can assume the role. |
This checker -- it names the one audience every real OIDC integration uses, so a pinned aud alone is never read as isolation. |
Check every value in a multi-value sub condition, not just the first few tightly-scoped ones. |
This checker -- it walks the whole list, since IAM treats it as "any of these match." |
Notice that a repo:org/name:* pattern is assumable from a fork's pull request, not just from branches in the same repository. |
This checker -- it names the pull_request exposure explicitly. |
| Gate a policy change in CI without a human review step every time. | This checker -- one Node command, no AWS credentials, a machine-checkable pass/fail. |
A careful read is a reasonable first pass, and it is often what catches the policy with nothing
at all in its Condition block. This checker is what you run once the policy has a
condition that looks right -- it resolves the operator semantics a human reviewer would otherwise
have to memorise, per statement, every time.
It catches the obvious case -- a bare * with no condition operator narrowing it at all. What it does not catch is the operator-dependent reading: a sub value of repo:org/name:* under StringLike is a genuine wildcard covering every branch, tag, and pull request in that repository, while the exact same string under StringEquals is a literal character match that no real GitHub token will ever satisfy -- it fails closed rather than opening anything. A reviewer has to hold that distinction in mind for every statement in the policy; this checker applies it automatically and reports which one each statement actually is.
Three shapes in particular. First, the operator-dependent wildcard above. Second, aud being mistaken for identity: every AWS/GitHub OIDC integration uses the same audience, sts.amazonaws.com, so a policy that pins aud tightly but leaves sub unconstrained reads as locked down while being assumable by any repository on GitHub -- pinning aud is necessary and is not, by itself, isolation. Third, a multi-value sub condition: IAM treats a list of values as "any of these match," so four tightly scoped branches plus one repo:org/name:* entry are exactly as open as the wildcard alone, and a top-to-bottom read tends to find the four good rows first and stop looking.
Because GitHub issues that sub claim shape to workflows triggered by a pull_request event, including one opened from a fork by someone with no write access to the repository. A policy scoped to that pattern is assumable by a stranger's pull request, not just by trusted branches in the same repo -- a detail that is easy to miss when eyeballing a condition block that otherwise looks like a normal repo-and-branch restriction.
There is nothing to upload either way. Reading a trust policy JSON file in a review tool or an editor involves no third party by itself, and this checker's browser page only parses and classifies the JSON you paste in locally -- a trust policy names your AWS account, your role, and your repositories, so nothing about it is sent anywhere. The CLI reads a file from your own disk and makes no network request either.