check-secrets.yml
Scan the repository’s git history for committed credentials – API tokens, private keys, high-entropy password assignments – using gitleaks. check-phi detects identifiers and has no notion of a credential, so a committed password or token passes it cleanly; this is the counterpart that catches those.
History rather than the diff, unlike every other check here. A secret committed and then removed in a later commit is still exposed, because the orphaned commit stays fetchable through the GitHub API until the repository is garbage-collected. So the reusable workflow checks out with fetch-depth: 0, and a shallow clone is refused rather than reported clean on a partial scan.
Wider than “history” suggests, too. gitleaks’ default for its git subcommand is git log -p -U0 --full-history --all, so an empty log-opts scans every ref the checkout holds – every branch and tag fetch-depth: 0 fetched – rather than only HEAD’s ancestry. A finding can therefore name a commit that is not an ancestor of the pull request’s own head. That is the right behaviour for a credential: an exposed one is exposed wherever it sits.
Matched values are never printed to the log or the run summary; findings report only the rule, file:line, and the commit. Blocking by default, unlike the advisory prose checks.
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
version |
string | '8.30.1' |
gitleaks release version to install, without the leading v. |
checksums-sha256 |
string | pinned | Expected SHA-256 of the release’s own gitleaks_<version>_checksums.txt; bump alongside version. |
config |
string | '' |
Path to a gitleaks TOML config with extra rules or allowlists; defaults to .gitleaks.toml when present. The generated config extends it. |
paths-ignore |
string | '' |
Comma- or newline-separated file paths to skip. Go regexes, not globs, and matched unanchored – anchor with ^ when that matters. |
allowlist-file |
string | '' |
Path to a file of regexes, one per line; a finding whose matched text matches any is suppressed. Defaults to .github/secrets-allowlist.txt. |
log-opts |
string | '' |
git log options narrowing which commits are scanned; empty scans every ref the checkout holds, not only HEAD’s ancestry. |
fail |
boolean | true |
Fail the workflow when a possible secret is found; otherwise warn only. Read fail-closed – only an explicit false opts out. |
Permissions
Only contents: read (the default), so no permissions: block is needed.
Suppressing a false positive
Four options, roughly in order of preference:
- A
gitleaks:allowcomment on the offending line. - The finding’s fingerprint in a
.gitleaksignorefile at the repository root. The run summary prints it ready to paste; paste it whole, because gitleaks matches these by exact lookup rather than by prefix, so an entry built from a shortened commit SHA is accepted silently and never matches. - A regex in the
allowlist-file. One per line; a comma there is ordinary regex syntax rather than a separator, so a{16,20}quantifier is safe. - A path in
paths-ignore. Remember these are unanchored Go regexes:docssuppresses every path containing that substring.
Two things this is not
It complements GitHub’s native secret scanning rather than replacing it. That is a repository setting, it evaluates pushes rather than running as a pull-request check, and no reusable workflow can supply it. Enable both.
And neither substitutes for rotating an exposed credential. Rewriting history does not un-expose one. Treat any value this check names as compromised.
Why not gitleaks/gitleaks-action
The vendor’s own action is proprietary: its action.yml carries a commercial EULA header, and its README states GITLEAKS_LICENSE is required for organization accounts. The gitleaks CLI itself is MIT, so the composite installs the official release binary and verifies it against a pinned SHA-256 of the release’s own checksums file.
Example
# Copy to .github/workflows/check-secrets.yml in your repo.
# Scans the repository's git HISTORY for committed credentials.
# Only contents:read is needed (the default), so no permissions block is required.
name: Check for secrets
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
jobs:
check:
uses: Morrison-Lab/gha/.github/workflows/check-secrets.yml@v2
# with:
# paths-ignore: 'tests/fixtures/,docs/examples/' # Go regexes, not globs
# allowlist-file: '.github/secrets-allowlist.txt' # regexes to suppress
# config: '.gitleaks.toml' # extra rules
# fail: false # warn instead of failingSee check-secrets.yml in examples/ for the full caller stub. (Linked to the directory rather than the file, because a blob/main/... link to a capability’s own example 404s on the pull request that introduces it – the file does not exist on main until that PR merges, and check-links rightly fails it. The sibling pages can use the direct form because their targets already landed.)