Skip to content
LearnMasked CI variable

What is a masked CI variable?

Also known assecret maskingprotected variable
Example. Kiteline is a fictional company.
On this page

A masked CI variable is a pipeline variable whose value the CI system replaces with a placeholder wherever that exact value appears in a job's log output, so that it doesn't show up in plain text for people reading the logs.

What masking does

GitLab replaces a masked value in job logs with [MASKED]. GitHub Actions "automatically redacts the contents of all GitHub secrets that are printed to workflow logs" (GitHub Docs). Both work the same way underneath: the runner knows the value and searches the output for it.

Related settings often come with it:

  • Protected variables in GitLab are "available only to pipelines that run on protected branches or protected tags" (GitLab Docs). This limits which code can read them at all, which matters more than masking.
  • Hidden variables in GitLab can't be revealed in the settings page again after they are saved.
  • Environment secrets in GitHub are available only to jobs that use that environment, after its protection rules pass (GitHub Docs).

What masking doesn't do

Masking is a display filter on logs, not access control. Both vendors say so:

  • GitLab: "Masking a CI/CD variable is not a guaranteed way to prevent malicious users from accessing variable values", and "if a process outputs the value in a slightly modified way, the value can't be masked."
  • GitHub: "Because there are multiple ways a secret value can be transformed, this redaction is not guaranteed", and "the runner can only redact secrets used within the current job."

In practice, values get out when they are base64-encoded, split, URL-escaped or embedded in JSON before printing; written into an artifact, cache or test report instead of the log; or sent over the network by a dependency or script that runs in the job. Every process in a job that receives a job-wide variable can read it.

GitLab also has format rules: a masked value must be a single line with no spaces, at least 8 characters, and with variable expansion enabled only a limited set of symbols is allowed. Some real secrets don't qualify.

Example. Kiteline is a fictional company.

Common mistakes

  • Masking instead of protecting. A masked production key available to every branch is readable by any merge request that can run a pipeline.
  • Job-wide variables. Set credentials on the step that needs them.
  • Debug modes. GitLab warns that with CI_DEBUG_SERVICES enabled, values might be revealed.
  • Treating a masked log as clean. If a value was printed in transformed form, it's in the log.

How Crunu fits

Crunu doesn't replace your CI's masking, and it can't stop a job from printing what it received. Two things help. A CI service token gets only the environments it is granted, with OIDC binding to the repository and branch, so a test job doesn't hold production values in the first place. And crunu run passes values only to the command it starts, not to the whole job; it never passes its own credentials, such as CRUNU_TOKEN, to that command.

For production, the recommended setup keeps secrets out of CI entirely: run the crunu agent on the production machine, and let the pipeline ship code and restart the service. The longer argument is in why masked CI variables still leak, and the full CI setup in CI/CD secrets with OIDC.

Sources

From the blog

  • CI/CD

    Masked CI variables still leak: where masking stops

    CI masking replaces exact strings in the job log, nothing more. Encoded values, JSON blobs, artifacts and untrusted pull requests all get past it.

    CI and OIDC guide

  • CI/CD

    Giving CI only the secrets it needs

    Long-lived CI tokens work from anywhere until revoked. How to scope pipeline secrets, keep production out of CI, and bind tokens to a repo with OIDC.

    CI and OIDC guide

  • CI/CD

    Bitbucket Pipelines OIDC: the step token explained

    What oidc: true gives a Bitbucket step: the BITBUCKET_STEP_OIDC_TOKEN, the workspace issuer, audiences, the sub claim and how to pin a trust policy to it.

    CI and OIDC guide