"category": "CI/CD federation" · job/github-actions-aws-oidc
Scoping a GitHub Actions trust policy in AWS
Which condition keys to use in an IAM role trust policy for GitHub Actions, which ones are immutable, and worked examples for a branch, an environment and a reusable workflow.
- Always check aud and pin sub to a specific repository and context.
- Add repository_id and repository_owner_id: they are immutable, names are not.
- Prefer one role per environment, and protect that environment in GitHub.
- IAM rejects new GitHub trust policies without a sub or job_workflow_ref condition, but older roles are not re-checked.
A role that GitHub Actions can assume has two policies. The permissions policy says what the role can do. The trust policy says which workflows may become the role. This guide is about the second one, where a loose condition lets other people’s workflows in.
What does a minimal safe trust policy look like?
For a deploy job that runs in a GitHub environment called production:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:octo-org/octo-repo:environment:production",
"token.actions.githubusercontent.com:repository_owner_id": "123456",
"token.actions.githubusercontent.com:repository_id": "456789"
}
}
}
]
}
Each line does one job:
Principal.Federatednames the GitHub OIDC provider in this account.audrefuses tokens minted for some other service.subpins the repository and the job context.repository_owner_idandrepository_idpin the numeric IDs, which do not change on rename and are not reused.[AWS IAM User Guide]
If the repository emits the immutable sub format, write sub as repo:octo-org@123456/octo-repo@456789:environment:production. The ID keys can stay; they cost nothing.
Which condition keys can I use?
AWS exposes these GitHub claims as condition keys, all prefixed with token.actions.githubusercontent.com:.[AWS IAM User Guide]
| Key | Example value | Stable across renames? |
|---|---|---|
aud |
sts.amazonaws.com |
n/a |
sub |
repo:octo-org/octo-repo:ref:refs/heads/main |
Only in the immutable format |
repository_id |
456789 |
Yes |
repository_owner_id |
123456 |
Yes |
actor_id |
1 |
Yes |
repository |
octo-org/octo-repo |
No |
ref |
refs/heads/main |
n/a |
environment |
production |
n/a |
job_workflow_ref |
octo-org/deploy/.github/workflows/deploy.yml@refs/heads/main |
No (contains names) |
enterprise_id |
345 |
Yes |
AWS’s guidance is to use the immutable identifiers (repository_id, repository_owner_id, actor_id) because repository, organization and user names can change.[AWS IAM User Guide]
What does IAM enforce for me?
GitHub’s issuer is shared by every GitHub customer, so the issuer alone says nothing about whose job it is. AWS calls these shared providers and requires certain claims in their trust policies.[AWS IAM User Guide] For GitHub, IAM checks on create and update that the policy has a sub condition (or job_workflow_ref) whose value is not only a wildcard. Otherwise the call fails with MalformedPolicyDocument.[AWS IAM User Guide][AWS IAM User Guide]
That check has two limits:
- It runs only when the policy is created or changed. Existing trust policies are not re-evaluated until someone edits them.[AWS IAM User Guide]
- It accepts broad patterns such as
repo:octo-org/*, which AWS documents as an example of organization-wide trust.[AWS IAM User Guide]
So an account can still hold roles with no sub condition, and new roles can still be organization-wide. Both are worth auditing.
How should I scope by branch, environment or workflow?
| Goal | Condition | Notes |
|---|---|---|
| One branch | sub = repo:ORG/REPO:ref:refs/heads/main |
Breaks if the job adds environment: |
| One environment | sub = repo:ORG/REPO:environment:production |
Add protection rules in GitHub |
| Any tag | StringLike sub = repo:ORG/REPO:ref:refs/tags/* |
Anyone who can push a tag can deploy |
| A central reusable workflow | job_workflow_ref = ORG/deploy/.github/workflows/deploy.yml@refs/heads/main |
Plus repository_owner_id |
| Whole repository | StringLike sub = repo:ORG/REPO:* |
Includes pull requests |
AWS recommends environment protection rules with deployment branch restrictions when you trust an environment.[AWS IAM User Guide] That way GitHub enforces which branches may run the job, and the trust policy only has to name the environment.
Which mistakes come up most often?
StringEqualswith a*in the value. It is compared literally and never matches.ForAllValues:StringEqualsin an Allow statement. It evaluates to true when the claim is missing, so a misspelled key can grant access. AWS’s action README warns against it.[AWS (GitHub repository)]- No
audcheck. - Name-only trust. A deleted organization name can be registered again by someone else, who then gets tokens with the same
sub. See subject recycling. - One role shared by many repositories with
repo:ORG/*, so every repository inherits the broadest permissions any of them needed.
Paste your policy into the OIDC trust checker to see which of these apply, then use its generated policy as a starting point.
Questions people ask
- Is StringLike with repo:my-org/* acceptable?
- IAM accepts it, and AWS shows it as an example. It lets every repository, branch and pull request in the organization assume the role, including repositories created later. AWS itself recommends narrowing to specific repositories or branches.
- Can I use job_workflow_ref instead of sub?
- IAM accepts either one as the required GitHub control. job_workflow_ref is set for jobs that call a reusable workflow, so it lets you trust a central deploy workflow instead of each calling repository. Keep the repository_owner_id check alongside it.
- Which condition keys survive a repository rename?
- repository_id, repository_owner_id and actor_id are numeric and stable. repository, repository_owner and the name-only sub format change when the repository or owner is renamed.
Sources
- Create a role for OpenID Connect federation (console) — AWS IAM User Guide.# official documentation · checked November 10, 2026 (full page text)
- IAM and AWS STS condition context keys — available keys for AWS OIDC federation — AWS IAM User Guide.# official documentation · checked November 10, 2026 (first 100,000 characters of the page (the GitHub and default OIDC sections were inside that range))
- Identity-provider controls for shared OIDC providers — AWS IAM User Guide.# official documentation · checked November 10, 2026 (full page text)
- OpenID Connect reference — GitHub Docs.# official documentation (issuer) · checked November 10, 2026 (full page text)
- Configuring OpenID Connect in Amazon Web Services — GitHub Docs.# official documentation (issuer) · checked November 10, 2026 (full page text)
- aws-actions/configure-aws-credentials (README) — AWS (GitHub repository).# official documentation (action maintainer) · checked November 10, 2026 (full README)
Update history
- — First published.
Related guides
3 jobs- job/fix-not-authorized-assumerolewithwebidentity category Troubleshooting
Fixing “Not authorized to perform sts:AssumeRoleWithWebIdentity” in GitHub Actions
A checklist for the AWS OIDC error, ordered by how often each cause applies: sub format, job context, wildcards under StringEquals, audience, provider ARN and customized claims.
· 7 min - job/github-oidc-immutable-subject category CI/CD federation
GitHub's immutable OIDC subject claims, fixed in AWS, Azure and Google Cloud
Repositories created after July 15, 2026 send a new sub format with owner and repository IDs. What changed, how to tell which format you get, and the trust changes for each cloud.
· 8 min - job/oidc-subject-recycling category Risks
Subject recycling: when a deleted GitHub name still unlocks your cloud role
Trust rules that match names can be satisfied by whoever registers that name next. What the 2026 research found, what GitHub, GitLab and the clouds changed, and how to audit your own roles.
· 7 min