1. Paste one or more workflow files
Copy the contents of each file under .github/workflows/ you want checked. Add more blocks for more files — nothing here reads your repository, so pasting is how it sees anything.
Paste at least one workflow file above and click Check.
Findings
| File : Line | Type | Detail | Fix |
|---|
Prefer a CLI, or want this in your own CI?
Same logic, zero dependencies, one file:
python3 check.py # scan .github/workflows under the current directory
python3 check.py /path/to/repo # scan a specific checkout
python3 check.py --format json # machine-readable
python3 check.py --format sarif # SARIF 2.1.0, for github/codeql-action/upload-sarif
python3 check.py --fail-on-risk # exit 2 if any finding exists
Get check.py from the repo — stdlib only, nothing to install, no network calls. Wire --fail-on-risk into your own CI to block a newly-added finding before it merges.
Common questions
Doesn't GitHub Actions already mask secrets in logs?
Yes, for the exact, unmodified string of a declared secrets.* value (or an env: var sourced from one) — the runner replaces every verbatim occurrence of that string in log output, including inside set -x trace output. This tool does not flag that case. It flags the two documented ways a value still reaches a log unmasked: transforming the secret first, and exporting a secret-derived value without registering it for masking.
Why does transforming a secret bypass masking?
GitHub's masking is an exact string match against the known secret value. base64, hex, case-folding, a substring, or any other re-encoding produces a different string than the one the runner is watching for, so the transformed output prints in plain text even though the original secret would have been masked.
Why is writing a secret to $GITHUB_ENV or $GITHUB_OUTPUT a separate risk?
Writing a value there exports it to later steps, but does not register it with the runner's masking list on its own. Only the exact string of a value GitHub already knows is a declared secret gets auto-masked. A freshly derived value — an OAuth token minted using a secret, a signed URL, a session cookie — is not on that list at all, so the first later step that echoes it prints it in plain text unless the workflow calls ::add-mask:: on it first.
Does this tool fetch anything from GitHub for me?
No. It makes no network calls at all — nothing here can, because nothing here talks to a server. Paste your workflow YAML and the scan runs entirely in this page.
Does a clean scan mean my secrets are safe?
No. This checks two specific, well-documented patterns within a single step's YAML text. It is not a security audit and makes no claim about your secrets' overall safety. A transform split across two steps, a custom action that re-exports a secret internally, or a transform utility this tool does not recognise are all outside what a regex/indentation scan can see.
More: why GitHub Actions secret masking fails on a transformed value, and this checker vs. reading the workflow by eye.