How to Grant GitLab CI Jobs Write Access Safely with Project Access Tokens

This guide explains why GitLab CI jobs lack git‑push rights by default, describes the minimal‑permission security model of CI_JOB_TOKEN, and provides a step‑by‑step solution using Project Access Tokens to securely enable write access for automated pipelines.

Ops Development & AI Practice
Ops Development & AI Practice
Ops Development & AI Practice
How to Grant GitLab CI Jobs Write Access Safely with Project Access Tokens

Introduction

When a GitLab CI pipeline runs, the git push command often fails with a 403 Forbidden error because the job uses the default CI_JOB_TOKEN. This token follows the principle of least privilege and cannot modify the repository.

Core Issue – CI_JOB_TOKEN Default Identity

GitLab generates a short‑lived token for each job and stores it in the predefined variable $CI_JOB_TOKEN. The token can:

Clone or pull code from the project and any projects it has read access to.

Read and write the project's Package Registry and Container Registry.

Download pipeline artifacts.

However, it cannot perform git push to any repository. This restriction is a deliberate security measure to prevent a compromised build script from altering source code.

Solution – Create and Use a Project Access Token (PAT)

A Project Access Token acts as a dedicated robot account with precisely the permissions required for the job. The minimal scope needed for pushing is write_repository.

Step‑by‑Step Procedure

Create a Project Access Token

Navigate to the target GitLab project (e.g., xlink/xlink-k8s-gitops).

Open Settings → Access Tokens .

Click Add new token .

Enter a name such as GITLAB_CI_PUSHER.

Set an expiration date for security.

Select a role (usually Developer; Maintainer also works).

In the scopes section, check only write_repository.

Click Create project access token and copy the generated token (it is shown only once).

Store the token securely as a CI/CD variable

Go to the upstream project that triggers the pipeline.

Open Settings → CI/CD → Variables .

Click Add variable .

Set Key to GITLAB_PUSHER_TOKEN (or any name you prefer).

Paste the copied token as the Value.

Enable Protect variable if the job only runs on protected branches/tags.

Enable Mask variable to prevent the token from appearing in job logs.

Click Add variable to save.

Use the token in CI scripts Rewrite the remote URL so that Git authenticates with the PAT.

Updated Deploy Job Example

deploy:
  stage: deploy
  extends: .unstable_branch_job
  script:
    - echo "Deploying to $TARGET_ENV with $APP_IMAGE for $APP_NAME"
    - |
      # Configure Git to use the new token
      # CI_SERVER_HOST is a predefined variable, e.g., gitlab.example.com
      git remote set-url origin "https://gitlab-ci-token:${GITLAB_PUSHER_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
      # Continue with the original logic
      cd manifests/overlays/$TARGET_ENV/ && kustomize edit set image $APP_NAME=$APP_IMAGE
      git config --local user.name "GitLab Runner"
      git config --local user.email "[email protected]"
      git commit -am "[auto ci] Update $APP_NAME image to $APP_IMAGE"
      git push origin "HEAD:$CI_COMMIT_REF_NAME"
  environment:
    name: $TARGET_ENV

Code Explanation

The command

git remote set-url origin "https://gitlab-ci-token:${GITLAB_PUSHER_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"

rewrites the origin remote to embed the token. gitlab-ci-token is the fixed username recommended by GitLab; ${GITLAB_PUSHER_TOKEN} supplies the password.

Visualization

Sequence diagrams (not reproduced here) show the failure with the default identity and the successful push when using the PAT.

Default vs. PAT push flow
Default vs. PAT push flow

Conclusion

CI jobs cannot inherit the triggerer's git push permission for security reasons. The recommended approach is to create a dedicated Project Access Token with the write_repository scope and store it as a protected, masked CI/CD variable.

Best‑practice summary:

Use a Project Access Token as the CI job’s write credential.

Grant only the write_repository scope (least‑privilege).

Store the token in a protected and masked CI/CD variable.

In the script section, employ git remote set-url to authenticate pushes with the token.

CI/CDGitLabsecurityProject Access Token
Ops Development & AI Practice
Written by

Ops Development & AI Practice

DevSecOps engineer sharing experiences and insights on AI, Web3, and Claude code development. Aims to help solve technical challenges, improve development efficiency, and grow through community interaction. Feel free to comment and discuss.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.