Secret Hygiene

Masking, Logging and Secret Hygiene in Workflows

The runner replaces every secret a job uses with *** in the log. The staging job of 3.11.3 tested how far that goes:

The staging job's log, filtered to the environment block and the four echo linesShell
gh api repos/{owner}/{repo}/actions/jobs/108068565047/logs --allow-escape-sequences \
  | cut -d' ' -f2- | grep -E '^(  SHELF|Region|Key|Reversed|Base64)'
Output
  SHELF_API_KEY: ***
Region ap-southeast-1, key ***
Key length 24
Reversed, first 6: c89b58
Base64: ***

The value and its Base64 form were masked; the variable was not. But the reversed string leaked six real characters: masking is string matching, so any other transformation (reversing, slicing, URL encoding, splitting into lines) shows the secret, and the length leaked too. Hence the rules:

Rotating the leaked demo secretShell
openssl rand -hex 12 | gh secret set SHELF_API_KEY && gh secret list
Output
SHELF_API_KEY   2026-09-25T12:31:31Z

Any signed-in user can read a public repository's logs, so treat every log line as published.