sec(gitleaks): add a gating full-history pass beside the working-tree pass - #756
Conversation
… pass
The PR secret gate scanned only the checked-out working tree, so a secret
that was committed and later deleted was permanently invisible to it. Owner
ruling 2026-09-09: "Make the PR gate full-history now."
This adds a SECOND gating pass rather than converting the existing one,
because the two passes see different populations. Measured on a fixture with
this exact pinned binary (8.18.4), the same secret in two placements:
committed then deleted working tree: MISSED history: FOUND
untracked file working tree: FOUND history: MISSED
git mode scans commit diffs and cannot see a file that was never committed;
--no-git walks the checked-out tree and cannot see a blob no longer in it.
Dropping either pass REMOVES coverage, so both gate.
Three changes, all inside the existing `gitleaks` job:
1. fetch-depth: 0 on the checkout. Without it the history pass would walk a
single commit and report a pass.
2. The stale comment that told readers to remove --no-git is replaced with
the two-population explanation.
3. A new step, "Gitleaks secret scan - git history (gating)", placed
immediately after the existing pass, which is left byte-identical.
The new step carries its own vacuity control: it asserts
`git rev-parse --is-shallow-repository` is false and refuses to report a
pass otherwise, because gitleaks' own "scanned N commits" counter
under-reports and is not proof of clone depth. It also asserts
`test -s "$CONFIG"`, so a silently-failed baseline staging step cannot leave
gitleaks scanning almost nothing and calling it green.
CHECK-NAME IDENTITY PRESERVED. Callers have `secret-scan / gitleaks` as a
required context; a renamed job would make that context go absent, and an
absence is never in a failure set. Verified structurally on the parsed YAML
tree, not by eye:
job keys identical gitleaks, rust-secrets, shell-secrets
job name: fields identical all three nil (unchanged)
steps 9 -> 10, exactly one added, zero removed
existing gating step parsed-identical before and after
only pre-existing delta `with:` on the checkout
new step position immediately after the working-tree pass
--no-git argument lines 2 (working tree + AsciiDoc mirror), unchanged
rust-secrets / shell-secrets entirely unchanged
KNOWN GAP, stated in the file rather than implied: the AsciiDoc mirror pass
copies files to a scratch directory and scans it with --no-git, so
fetch-depth: 0 cannot reach it. An AsciiDoc secret that was committed and
later deleted stays invisible. "The gate is full-history" holds for every
file class but that one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FmGveHYaPLDUZVKGR1rLaK
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📜 Recent review details⏰ Context from checks skipped due to timeout. (1)
|
| Layer / File(s) | Summary |
|---|---|
Full-history Gitleaks gate .github/workflows/secret-scanner-reusable.yml |
The checkout fetches full history. The workflow documents complementary working-tree and Git-history scans. A Git-mode Gitleaks step verifies repository depth, selects the repository or estate configuration, rejects empty configuration, and scans Git history. |
Priority: ⬇️ Low
Estimated code review effort: 2 (Simple) | ~10 minutes
Merge Risk: ⚪ Minimal · up to 6c248
Secret scanning now gates on both working-tree content and complete Git history, with safeguards for incomplete checkouts and empty configuration. The change is ready to merge.
Sequence Diagram(s)
sequenceDiagram
participant Workflow
participant Checkout as actions/checkout
participant Git
participant Gitleaks
Workflow->>Checkout: Fetch full repository history
Workflow->>Git: Verify repository is not shallow
Workflow->>Workflow: Select repository or estate Gitleaks configuration
Workflow->>Gitleaks: Scan Git history with the selected configuration
Gitleaks-->>Workflow: Return scan status
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
| Check name | Status | Explanation |
|---|---|---|
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0… |
| Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
| Title check | ✅ Passed | The title clearly and concisely describes the main change: adding a gating full-history Gitleaks scan alongside the existing working-tree scan. |
| Description check | ✅ Passed | The description is directly related to the changes. It explains the dual-scan design, validation controls, preserved job identity, known coverage gap, and scope boundaries. |
✨ Finishing Touches
🛠️ Fix failing CI checks
- Create stacked PR
- Commit on current branch
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
I am a rabbit guarding the gate
Full history makes secrets wait
Working files and old commits
Gitleaks checks the hidden bits
Clean scans let the workflow run straight
Comment @coderabbitai help to get the list of available commands.
|
✅ Coding Agent task started: View task and status The task will inspect the CI failures, validate its fix, and open a stacked fix pull request automatically.
|
⏸ HELD by owner ruling, 2026-09-09 — do not mergeThe owner has ruled that this PR stays unmerged until they confirm the leaked Why it is red, and why that is correctThe single failing check is The remaining two findings are Why the allowlist is not in this PR yet
The gate and its allowlist are therefore one bundled decision, not two sequenced What is already built and measured, held off-branchA 27-line Measured on this branch at
The two-sided canary is the load-bearing part. Three-to-zero alone cannot Documented residual: a The one action that unblocks thisOn the Cloudflare account whose id appears alongside the token in that same script,
Zones belong to the account, not to any member, so neither route disconnects a site. Honest scope of the fixRevocation is the cure; a history rewrite is not. PR #161 says so in its own words: Resume condition: the owner states the credential is dead. Then the allowlist is |
|



What changed
The estate PR secret gate scanned only the checked-out working tree, so a
secret that was committed and later deleted was permanently invisible to
it. Owner ruling 2026-09-09: "Make the PR gate full-history now."
This adds a second gating pass rather than converting the existing one.
Why both passes, not one
The premise behind "just drop
--no-git" is wrong, and it was measured, notassumed. A fixture built with this exact pinned binary (gitleaks 8.18.4), the
same secret placed two ways:
--no-git)gitmode scans commit diffs, so it cannot see a file that was nevercommitted.
--no-gitwalks the checked-out tree, so it cannot see a blob thatis no longer in it. Dropping either pass removes coverage. Both gate: a
finding in either exits non-zero.
The three edits, all inside the existing
gitleaksjobfetch-depth: 0on the checkout. Without it the history pass wouldwalk a single commit and report a pass.
--no-gitis replaced with the two-population explanation.Gitleaks secret scan — git history (gating), placedimmediately after the existing pass, which is left byte-identical.
Controls, so the new gate can actually fail
git rev-parse --is-shallow-repositoryisfalseand refuses to report a pass otherwise.If
fetch-depth: 0ever regresses, this fails loudly instead of scanningone commit and going green. gitleaks' own "scanned N commits" counter
under-reports and is not proof of clone depth, so it is not used here.
test -s "$CONFIG". A silently-failed baseline staging step wouldotherwise leave gitleaks running a reduced config over almost nothing and
calling the result green.
Check-name identity is preserved
Callers have
secret-scan / gitleaksas a required context. A renamed jobwould make that context go absent, and an absence is never in a failure set.
Verified on the parsed YAML tree, not by eye:
gitleaks,rust-secrets,shell-secretsname:fields identicalgitleaksGitleaks secret scan (gating)stepwith:on the checkout--no-gitargument linesrust-secrets/shell-secretsjobsKnown gap, stated rather than implied
The AsciiDoc mirror pass copies files into a scratch directory and scans
it with
--no-git, sofetch-depth: 0cannot reach it. An AsciiDoc secretthat was committed and later deleted stays invisible. "The gate is
full-history" holds for every file class but that one. This is written into
the workflow file itself, not just here.
Pre-existing defect surfaced, NOT fixed here
Stage estate gitleaks baselinecopies the baseline unconditionally,while
Estate baseline — installcopies it again gated onsteps.estate_baseline.outputs.referenced. The comment claiming it is"staged ONLY when actually referenced" is contradicted by the unconditional
cp. Out of scope for this PR; recorded so it is not lost.Propagation, honestly
standardscalls its own reusable by path (uses: ./.github/workflows/secret-scanner-reusable.yml), so it auto-follows onmerge. The six stapeln-ecosystem repos are pinned at three SHAs and each needs
a re-pin:
stapeln,svalinn→fad242d35291de1898242d6737ba02b74a59a2f2rokur,vordr,selur→a3308ea8d0cc513727c8ad149b1132317a886c98cerro-torre→257869d3061d5a8ed1529bf34225d90a2416d51aThe remaining ~280 callers land it whenever the existing pin-bump campaign
reaches them. This is not an estate-wide flip on merge.
Self-merged under the standing
--admingrant once required ∩ {failed, neverran} is empty.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FmGveHYaPLDUZVKGR1rLaK