Incorrect Permission Assignment issue in GitLab ID token

  • Mohit Gupta Mohit Gupta
  • Published: 18 Sep 2025
  • Type: Incorrect Permission Assignment
  • Severity: Medium

Affected Products

GitLab CE/EE

CVE

CVE-2025-5819

Overview

An issue was identified within the sub claim of the ID tokens generated by GitLab for Merge Request pipelines. This would allow a pipeline running from a project’s fork to be issued an ID token that has the same sub claim as a pipeline running in the parent project.

This can be leveraged by an attacker with developer permissions to obtain ID tokens for protected branches even without push permissions to protected branches, potentially enabling them to authenticate with third party services that rely on the security of the ID token. For example, this could be used to access AWS IAM roles should they be configured for the project.

The following GitLab versions are affected by this issue:

  • 15.7 to 17.11.6
  • 18.0 to 18.0.4
  • 18.1 to 18.1.2
  • 18.2 to 18.2.1

Technical Details and Proof of Concept

GitLab ID tokens are JSON Web Tokens (JWTs) that are issued by GitLab and provided to CI/CD pipelines. They contain a variety of claims that detail various parameters about the pipeline, for example the project’s name, job id, or the triggering user. These JWTs are commonly used to authenticate with third-party systems, such as AWS, through OpenID Connect (OIDC). In these cases, the third-party can validate the authenticity of the JWT by validating its signature against GitLab’s public keys, as well as validating the claims within the JWT against any pre-configured checks. For example in AWS, an IAM role that has been configured to allow assumption via OIDC, can have its trust relationship define which claims to check against what value before permitting access to that role. In the case of GitLab JWT tokens, this can be the sub claim only. Further details of this can be found in GitLab’s documentation.

Within the ID token, the sub claim is commonly used as the primary claim for authentication as the default subject of the JWT. Within GitLab ID tokens, the default sub claim follows the template project_path:PROJECT_PATH_HERE:ref_type:branch:ref:BRANCH_NAME_HERE. For example, if it were the main branch in the user/repo project, it would be project_path:user/repo:ref_type:branch:ref:main.

Regular pipelines running within a forked project would have a project path of the fork, instead of the parent project. The forked pipeline’s branch name would be the branch from the fork as well. However, if a merge request is submitted to the parent project, a developer of the parent project can trigger a merge request pipeline. In this case, the project path is of the parent project. The branch name remains what it would be in the fork. Thus, a pipeline running against the main branch for the fork through a merge request pipeline, and a pipeline against the protected main branch in the parent project, would have the same sub claim.

The steps below can be used to see this behaviour:

  1. Create a victim project with a main protected branch, in this example Skybound/oidc
  2. Configure the project to only allow maintainers to push/merge into the main protected branch
  3. Commit the below YAML as .gitlab-ci.yml into the Skybound/oidc project directly to the main branch
testing:
  id_tokens:
    TEST_TOKEN:
      aud: sts.amazonaws.com
  script:
  - apt update
  - apt install -y jq
  - echo $TEST_TOKEN | cut -d . -f 2 | base64 -d | jq
  1. In the resultant pipeline, note the sub claim
[..SNIP..]
"iss": "https://gitlab.com",
"sub": "project_path:Skybound/oidc:ref_type:branch:ref:main",
"aud": "sts.amazonaws.com"
[..SNIP..]
  1. Add a second user as a developer to the project, in this example rs_mohit. This user does not have access to the main branch.

The following steps are to be taken as the second user:

  1. Fork the Skybound/oidc project, the fork will be the attacker project, in this case would be rs_mohit/oidc
  2. Run the pipeline manually within the rs_mohit/oidc project
  3. In the resultant pipeline, note the sub claim - it is different to the previous sub claim from step 4
[..SNIP..]
"iss": "https://gitlab.com",
"sub": "project_path:rs_mohit/oidc:ref_type:branch:ref:main",
"aud": "sts.amazonaws.com"
[..SNIP..]
  1. Commit the below YAML to the rs_mohit/oidc project as .gitlab-ci.yml (replacing the previous version) directly to the main branch
testing:
  id_tokens:
    TEST_TOKEN:
      aud: sts.amazonaws.com
  script:
  - apt update
  - apt install -y jq
  - echo $TEST_TOKEN | cut -d . -f 2 | base64 -d | jq
  rules:
  - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  1. Submit a merge request to the Skybound/oidc project, attempting to merge main from rs_mohit/oidc into main of the victim
  2. Note a new pipeline is triggered for the merge request
  3. View the sub claim in this pipeline and note it has the same value as the pipeline from step 4.
[..SNIP..]
"iss": "https://gitlab.com",
"sub": "project_path:Skybound/oidc:ref_type:branch:ref:main",
"aud": "sts.amazonaws.com"
[..SNIP..]

When using Infrastructure-as-Code (IaC), such as Terraform, it is common for the main branch to have permissions to assume an administrative role in AWS. Ideally, this is done through OIDC as it enables administrators to restrict access to this privileged role to only pipelines that are executed from the main branch which would have been reviewed by trusted maintainers.

By leveraging merge request pipelines, a developer without direct access to the main branch could leverage merge request pipelines to gain access to an ID token that could enable them to get privileged access to a third-party system such as AWS, effectively enabling them to escalate their privileges or move laterally through the environment.

Remediation

GitLab have released a patch to this issue as discussed on their releases page. This has been automatically applied to their SaaS offering. If using a self-hosted version, it is recommended to upgrade to the latest possible version. The patch for this particular issue was included in the following versions:

  • 18.2.2
  • 18.1.4
  • 18.0.6

Timeline

Date Action
13 May 2025 Issue reported to GitLab through HackerOne
14 May 2025 Further information requested from Reversec
16 May 2025 Reversec provides additional information
6 Jun 2025 GitLab acknowledges the issue
13 Aug 2025 GitLab releases patch to address the issue
15 Aug 2025 GitLab award bounty and close the issue, further requesting 30 days before public publications to give time for customers to patch
18 Sep 2025 Reversec publishes advisory