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.

SafeguardEffect
MaskHides the value in job logs
ProtectLimits the value to protected branches and tags
Environment scopeGives 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 TypePurposeGood Practice
CI job tokenLets a pipeline access GitLab resourcesRestrict which projects may use it
Project access tokenAutomation for one projectChoose the lowest role and set an expiry date
Deploy tokenRead access for serversGrant read_repository or read_registry only
Deploy keySSH access to a repositoryEnable write access only when required
Personal access tokenYour own scriptsUse 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

  1. Revoke or rotate the secret at once at its source, such as the cloud console or the token page.
  2. Check access logs for signs of misuse.
  3. Replace the value in CI/CD variables.
  4. Remove the secret from the repository history with a rewriting tool and inform the team.
  5. 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

QuestionHealthy 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