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:allow comment on the offending line.
  • The finding’s fingerprint in a .gitleaksignore file 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: docs suppresses 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 failing

See 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.)