chore(secrets): replace detect-secrets baseline with gitleaks#6262
Conversation
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.
houko
left a comment
There was a problem hiding this comment.
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.
Why
.secrets.baselinewas 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 hadis_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 earlierline_number-drift mitigation (#5694) stopped CI from going red on pure line shifts, but it could not stop the underlyinggit mergetext conflicts.What changed
Replace detect-secrets with gitleaks. gitleaks scans against a fixed rule set (provider-specific key formats plus a guarded
generic-api-keyrule) 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.baselinedeleted;.gitleaks.tomladded. The path allowlist migrates the old detect-secretsexcludeblock (lockfiles,sdk/,openapi.json, dashboarddist/node_modules, snapshots,fixtures/,testdata/, doc images, examples). Two semantic regexes cover the bulk of prior false positives:*_envconfig fields hold an ENV-VAR NAME not a secret (^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$, underscore-required so real AWSAKIA…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.ymlrewritten to install a pinned, checksum-verified gitleaks binary (8.30.1) and rungitleaks dir .over the working tree. Thepre-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.scripts/hooks/pre-commitnow runsgitleaks protect --stagedand soft-warns when gitleaks is absent — with no baseline to drift, a tool-less commit can no longer push stale state tomain(the reason the detect-secrets path hard-errored), so the hard error is no longer warranted..pre-commit-config.yamlswaps the Yelp detect-secrets repo for the gitleaks pre-commit hook (pinnedv8.30.1).CONTRIBUTING.md(hook table + secret-scanning section),CLAUDE.md(git-side hook description).coverage.ymlpaths-ignore now points at.gitleaks.toml.CHANGELOG.mdnotes the change under[Unreleased].Verification
gitleaks dir . --config .gitleaks.tomlover 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..p8format example in.github/SECRETS.md, fake-----BEGIN PRIVATE KEY-----round-trip data in a dotenv test, and fakeghp_/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.sh -non the rewritten hook all parse clean.Action required for the maintainer
The CI status-check name changes from
detect-secrets scantogitleaks scan. If branch protection onmainlists 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.mdmentions.secrets.baselineas a historical finding; left as-is since it's an archived issue record, not active config.GITLEAKS_VERSIONlater means re-runninggitleaks dir .locally and allowlisting any new false positives — the version comment insecret-scan.ymlrecords this.