GitLab Secrets Management
A secret is any value that grants access: passwords, API tokens, private keys, and database addresses with credentials. Leaked secrets cause real damage, because attackers scan public code for them around the clock. Good secrets management keeps these values out of the repository and gives each job only the access it needs.
Think of house keys. You never tape a key to the front door. You keep copies with trusted people, and you change the lock when a key goes missing.
Where Secrets Leak
RISKY SAFER ─────────────────────────── ─────────────────────────── Password typed in source code Masked CI/CD variable Token inside .gitlab-ci.yml Protected CI/CD variable Key file committed to the repo File-type variable Secret printed with echo in a job Secret kept out of logs Shared chat message with a token Short-lived token from a vault
Git history keeps every old commit. Deleting a secret from the latest file leaves it readable in earlier commits.
Layer 1: CI/CD Variables
Variables in Settings → CI/CD → Variables hold secrets outside the repository. Apply three safeguards to each secret.
| Safeguard | Effect |
|---|---|
| Mask | Hides the value in job logs |
| Protect | Limits the value to protected branches and tags |
| Environment scope | Gives production secrets only to production jobs |
Use the File type for certificates and key files. GitLab writes the value to a temporary file and passes the path to the job.
deploy:
script:
- ssh -i "$DEPLOY_KEY_FILE" deploy@server.example.com "./release.sh"
Layer 2: Scoped Tokens
| Token Type | Purpose | Good Practice |
|---|---|---|
| CI job token | Lets a pipeline access GitLab resources | Restrict which projects may use it |
| Project access token | Automation for one project | Choose the lowest role and set an expiry date |
| Deploy token | Read access for servers | Grant read_repository or read_registry only |
| Deploy key | SSH access to a repository | Enable write access only when required |
| Personal access token | Your own scripts | Use short lifetimes and narrow scopes |
A token tied to a person stops working when that person leaves the team. Project tokens avoid this surprise.
Layer 3: Secret Detection
Secret detection scans commits for patterns that look like keys and passwords. Add the GitLab template to the pipeline.
include: - template: Security/Secret-Detection.gitlab-ci.yml
Findings appear in the merge request security widget and the vulnerability report. A detected secret deserves the same response as a real leak.
Layer 4: External Vaults
Large teams store secrets in a dedicated service such as HashiCorp Vault. A pipeline job proves its identity with a short-lived ID token and fetches only the secrets it needs.
Job starts
↓
GitLab issues an ID token (signed, expires in minutes)
↓
Job sends the token to the vault
↓
Vault checks project, branch, and environment claims
↓
Vault returns the secret for this job only
read-secret:
id_tokens:
VAULT_ID_TOKEN:
aud: https://vault.example.com
secrets:
DB_PASSWORD:
vault: production/db/password@secrets
token: $VAULT_ID_TOKEN
file: false
script:
- ./migrate.sh
The vault stores the long-lived secret. The pipeline never stores it. The same ID token approach lets jobs sign in to cloud providers without static access keys.
If a Secret Leaks
- Revoke or rotate the secret at once at its source, such as the cloud console or the token page.
- Check access logs for signs of misuse.
- Replace the value in CI/CD variables.
- Remove the secret from the repository history with a rewriting tool and inform the team.
- Add a detection rule or template so the mistake triggers an alert next time.
Rotation comes first. Cleaning the history helps little while the old secret remains valid.
Keep Secrets Out of Logs
BAD: - echo "Token is $API_TOKEN" BAD: - env BAD: - set -x (prints every command with expanded values) GOOD: - curl --header "PRIVATE-TOKEN: $API_TOKEN" "$API_URL"
Masking catches many accidents, but transformed values such as base64 copies slip through. Avoid printing secrets in any form.
Secrets in Local Work
- Store local settings in a .env file and list the file in .gitignore.
- Commit a .env.example file that shows variable names without values.
- Use a password manager for personal tokens.
- Enable two-factor authentication on your GitLab account.
Review Checklist
| Question | Healthy Answer |
|---|---|
| Does the repository contain any passwords? | No, scans run on every merge request |
| Are production secrets protected? | Yes, limited to protected branches and the production scope |
| Do tokens expire? | Yes, with dates on the calendar for renewal |
| Does each token carry minimum permissions? | Yes, reviewed every quarter |
| Does a written plan exist for leaks? | Yes, and the team practiced it |
