Skip to content

chore(secrets): replace detect-secrets baseline with gitleaks#6262

Merged
houko merged 3 commits into
mainfrom
retire-secrets-baseline
Jun 21, 2026
Merged

chore(secrets): replace detect-secrets baseline with gitleaks#6262
houko merged 3 commits into
mainfrom
retire-secrets-baseline

Conversation

@houko

@houko houko commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Why

.secrets.baseline was the single most merge-conflict-prone file in the repo. It was a 4296-line / 148 KB JSON snapshot pinning 534 high-entropy findings across 201 files, and none of the 534 were ever audited (every entry had is_secret: null) — so the file did no true-vs-false-positive triage. It functioned purely as a diff anchor: a frozen set of "known token-shaped strings". Any PR that added a fixture or example with a token-shaped string had to append to it, so parallel PRs collided constantly. The earlier line_number-drift mitigation (#5694) stopped CI from going red on pure line shifts, but it could not stop the underlying git merge text conflicts.

What changed

Replace detect-secrets with gitleaks. gitleaks scans against a fixed rule set (provider-specific key formats plus a guarded generic-api-key rule) and suppresses expected matches through path / regex / stopword allowlists in .gitleaks.toml. Those are rules, not a per-finding snapshot, so they change far less often and do not merge-conflict the way the enumerated baseline did.

  • .secrets.baseline deleted; .gitleaks.toml added. The path allowlist migrates the old detect-secrets exclude block (lockfiles, sdk/, openapi.json, dashboard dist/node_modules, snapshots, fixtures/, testdata/, doc images, examples). Two semantic regexes cover the bulk of prior false positives: *_env config fields hold an ENV-VAR NAME not a secret (^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$, underscore-required so real AWS AKIA… keys stay in scope), and the canonical jwt.io example signature. A short list of exact fake-credential literals from redaction/sanitization tests is allowlisted by value.
  • .github/workflows/secret-scan.yml rewritten to install a pinned, checksum-verified gitleaks binary (8.30.1) and run gitleaks dir . over the working tree. The pre-push-hook-presence assertion ([High] Git hooks & CI hygiene — rustfmt paths, openapi sha256, missing pre-push, unconsumed .secrets.baseline #5664 sub-finding 3) is kept.
  • Local hooks swapped to gitleaks. scripts/hooks/pre-commit now runs gitleaks protect --staged and soft-warns when gitleaks is absent — with no baseline to drift, a tool-less commit can no longer push stale state to main (the reason the detect-secrets path hard-errored), so the hard error is no longer warranted. .pre-commit-config.yaml swaps the Yelp detect-secrets repo for the gitleaks pre-commit hook (pinned v8.30.1).
  • Docs updated: CONTRIBUTING.md (hook table + secret-scanning section), CLAUDE.md (git-side hook description). coverage.yml paths-ignore now points at .gitleaks.toml. CHANGELOG.md notes the change under [Unreleased].

Verification

  • gitleaks dir . --config .gitleaks.toml over the full 36 MB tree → no leaks found, exit 0, on the pinned version (8.30.1) used by CI, so local and CI agree.
  • The initial config surfaced 26 false positives on the first local scan (docs config examples + intentional fake tokens in redaction/driver tests); each was inspected and confirmed fake before being allowlisted by rule or exact value — no real secret was suppressed.
  • The first CI run pinned gitleaks 8.21.2, whose default ruleset flags a further 11 docs/test fixtures that 8.30.x's refined defaults correctly skip — the .p8 format example in .github/SECRETS.md, fake -----BEGIN PRIVATE KEY----- round-trip data in a dotenv test, and fake ghp_/sk_live_ tokens in redaction/taint tests. All 11 were read and confirmed to be documentation or test fixtures, not real secrets, then the pin was moved to 8.30.1 so the version validated locally is the version CI runs. The 8.30.1 release assets were confirmed present before pinning.
  • TOML, YAML (all workflow/config files), and sh -n on the rewritten hook all parse clean.

Action required for the maintainer

The CI status-check name changes from detect-secrets scan to gitleaks scan. If branch protection on main lists the old check as required, update the required-checks list to the new name, otherwise merges will block on a check that no longer reports.

Notes

  • docs/issues/rustfmt-loses-spaced-paths.md mentions .secrets.baseline as a historical finding; left as-is since it's an archived issue record, not active config.
  • Bumping GITLEAKS_VERSION later means re-running gitleaks dir . locally and allowlisting any new false positives — the version comment in secret-scan.yml records this.

The detect-secrets `.secrets.baseline` was a 4296-line JSON snapshot pinning 534 high-entropy findings across 201 files, none of them audited (no `is_secret` verdict) — it functioned purely as a diff anchor. Every PR that added a token-shaped fixture had to append to it, which made it a structural merge-conflict magnet despite the earlier line_number-drift mitigation (#5694).

Replace it with gitleaks: a fixed rule set plus path/regex allowlists in `.gitleaks.toml`, which change far less often than a per-finding snapshot. CI scans the working tree (`gitleaks dir .`) as the gate of record; the pre-commit hook runs `gitleaks protect --staged` and soft-warns when gitleaks is absent — there is no baseline to drift, so a tool-less commit can no longer push stale state to main the way detect-secrets could.

- delete .secrets.baseline; add .gitleaks.toml with allowlists migrated from the detect-secrets exclude block
- rewrite .github/workflows/secret-scan.yml to install a pinned, checksum-verified gitleaks and scan the tree
- swap the detect-secrets pre-commit entries (scripts/hooks/pre-commit, .pre-commit-config.yaml) for gitleaks
- update CONTRIBUTING.md and CLAUDE.md; point coverage.yml paths-ignore at .gitleaks.toml

The CI check name changes from "detect-secrets scan" to "gitleaks scan"; branch-protection required-checks must be updated to match.
@github-actions github-actions Bot added size/XL 1000+ lines changed area/docs Documentation and guides area/ci CI/CD and build tooling labels Jun 21, 2026

@houko houko left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the diff (.gitleaks.toml, secret-scan.yml, pre-commit, .pre-commit-config.yaml, CONTRIBUTING.md, CLAUDE.md, coverage.yml). The migration is solid overall — the allowlist structure is well-reasoned and the soft-warn behavior in the hook correctly mirrors the rustfmt skip. Two items before this is merge-ready:

1. CHANGELOG.md entry is missing.
The PR body states "A CHANGELOG.md entry will be added referencing this PR number" but there is no CHANGELOG.md diff here. The pre-commit hook (check 2) will enforce (@user) attribution on staged [Unreleased] additions — just make sure the entry lands before the merge. If it's coming in a follow-up commit on this branch, that's fine; noting it so reviewers don't merge while it's still absent.

2. --verbose in the CI scan step will produce very large log output.
gitleaks dir . --config .gitleaks.toml --redact --no-banner --verbose — the --verbose flag prints each file path as it is scanned. Over a 36 MB tree this will generate thousands of lines of output per CI run. Unless the intent is to make the file list visible for audit purposes, consider dropping --verbose; --redact --no-banner already keeps secrets out of the logs and findings are still reported on exit.

Everything else looks good: version pinned to 8.21.2 with checksum verification, gitleaks protect --staged correctly wired in the pre-commit hook with the right soft-warn path, .pre-commit-config.yaml consistent with the CI pin, CLAUDE.md description accurate, coverage.yml paths-ignore updated. The \.lock$ catch-all and the ^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$ ENV-var-name regex are both carries from the old detect-secrets exclude config and are appropriately narrow in context.


Generated by Claude Code

CI pinned 8.21.2, but its default ruleset flags docs and test fixtures (the .p8 format example in .github/SECRETS.md, fake BEGIN PRIVATE KEY round-trip data in dotenv tests, fake ghp_/sk_live_ tokens in redaction/taint tests) that gitleaks 8.30.x's refined defaults correctly skip. Pin the version the local scan validates against so local gitleaks dir . and CI agree; all 11 prior findings were inspected and confirmed as documentation or test fixtures, not real secrets.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ci CI/CD and build tooling area/docs Documentation and guides size/XL 1000+ lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant