Security Risk: in CI jobs, the CI_JOB_TOKEN is stored in .git/config, shouldn't be

When a CI job clones a Git repository, the CI_JOB_TOKEN token for the job is stored within the project subdirectory, in .git/config.

This is because the job token is included inline in the remote URL, passed through in https://username:password@ format.

This can be seen with commands run from CI, eg from https://gitlab.com/andrewn/git-remote-show/-/blob/main/.gitlab-ci.yml

$ # show the remotes
$ git remote -v
origin	https://gitlab-ci-token:SECRET_VALUE@gitlab.com/andrewn/git-remote-show.git (fetch)
origin	https://gitlab-ci-token:SECRET_VALUE@gitlab.com/andrewn/git-remote-show.git (push)
$ # check for the git configuration
$ cat .git/config
...
[remote "origin"]
	url = https://gitlab-ci-token:SECRET_VALUE@gitlab.com/andrewn/git-remote-show.git
	fetch = +refs/heads/*:refs/remotes/origin/*

This token is only temporary, so the risk of exfiltration of this token is low. However, the risk is non-zero and an attacker could setup a polling job to wait for fresh exfiltrations.

For example:

  1. An attacker searches for all projects with Dockerfiles containing COPY . ... commands in them. Example search: https://gitlab.com/search?group_id=6543&scope=blobs&search=file:Dockerfile+COPY+.+. This command will copy the current project directory into a Container image. The image will include the .git/config file, containing the temporary CI_JOB_TOKEN token.
  2. The attacker looks for jobs which publish a docker image and continue running afterwards. For example, if the job performs some QA tests using the published images, it may run after the docker image is published.
  3. The attacker sets up a polling job waiting for new images to be published from the CI job. When one is published, the image is fetched and the the temporary, but still working CI_JOB_TOKEN token is exploited.

Simple Workaround

Projects should add .git to their .dockerignore configurations.

However, this needs to be done on a per-project basis, and can easily be missed for a single project.

Additionally, other means of accidentally exfiltrating the .git directory would not be covered. This could include tarball creation, pages site publishing etc etc. All of these methods may accidentally exfiltrate the .git/config directory, containing the CI_JOB_TOKEN.

GitLab Global Workaround

One option (there may be better ones) would be to not write the token into the project directory.

For example, instead of embedding the CI_JOB_TOKEN directly in the remote URL, a git-credential helper could be used to reference the $CI_JOB_TOKEN environment variable.

Example adapted from https://git-scm.com/docs/gitcredentials

$ cat .git/config
...
; Git credentials are not stored directly in the file any longer:
[remote "origin"]
	url = https://gitlab.com/andrewn/git-remote-show.git
	fetch = +refs/heads/*:refs/remotes/origin/*
[credential "https://gitlab.com"]
	username = gitlab-ci-token
	helper = "!f() { test \"$1\" = get && echo \"password=$CI_JOB_TOKEN\"; }; f"
Edited by Andrew Newdigate