# What is a masked CI variable?

> A masked CI variable is one whose value the CI system hides in job logs. What masking and protected variables do, and the ways values still get out.

URL: https://crunu.com/learn/masked-variable
Language: en
Updated: 2026-10-30

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](https://docs.github.com/en/actions/concepts/security/secrets)). 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](https://docs.gitlab.com/ci/variables/)). 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](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments)).

## 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.

*Figure: Diagram of where a secret goes once it is in CI logs. CI provider’s log storage: kept for the retention period; it stays after you delete it. Log forwarding: your log platform keeps its own copy; it stays after you delete it. Artifacts and caches: files the job uploads. Everyone who can open the job: read access to the project is often enough. Deleting the copy doesn’t undo the leak, so the value has to be reissued at its provider.*

## 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](https://crunu.com/blog/masked-ci-variables-still-leak), and the full CI setup in [CI/CD secrets with OIDC](https://crunu.com/guides/ci-cd-secrets-with-oidc).

## Sources

- [GitLab Docs: CI/CD variables](https://docs.gitlab.com/ci/variables/), checked October 30, 2026.
- [GitHub Docs: Secrets](https://docs.github.com/en/actions/concepts/security/secrets), checked October 30, 2026.
- [GitHub Docs: Managing environments for deployment](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments), checked October 30, 2026.
