Safeguard
Cloud Security•

GitHub Actions OIDC Immutable sub Claims: Fix Your Trust Policies

Since 15 July 2026, new GitHub repositories mint OIDC tokens with owner and repo IDs in the sub claim. Here is how to update AWS trust policies before a deploy fails.

Safeguard Security Team
5 min read

GitHub announced immutable subject claims for Actions OIDC tokens on 23 April 2026 and updated the post on 10 June. On 15 July 2026 the new format became the default for new repositories, and for any repository that is renamed or transferred after that date. If your cloud role trusts a sub string that contains only names, a repository on the new format will not match it.

The point of the change is sound. Names can be recycled, so a stranger who registers an abandoned org or repo name could once have produced a token with a subject your cloud account still trusts. IDs cannot be recycled.

What's affected

The old default subject looks like this:

repo:octocat/my-repo:ref:refs/heads/main

The immutable one adds numeric owner and repository IDs, joined with an at sign:

repo:octocat@123456/my-repo@456789:ref:refs/heads/main

GitHub's April post originally used a hyphen as the delimiter. The June editor's note and the current docs use @, so write your policies with @.

Who gets the new format:

  • Repositories created after 15 July 2026.
  • Repositories renamed or transferred after that date.
  • Any repository or organization that opts in through the OIDC settings or the REST API.

Older repositories keep the old format unless they opt in. The feature is for github.com only. GitHub Enterprise Server is not covered. If you customize the sub claim with include_claim_keys, the docs say the owner and repository IDs stay in the repo segment and cannot be removed.

How to check if you're exposed

Start with the failure you would see. A deploy job that assumed a role yesterday now fails at the credentials step with an AssumeRoleWithWebIdentity authorization error. That is a trust policy mismatch, not an outage.

Then look at what your roles trust. In AWS, list the roles that trust GitHub's provider and read the sub condition on each:

aws iam list-roles --query 'Roles[].RoleName' --output text | tr '\t' '\n' | while read r; do
  aws iam get-role --role-name "$r" \
    --query 'Role.AssumeRolePolicyDocument' --output json \
    | grep -q token.actions.githubusercontent.com && echo "$r"
done

For every role it prints, open the trust policy and note the sub value. Names only means it matches the old format. A wildcard such as the following will not match an immutable subject, because the IDs sit between the names and the colon:

repo:octo-org/octo-repo:*

Finally, look up the IDs you will need:

gh api repos/octo-org/octo-repo --jq '.id, .owner.id'

What to do now

  1. Inventory every role, workload identity pool and federated credential that trusts token.actions.githubusercontent.com. Include Azure and GCP, not only AWS.
  2. For repositories created after 15 July, write the trust policy in the immutable form. GitHub's AWS example is:
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:octo-org@123456/octo-repo@456789:ref:refs/heads/demo-branch"
  1. Update the trust policy before you opt an older repository in, or before you rename or transfer it. GitHub's changelog says to get the policy ready first. It does not say whether opting in can be undone, so treat it as one way.
  2. Prefer exact sub values for production roles: a branch, or better, a GitHub environment such as repo:octo-org@123456/octo-repo@456789:environment:prod with required reviewers on that environment.
  3. Keep id-token: write only on jobs that need it, and pin the action that exchanges the token. The AWS docs pin aws-actions/configure-aws-credentials to a commit SHA:
permissions:
  id-token: write
  contents: read
steps:
  - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502
    with:
      role-to-assume: arn:aws:iam::123456789012:role/gha-deploy
      aws-region: us-east-1
  1. If you used a custom claim template, remember that AWS does not support custom OIDC claims. GCP and HashiCorp Vault do, per GitHub's docs, so the right fix depends on the provider.

Why it matters

OIDC from CI is the right replacement for long-lived cloud keys, but the trust policy is now your credential. A loose sub condition is the equivalent of a key that anyone who can create a matching repository name can use. GitHub's AWS docs say to evaluate the sub condition key in any role that trusts GitHub's provider, so an audience-only condition is not enough.

The immutable format closes the name-recycling hole, but it does not fix over-broad wildcards. A role that trusts every branch and pull request in a repository still lets anyone who can open a pull request mint a token, subject to your workflow rules. Scope by environment and require reviewers.

Try it yourself

Pick one production deploy role. Write down its current sub condition, look up the repository and owner IDs with the gh api command above, and write the immutable version next to it. Then ask whether a pull request from a fork could ever satisfy the condition you wrote. If the answer is not an obvious no, tighten it.

Click through it at your own pace, or open it full screen.

Quick check

Your trust policy says repo:octo-org/octo-repo:* and a new repository under the same org was created on 20 July 2026. Will its token match?

No. New repositories use the immutable format, which includes owner and repo IDs after an at sign, so the name-only pattern does not match.

Why can a repository name not be trusted as an identity?

Names can be released and registered again by someone else. Numeric IDs cannot, which is why GitHub added them to the subject.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.