The Role Any Repo Can Wear: An Unpinned OIDC Trust Policy
The challenge
Northwind lets GitHub Actions deploy to AWS without long-lived keys: a workflow presents a short-lived GitHub OIDC token and assumes a role. Two roles trust that provider. One is for deploying the marketing site, one is full admin on the production account. Both confirm the token really is a GitHub Actions token. Only one of them also confirms which repository the token came from. Read the two trust policies and name the role that any workflow on all of github.com can assume.
What you'll learn
- Explain what the aud and sub claims of a GitHub OIDC token mean
- Recognise that aud is a shared constant and proves nothing about the repo
- Identify a trust policy that omits the sub condition as critically open
- Describe the risk of StringLike wildcards on sub and ref
- State that trust must tighten as the role's privilege grows
Skills tested
Prerequisites
- What an IAM role and trust policy are
- The idea of OIDC federation for CI
How it works
Keyless CI works by a workflow presenting a short-lived OIDC token and assuming an AWS role. The role's trust policy decides who may do that, so it is the whole boundary.
A GitHub token's aud claim is the same value for every GitHub Actions run everywhere; checking it only proves the caller is some GitHub workflow. The sub claim is what names the repository and ref. A trust policy that checks aud but not sub will accept a token minted by a workflow in any repository on github.com, including one the attacker owns.
So the role must pin sub to the exact repos and refs allowed, and treat wildcards carefully: repo:org/* admits any repo in the org, and a loose ref admits any branch or a pull request.
Common mistakes
- Answering
northwind-site-deploy. Its policy pinssubto one repo and branch, so it is the safe one. - Thinking the
audcheck helps. Every GitHub token carries the sameaud; it is not a scoping control. - Assuming an attacker cannot get a valid token. Anyone can run Actions in their own repo and receive a genuine GitHub OIDC token.
- Reading privilege as the flaw. Admin rights make it worse, but the bug is the missing
subcondition.
How to defend against it
Pin the subject, and keep privilege proportional to scoping.
- Always add a
subcondition listing the exactrepo:org/name:ref:...values allowed. Never rely onaudalone. - Prefer
StringEqualson full subjects. If you must useStringLike, avoidrepo:org/*and loose refs; scope to named repos and branches. - Separate low and high privilege: a deploy role for one site should never share the openness of an admin role, and admin roles need the tightest possible trust.
- Audit every web-identity role for a missing or wildcard
sub.