Safeguard
Security Guides•

GitLab CI masked versus protected variables explained

Masked variables hide values in job logs. Protected variables limit which pipelines can read them. Know what each does and why neither is a full defense.

Safeguard Security Team
3 min read

Masked variables hide a value in job logs. Protected variables are only available to pipelines on protected branches or tags. They solve different problems, so use both for sensitive values, and remember GitLab itself says masking is not a guaranteed way to stop a malicious user from reading a value.

Masked: log hygiene

When a variable is masked, its value shows as [MASKED] in job logs. GitLab lists requirements for a value to be maskable:

  • a single line with no spaces,
  • at least 8 characters,
  • not matching the name of an existing predefined or custom variable.

It also recommends turning off Expand variable reference for masked variables.

The limits are important. Output that is modified slightly, such as with escaped characters, is not masked. With CI_DEBUG_SERVICES enabled, values might be revealed. GitLab says masking is not a guaranteed way to prevent malicious users from accessing variable values.

Protected: who can read it

A protected variable is available only to pipelines running on protected branches or protected tags. A branch someone just created cannot read it. Merge request pipelines and merged results pipelines can optionally access them, per the docs, so check that setting.

This is the stronger control. It ties secrets to branches that need review to change. Combine it with protected branch rules.

Hidden: the UI side

The Masked and hidden option stops the value from being shown in the CI/CD settings UI. You can only set it when creating a variable, not on an existing one, and the value must meet masking rules.

File variables

A file type variable saves the value to a temporary file and puts the path in the environment variable. That suits tools that need a file, like kubectl. GitLab also suggests file variables so commands such as env or printenv do not print the secret.

Precedence

If two places define the same name, order decides. From highest: pipeline execution or scan execution policy variables, manual job variables, pipeline variables, project variables, group variables, instance variables, dotenv report variables, then variables in .gitlab-ci.yml, deployment variables and predefined variables. A project variable beats one in the YAML file.

Where this goes wrong

GitLab warns that code in .gitlab-ci.yml can expose variables, accidentally in logs or maliciously to third parties. Anyone who can change the pipeline file on a branch with access can print a variable.

So:

  1. Mark secrets protected and masked.
  2. Keep sensitive values in settings, not in the YAML file.
  3. Review merge requests that change .gitlab-ci.yml before running them. See fork merge request pipelines.
  4. Prefer an external secrets manager. GitLab recommends connecting one.
  5. Rotate anything that appeared in a log.

Decision table

  • API token for deploy: protected and masked, and limited to the production branch.
  • Test credential for CI: masked, not protected, low privilege.
  • Certificate file: protected file variable.

The free Safeguard scan at gold.safeguard.sh/scan can show dependency risk in the project that uses these variables.

Quick check

Is a masked variable safe from a malicious pipeline change?

No. Code in the pipeline can transform and print the value in a way masking does not catch.

Can I make an existing variable hidden?

No. Hidden can only be set when the variable is created.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.