GitLab 19.0 Secrets Manager: job-scoped CI secrets without a large vault migration

GitLabCI/CDSecretsDevSecOpsSecurity

GitLab security automation uses temporary job-scoped access and an auditable rollout instead of long-lived secrets

Hook

Secrets rarely break a system loudly. More often, they quietly accumulate in CI/CD variables, .env files, old deploy jobs, and group-level settings that nobody wants to touch anymore.

GitLab 19.0 moved GitLab Secrets Manager into public beta. The point is not that GitLab suddenly replaces every vault system. The practical value is a smaller first step: move the riskiest CI secrets away from broad variables and give them only to jobs that actually need them.

Problem / Context

A typical GitLab pipeline starts innocently: someone adds DATABASE_PASSWORD, DEPLOY_TOKEN, AWS_SECRET_ACCESS_KEY, or NPM_TOKEN to project variables, masks the value, and the pipeline works. Six months later, the same secret is available to too many jobs, environments, or branches.

The problem is not that CI/CD variables are bad. They are useful and still fit many simple cases. The problem starts when a secret lives at group or project level while the real access boundary is much narrower: only a deploy job, only a protected branch, only a production environment.

GitLab’s release angle is aimed at that exact situation. Instead of treating a secret as a broad pipeline variable, a job explicitly requests the secret through secrets: in .gitlab-ci.yml. The secrets backend checks the job identity, branch, environment, and protected status, then returns the value only for that run.

Why It Matters

A broad secret increases the blast radius. If a dependency inside a pipeline is compromised, a runner behaves unexpectedly, or someone gets access to job logs or shell output, the team has to reason about everything that secret could reach.

A job-scoped model reduces that panic. If a production deploy token is available only to a deploy job on a protected branch targeting a production environment, a test-job incident does not automatically mean rotating every production credential.

There is also a practical operations benefit. GitLab Secrets Manager follows the same project/group model where teams, roles, and permissions already live. For a small team, that may be less painful than immediately operating a separate Vault, separate authentication model, separate audit stream, and separate onboarding process.

But the beta label matters. This is not a reason to migrate every critical production secret in one evening. It is a reason to run a careful pilot where the current CI/CD variable is already clearly too broad.

How To Do It

1. Find over-scoped variables

Start without changing the pipeline. Export or review the list of GitLab variables without values and mark three things:

  • where the secret is defined: group or project;
  • which jobs actually read it;
  • which branches and environments should be allowed to use it.

Good pilot candidates:

  • deploy tokens;
  • registry credentials;
  • cloud credentials;
  • database migration passwords;
  • signing keys;
  • tokens for security or release tooling.

A bad first pilot candidate is the most critical production secret that blocks the whole release if anything goes wrong.

2. Check constraints before the pilot

Before the .gitlab-ci.yml diff, verify the boring conditions:

  • GitLab Secrets Manager is currently in public beta;
  • GitLab Premium or Ultimate is required;
  • GitLab.com and self-managed deployments have different opt-in steps;
  • self-managed deployments require OpenBao;
  • jobs need GitLab Runner 19.0+;
  • pricing after beta may change through GitLab Credits, so it belongs in the rollout plan.

This is the list you want to discover before the demo, not during a production incident.

3. Make one secret job-scoped

In the minimal version, a job explicitly requests a secret:

deploy:
  stage: deploy
  environment: production
  secrets:
    DATABASE_PASSWORD:
      gitlab_secrets_manager:
        name: db-password
  script:
    - deploy --password "$DATABASE_PASSWORD"

GitLab can provide the secret as a temporary file and expose the file path through an environment variable. That is often safer than passing the raw value into subprocesses, crash dumps, or telemetry.

For a real rollout, do not stop at YAML. Define the scope: environment, branch, and protected branch condition. Otherwise, you have moved the secret but not meaningfully narrowed access.

4. Keep rollback nearby

Beta means rollback should be boring and fast.

Rollback plan:

  1. keep the old CI/CD variable disabled or temporarily renamed;
  2. keep the .gitlab-ci.yml merge request small;
  3. test deploy on a non-production environment;
  4. keep a manual job that can restore the old variable path;
  5. write down which credentials must be rotated if the pilot misbehaves.

Rollback does not mean the pilot failed. It is normal rollout hygiene for a security feature that touches releases.

5. Do not mix the pilot with a large secrets refactor

The temptation is obvious: since we are already touching secrets, let us remove old variables, rename environments, rewrite deploy jobs, and clean up runners at the same time.

Do not.

The first pilot should answer one question: can one job receive one secret through GitLab Secrets Manager with the right scope and a clear audit trail? If the answer is yes, plan a second wave.

Anti-Patterns

  • Migrating every secret at once because the feature looks right.
  • Replacing CI/CD variables with Secrets Manager without narrowing branch/environment scope.
  • Forgetting to verify GitLab Runner 19.0+.
  • Ignoring OpenBao requirements for self-managed deployments.
  • Creating a group-level secret where project-level access is enough.
  • Treating a beta feature as a mature external Vault replacement for every regulated workload.
  • Changing the deploy job without preparing rollback first.

Conclusion / Action Plan

GitLab Secrets Manager in 19.0 is best treated as a practical tool for reducing CI secret sprawl, not as a magic replacement for every secret-management process.

A healthy minimum rollout:

  1. find over-scoped CI/CD variables;
  2. choose one real but non-critical secret;
  3. verify tier, beta opt-in, runner, and self-managed prerequisites;
  4. define branch/environment scope;
  5. add secrets: to one job;
  6. verify audit trail and runtime behavior;
  7. document rollback;
  8. only then expand to production secrets.

Official sources:

Quick checklist

  • Find secrets currently stored in group/project CI/CD variables.
  • Separate production secrets from dev/test secrets.
  • Mark jobs that truly need explicit secret access.
  • Check GitLab tier, beta opt-in, and GitLab Runner 19.0+.
  • For self-managed, check OpenBao prerequisites.
  • Run a pilot with one non-critical secret.
  • Document rollback to the current workflow.

Prompt Pack: audit GitLab CI secrets

Help audit secret usage in GitLab CI/CD. Input data: - list of GitLab projects/groups; - relevant .gitlab-ci.yml jobs that use secrets; - project/group CI/CD variables without secret values; - environments and protected branches; - GitLab tier and hosting model: GitLab.com or self-managed; - whether GitLab Runner 19.0+ is available; - whether Vault, OpenBao, or a cloud secrets manager is already used. Return: 1. which secrets are currently over-scoped; 2. which jobs should request a secret explicitly; 3. a minimal pilot for GitLab Secrets Manager; 4. a sample secrets: block for .gitlab-ci.yml; 5. beta and self-managed risks; 6. rollback plan to CI/CD variables or an external secrets manager. Response format: verdict, risk table, pilot plan, YAML diff, rollback.