Join our Newsletter — 33% off our NHI Course
Home› Guides› How to Scope CI OIDC Trust to Branch…
Guide Cloud & Workload Identity

How to Scope CI OIDC Trust to Branch and Environment: subject claims and pitfalls across four CI platforms

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 14 November 2026 14 min read
On this page

A CI OIDC trust that checks only the issuer and audience lets any branch, merge request or fork in scope mint production cloud credentials. Each CI platform puts branch and environment information into its tokens differently: some in the subject claim, some in separate claims, and one in neither, relying on checks inside the CI platform instead. This guide shows, for Azure DevOps, Bitbucket Pipelines, CircleCI and GitLab CI/CD, where branch and environment live in the token, how to write trust conditions in AWS, Google Cloud and Microsoft Entra ID that bind to them, and the cross-platform pitfalls that undo the scoping. Platforms are listed alphabetically. GitHub Actions is covered in depth in the GitHub Actions OIDC Federation Hardening Guide and is only referenced here; federation basics are in the Workload Identity Federation Guide. Platform details are from each vendor's documentation, checked on 13 November 2026.

Key takeaways

  • Find where the branch lives before writing a policy. GitLab puts the ref in the default subject, CircleCI puts it in its V2 and V3 subjects, Bitbucket puts the deployment environment in the subject, and Azure DevOps scopes by service connection, with branch rules enforced by checks in Azure DevOps.
  • Environment scoping beats branch scoping. A protected deployment environment carries approvals and branch rules with it; a branch name alone can be pushed by anyone with write access unless the branch is protected.
  • Wildcards are where scoping fails. A trailing * after a repository or project identifier usually trusts every branch, merge request and step; put wildcards only where the platform forces them, such as unique per-run IDs.
  • Check the configuration source, not only the checkout. CircleCI's own guidance is to match the config source and ref as well as the checkout, so a pipeline cannot use configuration from an unprotected branch.
  • Merge request and fork pipelines need explicit thought. On GitLab, project and ref claims in a merge request pipeline describe the source project and source branch, so production trust should never match those runs.

Where branch and environment appear in each platform's token

This table is the reference for the rest of the page. Values are from each platform's documentation; always confirm against a real token before tightening a policy.

PlatformIssuerSubject (sub) formatBranch informationEnvironment information
Azure DevOpsMicrosoft Entra issuer (https://login.microsoftonline.com/) by default for new connections; the older https://vstoken.dev.azure.com issuer is being retired for public cloud on 1 July 2027Generated by Azure DevOps and shown on the service connection; ends in /sc/ followed by the service connection identifierEnforced in Azure DevOps by the Branch control check on the service connection or environmentEnforced in Azure DevOps by approvals and checks on the environment resource
Bitbucket Pipelineshttps://api.bitbucket.org/2.0/workspaces/{WORKSPACE}/pipelines-config/identity/oidc{REPOSITORY_UUID}[:{ENVIRONMENT_UUID}]:{STEP_UUID}Scope through deployment environmentsEnvironment UUID in the subject, only when the step is assigned to a deployment environment
CircleCIhttps://oidc.circleci.com/org/<organization_id>V1: org/<org>/project/<project>/user/<user>; V2 adds /vcs-origin/.../vcs-ref/...; V3: org:...:project:...:user:...:checkout-source:...:checkout-ref:...:config-source:...:config-ref:...In the V2 and V3 subjects, and in the oidc.circleci.com/vcs-ref claimContexts used by the job in oidc.circleci.com/context-ids
GitLab CI/CDThe GitLab instance domain (for example GitLab.com)Default project_path:{group}/{project}:ref_type:{type}:ref:{branch_name}; configurable per projectIn the default subject, plus ref, ref_type and ref_protected claimsenvironment, environment_protected and deployment_tier claims when the job declares an environment

Azure DevOps: scope the connection, then gate it with checks

The workload identity federation subject for an Azure Resource Manager service connection identifies the service connection, not the pipeline run, so every pipeline authorised to use the connection presents the same subject. Branch and environment scoping therefore happens in Azure DevOps, and the cloud side scopes to the connection.

  • One connection per trust boundary: create separate service connections for production and non-production, each with its own federated credential and its own narrowly scoped role assignment. Microsoft's documentation advises against selecting "Grant access permission to all pipelines" and recommends authorising each pipeline individually.
  • Branch control check: add Branch control under Approvals and checks on the production service connection or environment. It verifies at run time that every resource in the run comes from an allowed branch, with branches written fully qualified (refs/heads/main), and can require that those branches have protection enabled. If the check fails, the stage fails.
  • Environment approvals: put production deployments behind an environment with required approvers, so a correct branch is necessary but not sufficient.
  • Cloud side: in Microsoft Entra ID, the federated credential matches the exact issuer and subject shown on the connection. In Google Cloud, Google documents that the full Azure DevOps subject exceeds the 127 character limit for google.subject, so map it with assertion.sub.split('/sc/')[1] and add the attribute condition assertion.oid=='APPLICATION_OBJECT_ID'.

See the How to federate Azure DevOps to Azure without secrets guide for setup and the Azure DevOps Security Guide for the wider platform.

Bitbucket Pipelines: scope through deployment environments

Bitbucket's subject is {REPOSITORY_UUID}[:{ENVIRONMENT_UUID}]:{STEP_UUID}, and Atlassian documents that the environment part appears only if the step is assigned to a deployment environment. The step UUID changes per step, so a trust condition always needs a wildcard at the end; the scoping decision is what comes before it.

  • Repository-only trust: Atlassian's example uses StringLike with {REPO_UUID}:*. That value also matches deployment steps, because the wildcard covers the environment segment. Use it only for read-only or non-production roles.
  • Environment trust: Atlassian's deployment example uses {REPO_UUID}*:{ENVIRONMENT_UUID}:*. For production roles, require the production environment UUID, so non-deployment steps and other environments cannot assume the role.
  • Branch restriction: branch names are not part of the subject. Control which branches can run deployment steps to the production environment in Bitbucket's deployment settings, and keep production steps in pipelines defined only for protected branches.
  • Audience: custom audiences can be set under oidc and audiences in the step, up to 10. Confirm the aud value in a real token before writing the trust, because the documentation shows both a client ID and a workspace-based value.

See How to federate Bitbucket Pipelines to AWS for setup and the Bitbucket Security Guide for the wider platform.

CircleCI: use the V3 subject and check the config source

CircleCI issues three tokens. The V1 subject identifies organisation, project and user only; V2 adds the VCS origin and ref; V3 adds the checkout source and ref and the config source and ref, with colons inside values encoded as %3A.

  • Project trust: CircleCI's recommended AWS example uses StringEquals on aud (the organisation ID by default) and on the oidc.circleci.com/project-id condition key. AWS lists that key as a CircleCI condition key available in the trust policy but not in the session.
  • Branch trust: for production, match the V3 subject with checkout-ref:refs/heads/main and config-ref:refs/heads/main, and pin checkout-source and config-source to your repository. CircleCI states that checking the config values stops a run from using configuration on an unprotected branch to gain access.
  • Forks: CircleCI warns that if tokens are issued for forked builds, policies must check repository claims; with V3, check checkout-source and, where the config comes from elsewhere, config-source.
  • Contexts: the oidc.circleci.com/context-ids claim lists the contexts used by the job. Where production secrets or settings sit in a dedicated context, matching its ID ties the trust to jobs that use it.
  • SSH reruns: the oidc.circleci.com/ssh-rerun claim marks jobs rerun with SSH access. Where your cloud supports conditions on it, exclude those runs from production roles, because a person has an interactive shell in the job.

See the CircleCI Security Guide for the wider platform.

GitLab CI/CD: use protected refs and protected environments

GitLab's default subject is project_path:{group}/{project}:ref_type:{type}:ref:{branch_name}, so a branch can be pinned with an exact match. GitLab adds custom claims that make stronger conditions possible.

  • Protected refs: ref_protected is true when the ref is protected. GitLab documents it as an AWS trust policy condition key on GitLab.com, and AWS lists ref_protected under its GitLab.com keys; on GitLab Self-Managed and GitLab Dedicated, only sub is supported for AWS conditions.
  • Protected environments: when a job declares an environment, the token carries environment, environment_protected and deployment_tier. Since GitLab 18.7 the subject can include ref_protected, environment_protected and deployment_tier, which puts these properties in the one claim every cloud can match.
  • ID-based subjects: the subject format can be changed per project with ci_id_token_sub_claim_components, and the first component must be an ID (project_id, job_project_id or source_project_id). GitLab blocks token issuance if the subject contains a project path that another project used before, which is a strong reason to move to ID-based subjects.
  • Audience per target: set a different aud for each cloud in the id_tokens keyword, so a token minted for one service is rejected by another.
  • Google Cloud: Google documents that GitLab's shared issuer requires an attribute condition restricting access to your group, for example assertion.namespace_id=='GROUP_ID', and gives assertion.environment=='production' as an environment condition.

See How to set up workload identity federation for GitLab CI for Azure setup, the GitLab Security Guide for the platform and the GitLab Tokens Security Guide for the static tokens federation should replace.

Writing the condition on each cloud

CloudMechanismWhat you can matchPractical note
AWS IAMRole trust policy with sts:AssumeRoleWithWebIdentity; keys named issuer host plus claim, for example gitlab.com:subsub and aud for every issuer; listed custom claims for some providers (CircleCI project-id; GitLab.com claims including project_id, namespace_id, ref_protected and pipeline_source)Use StringEquals wherever possible; use StringLike only for per-run segments. AWS recommends immutable IDs over names that can be reclaimed
Google CloudWorkload identity pool provider with attribute mapping and a CEL attribute conditionAny mapped claim; every condition field needs a matching mappingShared issuers require a condition that restricts to your organisation or group
Microsoft Entra IDFederated identity credential on an app registration or user-assigned managed identityExact issuer, subject and audience; flexible credentials (preview) add claimsMatchingExpression with eq, matches and andThe documentation cites a limit of 20 federated credentials per application or identity, so one credential per branch does not scale; prefer one per environment

For each production trust, write down the intended scope in one sentence (for example "deploy jobs for the production environment of project 1234 from protected refs only") and check that the condition expresses exactly that, nothing more. The Workload Identity Federation Trust Review Checklist turns this into a periodic review.

Rolling out scoped trust

  1. Inventory: export every OIDC provider, role trust policy, workload identity provider and federated credential, and tag each with the CI platform and target environment.
  2. Capture real tokens: for each platform, log the decoded claims from a test job on a protected branch, an unprotected branch and a merge or pull request, and keep them as test fixtures. Never log the raw token.
  3. Observe: read the cloud audit logs (AWS CloudTrail AssumeRoleWithWebIdentity events, Google Cloud STS audit logs, Entra service principal sign-ins) for 14 to 30 days to see which subjects use each trust today.
  4. Tighten non-production first: replace wildcard subjects with environment or ref conditions on development roles, confirm pipelines still work, then do production.
  5. Test the negative: run a job from an unprotected branch and, where allowed, a fork or merge request pipeline against the production trust, and confirm it is refused.
  6. Roll back safely: keep every trust policy, provider and federated credential definition in version control, change one trust at a time, and revert that single change if a pipeline breaks.

Cross-platform pitfalls

  • Trailing wildcards after an identifier: project_path:group/project:* on GitLab or {REPO_UUID}:* on Bitbucket trusts every branch and step. Put the wildcard only on the segment that changes per run.
  • Matching names instead of IDs: group and project paths can be renamed or reused after deletion. Use project, namespace and organisation IDs where the platform offers them.
  • Trusting merge request and fork pipelines: GitLab documents that in merge request pipelines project_id and ref describe the source project and source branch. A subject or claim match built for branches can therefore match code from a contributor's branch. Keep production trust on protected refs or protected environments only.
  • Checking the checkout but not the pipeline definition: the code that requests the token is the pipeline configuration. CircleCI's config claims address this directly; on other platforms, protect the pipeline file with code owners and branch protection (ATT&CK T1677 Poisoned Pipeline Execution).
  • One shared connection or role for everything: in Azure DevOps, one service connection authorised for all pipelines means any pipeline in the project can deploy. Split by environment and authorise per pipeline.
  • Issuer-only trust on shared issuers: GitLab.com serves many customers from one issuer, and Google documents shared issuers for GitLab SaaS and other platforms; without a subject or organisation condition, another tenant's job can present a valid token (ATT&CK T1078.004 Cloud Accounts).

Incidents in this area usually start with a writable pipeline rather than a broken trust policy: see the Ultralytics PyPI compromise, where a release workflow triggered by pull_request_target was abused and the project's short-lived OIDC publishing identity was used to publish malicious releases. The federation worked as designed; the pipeline that requested the token was the weak point.

Detection and measurement

Alert on federation exchanges whose subject or claims do not match the expected scope for the role, on any new OIDC provider, workload identity provider or federated credential, and on trust policy edits outside change control. In AWS, CloudTrail records the web identity subject on AssumeRoleWithWebIdentity events; compare it with the intended pattern for that role.

  • Wildcard ratio: percentage of production trusts whose subject condition contains a wildcard outside per-run segments (target: zero).
  • Protected scope coverage: percentage of production trusts bound to a protected environment, protected ref or Azure DevOps Branch control check.
  • ID-based subjects: percentage of trusts matching IDs rather than paths or names.
  • Negative test pass rate: percentage of production trusts that refused a test token from an unprotected branch in the last quarter.
  • Unexpected subjects: count of successful exchanges per month whose subject did not match the documented scope (target: zero, each investigated).

Framework mapping

FrameworkReferenceHow this guide applies
NIST SP 800-53 Rev. 5IA-9 Service Identification and Authentication; AC-6 Least PrivilegePipelines authenticate as workloads, scoped to branch and environment
NIST SP 800-53 Rev. 5CM-3 Configuration Change Control; AU-6 Audit Record ReviewTrust policy changes under change control; exchange logs reviewed
NIST CSF 2.0PR.AA-01; PR.AA-05Workload identities managed; permissions defined and enforced
MITRE ATT&CKT1677 Poisoned Pipeline Execution; T1078.004 Cloud Accounts; T1195.002 Compromise Software Supply ChainThreats that loose CI trust enables

Practitioner checklist

  • For each CI platform, record where branch and environment appear in the token, using a real decoded token.
  • Bind every production trust to a protected environment, protected ref or Branch control check, never to a repository alone.
  • Remove wildcards except on per-run segments such as Bitbucket step UUIDs.
  • Move GitLab subjects to ID-based components and match IDs on every platform where offered.
  • On CircleCI, match the V3 checkout and config source and ref for production roles.
  • In Azure DevOps, split service connections by environment and authorise pipelines individually.
  • Add organisation or group conditions on every shared issuer.
  • Run negative tests from unprotected branches and merge request pipelines, and keep the evidence.
  • Alert on unexpected subjects and on trust policy changes.

Standards and references

Related NHI Mgmt Group resources: GitHub Actions OIDC Federation Hardening Guide · Workload Identity Federation Guide · Workload Identity Federation Trust Review Checklist · CI/CD Pipeline Identity Security Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 14 November 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org