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_repositoryandwrite_repositoryfor Git operations over HTTPSread_apifor read-only automation and dashboardsapifor full programmatic control (grant this sparingly)read_registryandwrite_registryfor 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:
- 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. - CI logs. A
curlcommand that echoes the token, or a debugset -xthat prints the full command line. - 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.