GitLab ID tokens include a ref_protected claim that is true only when the job's Git ref is protected. Add it to your cloud trust rule, together with stable identifiers such as project_id and namespace_id, so jobs on unprotected branches cannot assume a deploy role.
The claims that matter
GitLab's ID token documentation lists the claims you can use in a trust policy:
| Claim | What it tells the cloud |
|---|---|
| project_path | Path of the project running the job |
| namespace_path | Namespace path of the project |
| ref | The Git ref for the job |
| ref_protected | true if the ref is protected, otherwise false |
| environment | Present only when the job sets an environment |
| environment_protected, deployment_tier | Related environment details |
In merge request pipelines, project_path is the source project's path and ref is the source branch. That matters if you accept merge requests from forks or from topic branches: the claim describes the source, which is the part an attacker controls.
Use identifiers, not just names
GitLab's advice is to include stable identifiers alongside the subject, specifically namespace_id and project_id. The project_id is described as globally unique and constant for the project's lifetime, so renaming a project does not break the trust rule. It also does not let someone recreate a project with the same name to inherit trust.
GitLab also warns against using user_login or user_email as the only condition, because users can change them.
The sub claim itself can include fields such as ref_protected. The documentation describes that as added in GitLab 18.7.
Steps to build the rule
- In the job, request a token with the id_tokens keyword and set a narrow audience for the cloud service.
- In the cloud trust definition, require the audience.
- Require project_id and namespace_id to match your project.
- Require ref_protected to be true, or require the sub to include it.
- Protect only the branches and tags that should deploy, and limit who can push or merge to them.
- Test from an unprotected feature branch and confirm the login is refused.
Provider differences
GitLab notes that for AWS, extra condition keys such as namespace_id, project_id, and ref_protected apply only to the gitlab.com OIDC provider. On Self-Managed and Dedicated, where only the sub claim is supported as an AWS condition key, sub is the only option. If you run your own GitLab, check which claims your provider supports before relying on them. This is the sort of detail that quietly weakens a rule, so test it.
Common errors
A 401 Unauthorized often comes from the deprecated CI_JOB_JWT_V2 variable, mismatched provider names or audiences, or a missing id_tokens block. An "ID token issuance is disabled" message appears when a project path in the sub was used by another project before. GitLab's fix is to set the sub claim components through the projects API, then update the trust policies.
Quick check
Is protected branch enforcement in GitLab enough on its own?
It controls who can push and merge. The cloud rule is what makes the cloud honor that decision, so you need both.
Should I trust project_path or project_id?
Prefer project_id. It does not change on rename, while a path can be reused.
Safeguard asset discovery can help you see which projects and pipelines hold deploy access, and policies and gates can review changes to CI configuration. A free scan is at gold.safeguard.sh/scan.
Sources
Last reviewed: 19 October 2026.