GitHub Actions OIDC Hardening Cheat Sheet: AWS, Azure, and Google Cloud
A different kind of checklist from the storage sheets: how to give GitHub Actions workflows access to AWS, Azure, and Google Cloud without long-lived keys, and how to make sure only the right repository can use that access. Baseline as of October 2026.
A cloud key stored as a CI secret fails the way every standing credential does: it works for whoever holds it, it outlives the person who created it, and nobody notices when it's copied. OpenID Connect (OIDC) federation replaces the key with a short-lived token that each job requests, but the risk doesn't disappear. It moves to one place: the trust condition on the cloud side that decides which repositories may ask. This is a working checklist pulled from GitHub's and the three clouds' own documentation, not a substitute for reading it.
What changed recently
Four 2026 changes from GitHub and Microsoft are worth knowing:
- April 23, edited June 10: GitHub announced immutable subject claims for Actions OIDC tokens. The default
subcan now carry the numeric owner and repository IDs, so a recycled name can't reproduce an old subject. The June 10 edit changed the delimiter between a name and its ID from-to@. - July 15: repositories created after this date use the immutable format automatically, and so do renames and transfers after it. Existing repositories keep the name-based format unless you opt in at the organization or repository level. GitHub Enterprise Server isn't affected. Check which format your repositories emit before you write or change a condition.
- July 28: Microsoft's guide to migrating Entra federated credentials from name-based to immutable subjects without downtime is dated this day, and its steps are folded into the checklist below.
- October 2: npm's trusted publishing, which runs on the same GitHub OIDC tokens, tightened. Unvalidated configurations now expire after 48 hours, and tokens from
issue_commentevents are rejected, on top of the existingpull_request_targetblock. If your publish workflow runs onissue_comment, move it topush,release, orworkflow_dispatch. The flaw behind the change, which let an attacker claim a freed repository name and publish to the victim's package, is covered in Issue 007's SlugFest item.
Worth flagging: a trust policy that matches a repository by name trusts whoever owns that name next
GitHub's own OIDC reference now says the old name-based sub was vulnerable: after a namespace was recycled, a different owner could create a matching subject. The risk runs both ways. A condition written from a name can match a stranger who later claims the name. And a repository renamed or transferred after July 15 moves to the immutable format automatically, so a name-only condition stops matching and AWS returns "Not authorized."
A researcher at Boost Security Labs argues the transition itself was risky. By the researcher's account, the original hyphen-delimited prefix could collide with a lookalike organization name, which is why GitHub pulled the changelog post and moved to @. The researcher reports no evidence that anyone was compromised, and GitHub's republished post records only the delimiter change. The researcher's practical warning stands either way: wildcard subjects such as repo:org/* are the most dangerous place to paste a new prefix.
Trust conditions
- [ ] Never leave a trust policy without a condition. GitHub's documentation says you must define at least one, so untrusted repositories can't request tokens for your cloud resources. AWS adds that without a
Conditionany GitHub user or repository could potentially assume the role - [ ] Match immutable IDs, not names. On AWS, put the immutable subject (
repo:<owner>@<owner_id>/<repo>@<repo_id>:...) in thetoken.actions.githubusercontent.com:subcondition. On Azure, create the credential for the immutable subject, and in a flexible credential also matchrepository_id,repository_owner_id, or both. On Google Cloud, maprepository_idandrepository_owner_idas attributes and condition on them, since Google advises the numeric*_idfields over the name fields, which can be reclaimed - [ ] No wildcard subjects.
repo:org/*trusts every branch, pull request, and environment in the repository, and an AWSStringLikewildcard is broader than the exact-match examples. Standard Azure federated credentials match exactly and support no wildcards, but the flexible credential (preview) accepts matching expressions, so scope any pattern deliberately - [ ] Trust an environment with protection rules for production roles rather than "any branch." GitHub recommends deployment branch and tag restrictions on the environment, and the
environment:subject is what the cloud condition should name - [ ] Restrict `event_name` to the events your workflows really use, where your provider supports the condition. Dependabot update jobs present
event_name: dynamic - [ ] Treat `pull_request_target` and non-ephemeral environments as high risk whenever they can request cloud tokens. AWS's guidance calls out both by name
- [ ] One identity per workload and environment, with least privilege. Google recommends a dedicated service account per workload, AWS says to grant least privilege, and Azure grants the app access only to the resources the workload targets. On Google Cloud, use the project number, not the project ID, in member identifiers
- [ ] Control who can change the trust. Azure limits creating or updating a federated credential to the app's owner and a short list of Entra roles. Treat the right to edit the equivalent trust policy on AWS and Google Cloud as just as sensitive, and audit who holds it
Subject formats and migration
- [ ] Check what your repositories actually emit before writing a condition. Use GitHub's preview endpoint and
github/actions-oidc-debugger, in private repositories only, since claim values can be sensitive. On Azure, the check matters twice: a wrong subject saves without error and the exchange fails silently later, and Microsoft's CLI examples spell the pull-request subject differently from GitHub's reference, so copy the value from a decoded token - [ ] Migrate in order, with no gap. Create the condition or credential for the immutable subject alongside the old one, opt the repository in, run the workflow, then delete the old name-based credential so no mutable trust remains. Create the new condition first: tokens that arrive before it exists may be rejected. On Azure, create one credential for each subject the workflow presents, mindful of the limit of 20 federated credentials per app or managed identity, and use environments to keep the count down
- [ ] Audit existing trust for repositories that were renamed, transferred, or deleted. A name-based condition that still points at a name you no longer control is exactly the opening GitHub's reference describes
- [ ] Where immutable subjects aren't available, customize the subject. On GitHub Enterprise Server, set the subject template with
include_claim_keysto userepository_idorrepository_owner_id, which don't change when a repository is renamed - [ ] Check every character of a new prefix before you paste it. Note
@where older examples show-, and prefer exact matches to patterns
Workflows and credentials
- [ ] Retire long-lived cloud keys stored as GitHub secrets. GitHub's guidance is not to store cloud credentials as long-lived secrets, and AWS advises preferring OIDC and rotating any long-lived credentials you still use
- [ ] Request `id-token: write` only on jobs that need it. The permission grants no write access to resources, but it lets the job mint a token. For a reusable workflow from outside your organization, set it explicitly at the caller so only intended callers receive a token
- [ ] Pin third-party actions to a full commit SHA, as GitHub's own examples do for the AWS, Azure, and Google actions
- [ ] Keep sessions short. GitHub's tokens are valid for a single job; AWS role sessions default to one hour (
role-duration-secondsaccepts 900 to 43,200), so set the shortest your jobs need - [ ] Avoid `ForAllValues:` in an `Allow` trust statement. AWS warns it can evaluate true when a claim is absent or misspelled; use
StringEqualsorStringLikefor specific values - [ ] On Google Cloud, prefer direct resource access, granting roles to the federated principal, and use service account impersonation (
roles/iam.workloadIdentityUser) only for products that need it, with a dedicated service account per workload
Logging & monitoring
- [ ] AWS: alert on
AssumeRoleWithWebIdentityfor these roles. CloudTrail logs the call along with the identity provider's information, and the loggeduserIdentitylets you map the assumed role's later calls back to the federated caller. Events served by the STS global endpoint are logged in US East (N. Virginia) - [ ] Azure: review the service principal and managed identity sign-in logs to see which workload identities accessed which resources. Sign-in log entries are system generated and can't be changed or deleted
- [ ] Google Cloud: watch the Admin Activity audit logs (
iam.googleapis.com) for creates, updates, deletes, andSetIamPolicycalls on workload identity pools and providers - [ ] Review trust changes on a short cadence, with a human looking. A new or edited federated credential, pool provider, or role trust policy is a high-signal event, worth reviewing in days rather than at the next quarterly audit
A note on scope and benchmarks
This sheet cites no CIS control numbers. The CIS benchmarks for each provider address long-lived access keys and credential rotation, but numbering shifts between versions and this piece spans three providers' benchmarks, so it cites the vendors' own documentation rather than controls that haven't been checked against each benchmark. The conditions described above are patterns from those documents, not drop-in policies: test them in a non-production account first. One extrapolation is worth stating plainly: Google's page prints the attribute-condition syntax only for the owner name (assertion.repository_owner=='ORGANIZATION'). The ID-based form uses the same shape with the mapped ID claims, but the page doesn't print an example, so verify it before relying on it.
Finding the keys you still have
You can't retire a long-lived key you haven't found. Gitleaks, covered in this pillar's OSS Spotlight, scans repository history for exactly these credentials. Run it across the repositories whose workflows you are migrating, and rotate anything it finds, since a key that was ever committed should be treated as exposed. Exposure isn't limited to commits, either: the credential behind the JadePuffer attack first appeared in a public GitHub issue.
Sources: GitHub Docs: OpenID Connect · GitHub Docs: OIDC reference · GitHub Docs: OIDC in AWS · GitHub Docs: OIDC in Azure · GitHub Docs: OIDC in Google Cloud · GitHub Changelog: Immutable subject claims · GitHub Changelog: Unvalidated npm trusted publishing configurations now expire · AWS: Create an OIDC identity provider in IAM · AWS: Logging IAM and AWS STS API calls with CloudTrail · aws-actions/configure-aws-credentials · Microsoft Learn: Create a trust relationship between an app and an external identity provider · Microsoft Learn: Migrate GitHub Actions federated credentials to immutable subjects · Microsoft Learn: Sign-in logs in Microsoft Entra ID · Google Cloud: Workload Identity Federation with deployment pipelines · Google Cloud: IAM audit logging · Boost Security Labs: Sleeper Squats (the researcher's account)