← OIDC Trust Policy Check

This checker vs. reading the trust policy by eye

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.

Which one answers your question

What you are trying to doWhere 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.

Common questions

Isn't scanning the trust policy for a wildcard enough?

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.

What does a manual read of the policy tend to miss?

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.

Why does repo:org/name:* matter for pull_request specifically?

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.

Is anything I paste into either process uploaded?

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.

← Back to OIDC Trust Policy Check