Safeguard
Security Guides•

How do I respond when a secret leaks into a CI build log?

Revoke and replace the credential first, then delete the log, then check how it was used. A step by step runbook a small team can follow in under an hour.

Safeguard Security Team
4 min read

Treat the secret as compromised the moment it appears in a log. Revoke or rotate it at the provider first, then delete the log, then check the provider's audit trail for use you did not expect. Deleting the log alone does not help, because anyone who saw it already has the value.

Why order matters

GitHub's guidance on secrets is direct: if an unredacted secret is sent to a workflow run log, delete the log and rotate the secret. Redaction is done by the runner, and only works when the secret was used in the job and the runner could match it. Rotation is the step that actually ends the risk. Log deletion only limits further exposure.

Do rotation first because it is the only step that closes the window. Everything after is cleanup and learning.

The runbook

  1. Identify the secret and where it works. Which service issued it, what scopes it has, and which repositories or environments hold a copy.
  2. Revoke or rotate at the source. Create the new credential, update the secret in your CI settings, and revoke the old one. If the old one cannot be revoked without an outage, set a short deadline and say who owns it.
  3. Delete the exposed logs. GitHub lets anyone with write access delete a run's logs from the Actions page ("Delete all logs") or programmatically. On other platforms, check the docs for how to erase a job log.
  4. Search other places the value may have gone. Artifacts, cache entries, pull request comments, build summaries, and any log shipping tool.
  5. Review provider audit logs from the time the value first appeared. Look for use from unfamiliar addresses, new keys, new users, or changes to permissions.
  6. Fix the cause so it cannot recur. See the list below.
  7. Write down what happened, in a few lines. Time, secret, scope, fix, who checked.

Why it leaked: common causes

GitHub's secure use reference names several that apply broadly:

  • Structured data used as a secret. Redaction relies on exact string matches, so a blob of JSON or YAML defeats it. Store each sensitive value as its own secret.
  • Derived values not registered. A signed token generated from a key, or a Base64 or URL encoded copy of a secret, is a different string. Register it as a secret too, using the add-mask workflow command.
  • Error output from tools. Commands can print secrets to standard error in ways you do not expect. The docs advise reviewing logs after testing both valid and invalid inputs.

GitLab's docs add that masking is not a guaranteed way to stop a determined user from reading a variable, that a value must meet format rules to be masked at all (single line, at least 8 characters), and that a slightly modified output, such as shell escaping, will not be masked.

Make the next one cheaper

  • Prefer short-lived credentials from OIDC over stored keys, so a leaked value expires on its own.
  • Scope each secret to the one job and environment that needs it.
  • Keep a list of every secret, who owns it and how to rotate it. A leak is a bad time to discover that nobody knows how.

Where Safeguard fits

After an incident, Safeguard can help on the dependency side: scanning your SBOM to check whether a compromised package was part of the build.

Quick check

Is deleting the log enough?

No. Anyone who read it, and any system that copied it, still has the value. Rotate first.

How do I know the leaked secret was not used?

Check the issuing service's audit log from the first moment the value appeared. If no audit log exists, assume it was used.

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.