Redact secrets inside your CLI before anything is written, and treat the CI platform's masking as a backstop. GitHub Actions masks a value only after it is registered with add-mask, and redaction relies on matching the exact value. Anything transformed, split or printed earlier can slip through.
What masking does
GitHub's workflow commands documentation says that ::add-mask::VALUE stops a string or variable from being printed in the log. Each masked word is replaced with asterisks. It adds that you must register the secret before outputting it in logs or using it in other workflow commands, and that masking is per value per job.
The security hardening guide adds a limit that affects tool authors. Redaction largely relies on finding an exact match for the specific secret value, so structured data such as a JSON blob stored as a secret can cause redaction to fail. It also says that if an unredacted secret reaches a log, you should delete the log and rotate the secret.
Four ways a CLI defeats masking
- Printing before registering. Your tool starts, logs its configuration, and only later does the workflow mask the value.
- Transforming the value. Base64, URL encoding, truncation or splitting across lines produces a different string that does not match.
- Structured secrets. A token embedded in a larger JSON secret is not matched as its own value.
- Derived values. A signed URL built from a token contains the token in a new form.
What to build into the CLI
- Keep a single logging function. Every message passes through it.
- Maintain a list of values to hide, filled when credentials are loaded. Replace each occurrence with a fixed marker.
- Redact common transformed forms of each secret, at least the encoded form your own code produces.
- Never print raw request or response headers. Print an allowlist of safe header names instead.
- Hide query string values in URLs you log.
- Make verbose mode safe. Debug output is where secrets usually leak, because people turn it on in a hurry during an incident.
Ask the CI platform for help too
If your tool produces a value at run time, such as a short-lived token, the workflow can register it. GitHub documents masking a value generated in a job, then passing it on through an output file instead of printing it. If your CLI has a mode for CI, consider printing a mask command line for any credential it creates, in the format the platform expects.
Test it
Write a test that sets a fake token, runs the tool in its noisiest mode, and searches the captured output for the token and for its encoded forms. Run it in your own release pipeline.
If a leak happens
Delete the affected logs, rotate the secret, and check where else the value could have gone, such as uploaded artifacts and error reports. Say so in your release notes if your tool caused it.
Quick check
Does masking remove a secret that was already printed?
No. Register the value before it can appear. If a secret reached a log, delete the log and rotate the secret, as GitHub's guidance says.
Why did masking miss my token?
Likely the printed string differs from the registered value, for example encoded or part of a larger structured secret. Masking relies on exact matches.
Safeguard offers a free scan of your dependencies at gold.safeguard.sh/scan, useful for seeing what third-party code sits in your CLI.
Sources
Last reviewed: 19 October 2026.