Repository navigation
fix(gitleaks): resolve the org ruleset at the caller's pinned ref - #37
Merged
Merged
Conversation
The reusable workflow fetched `.gitleaks.toml` from `main` on every run while the detector version it runs with is an input default in the workflow file itself. A caller's `gitleaks.yml@<digest>` therefore froze the binary but not the rules, and the uncovered half failed open: broadening the shared allowlist shrank detection in every calling repo on its next scan, with no commit, no PR and no review in any of them, and every required check stayed green. Fetch the ruleset at `job.workflow_sha` — the commit of the workflow file defining this job, which is the caller's ref — so the pin covers detection behaviour. A caller on `@main` or any branch is unchanged, since that resolves to the branch head; only a pinned caller changes, and it changes to what its pin already claimed. Keep the fetch fail-closed and widen it: an empty `job.workflow_sha` aborts the step rather than falling back to a branch, because an unknown ruleset version is the one thing the scan must not run with. Also write the ruleset commit and its sha256 to the job summary, so what a run scanned with is legible from the run and not only from a diff in another repository. The cost: a ruleset fix reaches a pinned repo only when its pin moves. Measured across the 69 callers — 67 resolve a byte-identical ruleset today, and the 2 that do not are pinned to a July commit that also freezes them on gitleaks 8.21.2, so they were already scanning with a year-old binary against current rules. Those two need a repin. Update README.md and AGENTS.md, which both asserted the old behaviour.
AGENTS.md said 68 of 70 active repos call the reusable workflow and named one repo as a non-caller. Measured while verifying the pin change: 69 of the 70 non-archived repos carry a caller, that one included, and the only repo without one is this one, which calls it through a local ref. Reworded so the claim is the invariant — every active repo but this one — rather than a count that rots as repos are added.
The job summary records which ruleset a run scanned with, but a summary is not reachable over the API. Print the same line to the log as well, so the ruleset in force can be audited across every calling repo from run logs instead of by opening each run page.
CameronBrooks11
force-pushed
the
fix/pin-gitleaks-config-to-caller-ref
branch
from
October 17, 2026 20:30
8aa9e0c to
f9ce275
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The mechanism, precisely
gitleaks.ymlis consumed by every active repo in the org but this one, andalmost all of them pin it by digest:
That digest does freeze the detector:
gitleaks-versionis aworkflow_callinputdefault:inside the pinned file, so a repo pinned atdf3d01bruns gitleaks 8.21.2 and one pinned at3fa1141runs 8.30.1.Measured:
The digest did not freeze the rules that detector ran with. The fetch step
was hardcoded to a branch:
So the pin was half a pin, and the uncovered half failed in the open direction.
Broadening the shared
[allowlist]here shrank detection in every calling repoon its next scan — no commit, no PR, no review and no reviewer in any of those
repos, and every required
gitleaks / gitleakscheck green throughout. For arequired security check that is the wrong direction to fail: the thing that
changed was invisible precisely where it mattered.
Is this a deliberate design with a recorded reason?
Checked before changing it, because the hazard is written down —
eng-governance/github/security-config.mdhas a section "The config channel:.githubis load-bearing for the whole fleet" listing "three consequences,recorded so they are decisions rather than surprises", and this repo's
README.mdandAGENTS.mdboth describe it.It reads as an acknowledged hazard, not a ratified design. The other two
consequences in that list are explicitly accepted ("the visibility choice is
effectively one-way", "the fetch is deliberately fail-closed … Accepted
risk: …"). This one is not: it states the fail-open and then records that the
mitigation is not in place — "code-owner review is not yet required there
(that is a ruleset decision, tracked on #111)". Nothing anywhere gives a reason
to prefer a floating config; the one place it is reasoned about names a
different, unimplemented fix. So: changed, and the governance doc needs a
follow-up edit that cannot be made from this repo.
What changed, and the trade-off
Fetch the ruleset at
job.workflow_sha— the commit SHA of the workflowfile that defines the running job, i.e. this file at the ref the caller asked
for — instead of from
main.The three options, and why this one:
.gitleaks.tomlstops being a standalone reviewable file with its ownCODEOWNERSentry, and.pre-commit-config.yamlcan no longer run the same rules locally. No integrity gain over the above.Chosen because it is the only one of the three in which a weakening of
detection becomes visible: it has to arrive as a commit in the consuming
repo. It also makes the pin internally consistent — the detector and the rules
it runs are now frozen by the same digest, instead of one being frozen and the
other floating.
The summary line is kept as well, but as a record rather than the control: each
run now writes the ruleset commit and its
sha256to the job summary, so what ascan ran with is legible from the run itself.
Unchanged for unpinned callers:
job.workflow_shaon a branch ref resolves tothat branch's head, so a
@maincaller behaves exactly as before. Only apinned caller changes, and it changes to what its pin already claimed.
Cost, measured rather than asserted
67 of the 69 callers (the 63 at
3fa1141, the 2 ata53251c, the one atfdd0291, and the@mainone) resolve a byte-identical ruleset today —4913 bytes,
gohighlevel-pitpresent — so for them this is a no-op thatremoves a hazard.
The exceptions are the two repos pinned at
df3d01b(2026-07-06), which wouldresolve the July ruleset: 1399 bytes, no
gohighlevel-pitrule. Those twoneed a repin — they are identifiable from that digest. Note what their pin
already does to them: they run gitleaks 8.21.2, from October 2024. Under the old
behaviour they got current rules executed by a year-old binary and a green
check; the staleness was real either way, it was just split across two places
with only one of them visible. Repinning fixes both halves at once.
Also fail-closed, more so
An empty
job.workflow_shanow aborts the step rather than falling back to abranch, because an unknown ruleset version is the one thing the scan must not
run with.
curl -fwith no fallback is unchanged.Dry-run of the step body, three cases:
The repo-local-config branch is preserved: a repo that already has
.gitleaks.tomlkeeps it and inherits the org one through[extend] path = ".gitleaks.org.toml", exactly as before.raw.githubusercontent.comserves a commit SHA that is not on a branch tip,including a pull request's merge commit — checked against both the head and
merge SHAs of an open PR in this repo (
200for each), so thepull_requestcase resolves.
Validated: the workflow parses as YAML, all three
run:blocks passbash -n,and this PR's own
secret-scanrun exercises the change through the local./ref — which is the live measurement of
job.workflow_sha, since the step failsclosed if it is empty.
Docs corrected in the same PR
README.mdandAGENTS.mdboth asserted the old behaviour in prose, so they areupdated in the same commit rather than left to contradict the workflow.
A second commit corrects an unrelated false claim found while measuring the
callers:
AGENTS.mdsaid 68 of 70 active repos call the workflow and named onerepo as a non-caller. That repo does call it, pinned at
3fa1141; 69 of 70carry a caller and the only repo without one is this one. Reworded to the
invariant rather than a count that rots.
Review notes
deliberately not been enabled — the repo allows auto-merge and requires 0
approving reviews, so enabling it would self-merge on green.
reaches every caller the moment it merges; once this PR merges, it reaches a
pinned caller only when that pin moves. Merge feat(gitleaks): detect gohighlevel webhook capability urls #36 first.
df3d01b, andupdate
eng-governance/github/security-config.mdconsequence 2, which stilldocuments the unpinned fetch.