Safeguard
DevSecOps

How to Secure a GitLab Personal Access Token (and Avoid Leaks)

A GitLab personal access token is a password-equivalent credential for the API and Git over HTTPS. Here is how to scope, rotate, and store one without turning it into a breach.

Marcus Chen
DevSecOps Engineer
6 min read

A GitLab personal access token is a password-equivalent credential that authenticates you to the GitLab API and to Git over HTTPS, so it needs the same care you would give a database password. Treat it casually and you have handed an attacker a key to your source code, your CI pipelines, and often your container registry. This guide walks through creating one with the right scopes, storing it so it never lands in a commit, and rotating it before it becomes a liability.

What a GitLab personal access token actually grants

When you generate a token under User Settings, Access Tokens, GitLab asks for three things: a name, an expiration date, and a set of scopes. Those scopes are the whole game. A token with api scope can do nearly everything your user account can do through the REST and GraphQL APIs, including deleting projects and reading every repository you can see. A token with only read_repository can clone and pull but not push or touch project settings.

The scopes you will reach for most often:

  • read_repository and write_repository for Git operations over HTTPS
  • read_api for read-only automation and dashboards
  • api for full programmatic control (grant this sparingly)
  • read_registry and write_registry for the container registry

The mistake teams make is defaulting to api because it "just works." A leaked full-api token is a full account compromise. A leaked read_repository token is a bad day, not a catastrophe.

Creating a token with least privilege

Give every token a name that says what it is for, so a stale entry in the list is obvious six months later. ci-image-push-2025 beats token1. Set the shortest expiration that fits the job. GitLab now enforces a maximum lifetime on personal access tokens in many configurations, and self-managed instances can cap it instance-wide, which is a policy worth turning on.

For a CI job that only needs to pull code, the correct answer is often not a personal access token at all but a project or group access token, or the job-scoped CI_JOB_TOKEN. Personal access tokens carry your identity and your full set of memberships. A project access token is bounded to one project, so its blast radius when leaked is far smaller.

Using the token with Git over HTTPS

The most common day-to-day use is cloning and pushing. You supply the token as the password when Git prompts, or embed it in the credential helper:

git clone https://oauth2:glpat-XXXXXXXXXXXXXXXX@gitlab.com/acme/backend.git

Do not leave the token in the remote URL like that permanently. It gets written to .git/config in plaintext and shows up in your shell history. Instead, use a credential helper so the token is stored in the OS keychain:

git config --global credential.helper osxkeychain   # macOS
# or on Linux:
git config --global credential.helper libsecret

The personal access token git workflow is nearly identical across providers. A github personal access token is used the same way (as the HTTPS password), and GitHub's fine-grained tokens mirror GitLab's scoping model. A personal access token bitbucket (Bitbucket calls these app passwords or repository access tokens) follows the same pattern. If you script against several forges, keep each token in a named entry in your secrets manager rather than juggling environment variables.

Where tokens leak, and how to stop it

Tokens leak in predictable places. The big three:

  1. Committed to a repo. A .env, a Terraform variables file, or a hardcoded CI script. Once pushed, assume it is compromised even if you force-push it away, because it lives in the reflog and any clone.
  2. CI logs. A curl command that echoes the token, or a debug set -x that prints the full command line.
  3. Screenshots and pasted snippets. Support tickets and chat messages are a surprisingly common source.

The GitLab token prefix glpat- exists precisely so scanners can catch these. Push protection and secret detection scanning will flag a glpat- string before it ever reaches the default branch. Enable secret push protection on your projects and it becomes much harder to leak one by accident. On the pull side, an SCA and secrets pipeline can catch tokens that already made it into history so you can revoke them.

If a token does leak, the fix is not "delete the commit." It is: revoke the token in Access Tokens, generate a replacement, and update every consumer. Revocation is instant and cannot be undone, which is exactly what you want. For a broader treatment of pipeline hygiene, our DevSecOps academy material covers secret scanning gates in more depth.

Rotation without downtime

Short expirations only help if rotation is painless. The pattern that works: create the new token before the old one expires, deploy it to every consumer, verify traffic is flowing on the new credential, then revoke the old one. For CI, store the token in a masked and protected CI/CD variable so it is never printed and is only exposed to protected branches.

Automate the reminder. GitLab emails you seven days before a token expires, but an expired token that silently breaks a nightly deploy is a bad way to find out. Track expirations in whatever your team uses for on-call, not just an inbox rule.

Auditing tokens across the org

On a self-managed instance, an admin can list all personal access tokens and their scopes and expirations through the admin API. Run that audit periodically. You are looking for tokens with api scope that should be read-only, tokens with no expiration on older instances, and tokens belonging to people who have left. Service accounts should own automation tokens, not individual humans, so that an offboarding does not break production. A software composition tool such as Safeguard can correlate a leaked token finding with the repositories and pipelines it touched, which shortens the "what did this credential have access to" question during an incident.

FAQ

Is a GitLab personal access token the same as an SSH key?

No. An SSH key authenticates Git over the SSH protocol and cannot be used against the HTTP API. A personal access token authenticates the API and Git over HTTPS. Many teams use SSH keys for developer Git access and personal access tokens strictly for API automation, which keeps the two concerns separate.

What scope does a personal access token need just to clone and push?

read_repository for clone and pull, and write_repository to push. You do not need api scope for ordinary Git operations, and granting it unnecessarily widens the damage if the token leaks.

How is this different from a GitHub personal access token?

The concept is identical: a string used as the HTTPS password and API credential. GitHub's fine-grained personal access tokens let you restrict a token to specific repositories, which is close to GitLab's project access tokens. The git personal access token workflow (supply it as the password) is the same across GitLab, GitHub, and Bitbucket.

What should I do the moment a token leaks?

Revoke it immediately in Access Tokens, then issue a replacement and update every consumer. Do not rely on deleting the commit that exposed it, because the value persists in Git history and any existing clone.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.