Safeguard
Security Guides•

What can GitHub Actions secret masking not hide in logs?

Masking matches exact strings on the runner. Encoded, transformed or structured secrets can slip through. What to do about it, with a short checklist.

Safeguard Security Team
4 min read

Masking cannot hide a value it does not recognize. It redacts exact matches of registered secrets on the runner, so a transformed copy, a secret buried in a JSON blob, or a value never registered can appear in logs in plain text. Treat masking as a safety net, not a control.

How masking works

GitHub's guidance says redaction happens on the runner, so a secret is only redacted if it was used within a job and is accessible to the runner. Values stored as secrets are masked automatically. Other sensitive values can be masked with the add-mask workflow command, which treats the value as a secret and redacts it on the runner.

Two details from the workflow command reference are easy to miss. Masking must be done once per value per job, and you must register a value before you output it. And once a value is masked, you cannot set it as a step output.

What slips through

CaseWhy it is not hidden
A secret stored as a JSON, XML or YAML blobRedaction relies on exact matches, so printing one field of the blob does not match the whole secret
An encoded or transformed copy, such as base64 of the secretIt is a different string, unless you register it too
A value you generated or fetched at run timeNot a stored secret, so not masked unless you add the mask
Output from a tool that reformats the valueCase changes, splitting or escaping break the exact match
A secret passed in command line argumentsOther jobs on a shared runner may see it in the process list

GitHub's documentation addresses each of these. It recommends creating a separate secret for each sensitive value rather than one structured secret, registering any value derived from a secret as a secret too, and warns that secrets in command line arguments can be visible to other jobs on the same runner.

A checklist for safer logs

  1. One secret per value. Store a username, a password, and a key as three secrets, not as one JSON object.
  2. Register derived values. If a script base64 encodes a token, mask the result before it is used or printed.
  3. Mask run-time credentials. When a step fetches a short-lived token, call add-mask on it right away, before any command prints it.
  4. Use environment variables for secrets, not arguments. This keeps them out of process listings.
  5. Turn off debug output for jobs that handle secrets. Verbose modes can print more than you expect.
  6. Never print secrets to confirm they are set. Check for emptiness instead.
  7. Review third-party actions for logging of inputs. A compromised or careless action can print what it receives, in a form masking does not match.

When a secret does leak

GitHub's advice is direct: if a secret reaches a log, delete the log and rotate the secret. Deleting the log is not enough on its own, because anyone who read it, or any system that copied it, has the value. Rotation ends the exposure. Plan for that before you need it, so the rotation steps are written down and tested.

Read access matters too. Anyone with write access to a repository can read its secrets, so the people who can edit workflows are part of your trust boundary. See protecting secrets in CI test logs and artifacts.

Prefer not to store secrets at all

Where you can, use short-lived credentials from OIDC rather than a stored cloud key. A token that expires with the job is a smaller loss if it appears in a log. See GitHub Actions OIDC to replace long-lived cloud secrets.

Quick check

Will masking hide a base64 version of my secret?

Not unless you register that encoded value too. GitHub's guidance says to treat derived values, including encoded forms, as secrets.

Is deleting a log enough after a leak?

No. Delete it, and rotate the secret. Assume the value was seen.

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.