GitLab CI/CD Pipeline & Access Tokens Security Prompt
Manage and secure GitLab tokens — trigger tokens, project access tokens, group access tokens, $CI_JOB_TOKEN scope, leak detection and rotation.
- Target user
- DevOps engineers managing GitLab access tokens
- Difficulty
- Intermediate
- Tools
- Claude, ChatGPT
The prompt
You are a senior DevOps engineer / security engineer who has audited and rotated GitLab tokens at scale. You know the difference between trigger tokens, deploy tokens, project access tokens, $CI_JOB_TOKEN, and personal access tokens — and which ones leak in practice. I will provide: - The use case (CI to trigger downstream, CI to call API, external system to call GitLab, deploy authentication) - Current token type in use (if any) - Token storage approach (variables, external secret manager) - Rotation policy / current state - The symptom (if debugging: leak, 401, scope-too-narrow, scope-too-wide) Your job: 1. **Map token type to use case**: - **`$CI_JOB_TOKEN`** — auto-generated per job; scope to specific projects' APIs (Settings → CI/CD → Job token permissions); preferred for in-instance API calls - **Pipeline trigger token** — project-scoped; can ONLY create pipelines; for external CI systems firing pipelines - **Project access token (PrAT)** — project-scoped; configurable scopes (api, read_repository, etc.); has an expiry; for project-bound automations - **Group access token (GrAT)** — group-scoped variant of PrAT - **Personal access token (PAT)** — user-scoped; broadest power; should be avoided for shared automation - **Deploy token** — project-scoped; read-only to repo/registry by default; for deploy reads - **Deploy key** — SSH-based; pull or pull+push to one repo 2. **For storage**: - **Don't commit tokens to git**, ever - **Don't paste in chat / email / tickets** - **GitLab variables (masked)** — adequate for general use; rotation requires manual update - **External secret manager (Vault, AWS SM)** — preferred; CI pulls at runtime - **`.env` files with sealed-secrets pattern** — for GitOps-like flows 3. **For rotation**: - Project / group access tokens have built-in expiry (1d to 1y) - Trigger tokens are revocable but not auto-expiring - PATs have expiry - Plan rotation: create new token → update consumers → revoke old → verify 4. **For audit / leak detection**: - GitLab Premium has audit events for token usage - Self-managed: review `production_json` logs for token use patterns - Secret scanning (GitLab built-in) catches tokens in commits — alerts in Security Dashboard - GitHub-style: if a token leaks to GitHub, GitLab's GH integration may revoke 5. **For least-privilege scoping**: - `$CI_JOB_TOKEN` permissions (newer GitLab): allowlist specific projects allowed to use this project's job token - PrAT scopes: pick minimum needed (`read_api` vs `api`) - Avoid `api` scope where `read_api` works 6. **For multi-project / multi-tenancy**: - Group access tokens for org-wide automation - Project tokens for project-specific - Avoid PAT in shared automation; the user can leave the org 7. **For incident response (suspected leak)**: - Revoke token immediately via API or UI - Check audit log / pipeline log for unexpected use - Rotate downstream dependents - Consider: was the token used in places the legitimate user wouldn't have triggered? 8. **For automated rotation**: - PrAT/GrAT can be rotated via API - Update GitLab variable AND consumer simultaneously - Use a wrapper script or rotation tool Mark DESTRUCTIVE: revoking a token in active use without notifying consumers, generating a PAT with `api` scope for a long-lived integration (broad blast radius), using a service-account-style PAT (single user) for team-shared automation. --- Use case: [DESCRIBE] Current token type: [DESCRIBE] Storage: [DESCRIBE] Rotation policy: [DESCRIBE] Symptom (if debugging): [DESCRIBE]
Why this prompt works
GitLab tokens are easy to provision but hard to govern. Many teams use PATs everywhere, which creates user-dependency, broad scopes, and rotation headaches. This prompt enforces token-type-by-use-case.
How to use it
- Match token to use case — see the cheat sheet.
- Start with least scope —
read_apibeforeapi. - Plan rotation — set expiry, automate updates.
- Audit usage — Premium audit events or log review.
Useful commands
# List project access tokens
curl --header "PRIVATE-TOKEN: <t>" \
"https://gitlab.example.com/api/v4/projects/<id>/access_tokens" | jq
# Create a project access token (limited scope)
curl --request POST --header "PRIVATE-TOKEN: <t>" --header "Content-Type: application/json" \
--data '{
"name": "deploy-token-2026",
"scopes": ["read_api","read_repository"],
"access_level": 30,
"expires_at": "2027-05-30"
}' \
"https://gitlab.example.com/api/v4/projects/<id>/access_tokens" | jq
# Rotate a project access token (1 API call; old becomes invalid, new returned)
curl --request POST --header "PRIVATE-TOKEN: <t>" \
"https://gitlab.example.com/api/v4/projects/<id>/access_tokens/<token-id>/rotate" | jq
# Revoke
curl --request DELETE --header "PRIVATE-TOKEN: <t>" \
"https://gitlab.example.com/api/v4/projects/<id>/access_tokens/<token-id>"
# List trigger tokens
curl --header "PRIVATE-TOKEN: <t>" \
"https://gitlab.example.com/api/v4/projects/<id>/triggers" | jq
# Job token allowlist (newer GitLab)
# Settings → CI/CD → Job token permissions
# API: PATCH project's ci_cd_settings
curl --request PATCH --header "PRIVATE-TOKEN: <t>" --header "Content-Type: application/json" \
--data '{"ci_inbound_job_token_scope_enabled":true}' \
"https://gitlab.example.com/api/v4/projects/<id>"
Token cheat sheet
| Use case | Token type | Why |
|---|---|---|
| CI calls own project’s API | $CI_JOB_TOKEN | Auto-generated, scope-aware |
| CI triggers downstream pipeline (same instance) | $CI_JOB_TOKEN or trigger token | JOB_TOKEN if target’s allowlist set |
| External system triggers pipeline | Trigger token | Project-scoped, limited |
| CI pushes to another project’s repo | PrAT or deploy key | Scope: write_repository |
| External tool reads project metadata | PrAT (read_api) | Least privilege |
| Org-wide automation | Group access token | Spans projects in group |
| User-bound personal scripts | PAT | Tied to user; usually not for shared automation |
| Image registry pull (CI) | Deploy token | read_registry |
| Git pull-only mirror | Deploy key (SSH) | No web token leaks |
Patterns
Hot-swap rotation
# 1. Create new token (note expiry on old)
NEW_TOKEN=$(curl -X POST -H "PRIVATE-TOKEN: $ADMIN_PAT" \
-d '{"name":"rotation-2026-05","scopes":["read_api"],"expires_at":"2026-08-30"}' \
"$GITLAB/api/v4/projects/$PROJECT_ID/access_tokens" | jq -r .token)
# 2. Update consumer's variable / secret
curl -X PUT -H "PRIVATE-TOKEN: $ADMIN_PAT" \
-d "value=$NEW_TOKEN" \
"$GITLAB/api/v4/projects/$PROJECT_ID/variables/MY_TOKEN"
# 3. Verify consumer can authenticate (test job or healthcheck)
# 4. Revoke old token
curl -X DELETE -H "PRIVATE-TOKEN: $ADMIN_PAT" \
"$GITLAB/api/v4/projects/$PROJECT_ID/access_tokens/$OLD_TOKEN_ID"
Using $CI_JOB_TOKEN for cross-project API calls
In the target project’s Settings → CI/CD → Job token permissions → Allowlist source projects.
# In source project's .gitlab-ci.yml
trigger-other:
script:
- curl --request POST \
--header "JOB-TOKEN: $CI_JOB_TOKEN" \
"https://gitlab.example.com/api/v4/projects/<target-id>/jobs/<job-id>/play"
External secret manager pull
deploy:
script:
- |
# Retrieve token from Vault using OIDC auth
VAULT_TOKEN=$(curl -s -X POST -d "{\"jwt\":\"$CI_JOB_JWT\",\"role\":\"my-role\"}" \
$VAULT_ADDR/v1/auth/jwt/login | jq -r .auth.client_token)
DEPLOY_TOKEN=$(curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
$VAULT_ADDR/v1/secret/data/deploy-token | jq -r .data.data.token)
./deploy.sh
variables:
VAULT_ADDR: https://vault.example.com
Common findings this catches
- PAT used in shared CI when user leaves → pipelines break.
apiscope used whereread_apiwould suffice — over-privileged.- Trigger token in
.gitlab-ci.ymlcommitted to git — public credential. - No expiry on PrAT — long-lived; against best practice.
- Token rotation breaks pipelines because consumers not updated atomically.
$CI_JOB_TOKENrejected by target project — needs allowlist permission.- Deploy token has
apiscope — should be onlyread_repository/read_registry.
When to escalate
- Suspected token leak → revoke immediately, audit usage, coordinate with security.
- Org-wide rotation campaign — coordinate with platform team; staged rollout.
- Compliance requires audit trail of token usage → enable Premium audit events; integrate with SIEM.
Related prompts
-
CI/CD Secret Exposure Review Prompt
Audit GitHub Actions, GitLab CI, CircleCI, or Jenkins pipelines for secret leaks — logged secrets, exfiltration via unscoped tokens, third-party action risks.
-
GitLab CI/CD Variables Debugging Prompt
Diagnose why a GitLab CI/CD variable is missing, masked oddly, expanded wrong, or scoped to the wrong environment — protected, masked, file-type, inheritance, environment scope.
-
GitLab Webhooks & External Triggers Debug Prompt
Set up and debug GitLab webhooks (project, group, system), external pipeline triggers, integration with external orchestrators, and delivery troubleshooting.