Skip to content

gitleaks binary is pinned to 8.21.2 (Oct 2024) with no update path — Renovate cannot see it, and the anthropic-api-key rule does not exist in it #30

Closed

Description

@CameronBrooks11

The reusable secret-scanning workflow pins the gitleaks binary as a bare string with no update path:

$ gh api repos/FlowMatrix-AI/.github/contents/.github/workflows/gitleaks.yml --jq .content | base64 -d
15:      gitleaks-version:
16:        description: gitleaks release version (no leading v)
17:        type: string
18:        default: "8.21.2"
...
34:          v="${{ inputs.gitleaks-version }}"
35:          curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${v}/gitleaks_${v}_linux_x64.tar.gz" \
$ gh api repos/gitleaks/gitleaks/releases/tags/v8.21.2 --jq '"\(.tag_name) \(.published_at)"'
v8.21.2 2024-10-28T13:32:54Z
$ gh api repos/gitleaks/gitleaks/releases/latest --jq '"\(.tag_name) \(.published_at)"'
v8.30.1 2026-03-21T02:17:58Z

Every repo in the org scans with a scanner published 2024-10-28, and nothing will ever tell us.

Renovate cannot see it

It is a string interpolated into a curl URL, not a uses: ref. Renovate's own dashboard reports exactly one dependency in this file:

$ gh issue view 12 --repo FlowMatrix-AI/.github --json body --jq .body
<details><summary>.github/workflows/gitleaks.yml (1)</summary>

 - `actions/checkout v7@3d3c42e5aac5ba805825da76410c181273ba90b1`

</details>

So the one line in this file that decides what a secret scan can detect is the one line no automation is watching.

Blast radius: every repo, and no caller overrides the default

$ for r in <each repo>; do gh api repos/FlowMatrix-AI/$r/contents/.github/workflows/secret-scan.yml ... ; done

site-omws-main                   gitleaks.yml@d2eab9f    overrides gitleaks-version: 0
site-hillsandthrills-main        gitleaks.yml@d2eab9f    overrides gitleaks-version: 0
site-ruttedbuck-main             gitleaks.yml@d2eab9f    overrides gitleaks-version: 0
site-marketfourseasons-main      gitleaks.yml@5eb6dcf    overrides gitleaks-version: 0
site-thehuntadvisory-main        gitleaks.yml@8aa9018    overrides gitleaks-version: 0
site-flowmatrixai-main           gitleaks.yml@5eb6dcf    overrides gitleaks-version: 0
site-flowmatrixai-studio         gitleaks.yml@158c596    overrides gitleaks-version: 0
marketing-site-engine            gitleaks.yml@d2eab9f    overrides gitleaks-version: 0

Eight repos, four different pinned revisions of this workflow — and all four revisions carry default: "8.21.2", verified at each SHA. Renovate keeps the callers' workflow pins current, which is why this looks healthy from the caller side; bumping the pin has never changed the scanner.

What the gap actually costs — this is the part that matters

Not a version delta. The org config sets useDefault = true (this workflow's own comment: "useDefault = true in the org config chains through, so the built-in rules stay active either way"), so the binary's built-in ruleset is what does the detecting, and it is frozen at 8.21.2.

$ for t in v8.21.2 v8.30.1; do gh api "repos/gitleaks/gitleaks/contents/cmd/generate/config/rules?ref=$t" \
    --jq '[.[] | select(.name|endswith(".go")) | select(.name|endswith("_test.go")|not)] | length'; done
119
131

Twelve rule files added, none removed:

anthropic.go  artifactory.go  clickhouse.go  freemius.go  looker.go  maxmind.go
meraki.go     notion.go       perplexity.go  settlemint.go  sonar.go  sourcegraph.go

anthropic.go is the one to read. It does not exist at v8.21.2 (404), and at v8.30.1 it is:

RuleID:      "anthropic-api-key",
Regex:       utils.GenerateUniqueTokenRegex(`sk-ant-api03-[a-zA-Z0-9_\-]{93}AA`, false),

Every repo in this org is worked on with Claude Code. An sk-ant-api03-… key committed to any of them is not detected by the scanner we run, on any branch, on any scheduled sweep. The same holds for Notion and Perplexity tokens, and for Artifactory and Sonar credentials.

Fix

Bump default: to "8.30.1" and give the line an update path, so this cannot silently rot again. Options, cheapest first:

  1. A Renovate customManagers (regex) rule matching the default: line in this file with datasource=github-releases, depName=gitleaks/gitleaks. Keeps the existing shape and makes the version a tracked dependency.
  2. Drop the pin and resolve the latest release at run time. Rejected as written — it makes the scan non-reproducible and puts an unreviewed binary in CI.

(1) is the one worth doing.

Found while triaging follow-ups from adversarial reviews on marketing-site-engine. The original review claim was "site-thehuntadvisory-main's gitleaks pin is 7 commits behind" — that was false on its premise: the site pins the workflow at a SHA while the template documents @main, so the caller is stricter, and the one intervening commit that touches this file is a no-op for it. Checking that claim is what surfaced the real one.

Related: #25 (the last detection-rule gap filed here).

Activity

  1. CameronBrooks11 commented on Sep 10, 2026

    @CameronBrooks11
    ContributorAuthor

    Verified, measured, and fixed in #31 (not merged).

    What held up, and what did not

    Claim Verdict
    default: "8.21.2" at line 18 Confirmed on main
    No caller overrides gitleaks-version Confirmed — zero overrides across every caller
    All pinned revisions carry the same default Confirmed, but 9 revisions are in use, not 4
    "Eight repos" Wrong. 66.
    8.21.2 has no anthropic-api-key rule Confirmed empirically — and there are two Anthropic rules, not one
    "Twelve rule files added" True of files; the rule-ID count is 27 added, 0 removed

    The undercount is the part worth correcting. The listing in the description covered eight repos; enumerating the whole org finds this workflow called by 66 of 67:

    $ gh api "orgs/FlowMatrix-AI/repos?per_page=100&type=all" --paginate --jq '.[].name' | wc -l
    67
    # then fetch every .github/workflows/*.yml in each and grep for gitleaks.yml
    $ grep -c . caller-repos.txt
    66
    

    Nine distinct revisions of this file are pinned across them (158c5963, 1bbaee7d, 5eb6dcf1, 8aa90186, bbd63dc5, d12147b2, d2eab9f6, df3d01bf, fdd02918), spanning 2026-07-06 to 2026-09-05, and default: "8.21.2" is present at every one — checked at each SHA, not inferred.

    The rule gap, pinned down

    anthropic.go first ships in v8.28.0 (2025-07-20). Absent at 8.21.2, 8.22.0, 8.23.0, 8.24.0, 8.25.0, 8.26.0, 8.27.0, 8.27.1, 8.27.2; present from 8.28.0 through 8.30.1. It defines anthropic-api-key and anthropic-admin-api-key (sk-ant-admin01-…), which the description missed.

    The null result has a positive control: the same v8.21.2 listing does contain openai.go, so the check would have found anthropic.go had it been there.

    Not argued from source — run against a throwaway repo holding one synthetic key of each shape, with this org's .gitleaks.toml:

    ===== gitleaks 8.21.2 =====   OPENAI_API_KEY -> generic-api-key            leaks found: 1
    ===== gitleaks 8.30.1 =====   anthropic-admin-api-key, anthropic-api-key   leaks found: 2
    

    Built-in rule IDs: 195 at v8.21.2, 222 at v8.30.1 (grep -cE '^\s*id\s*=' config/gitleaks.toml at each tag). Beyond Anthropic, the additions that matter here include both Amazon Bedrock key rules, perplexity-api-key, notion-api-token, 1password-secret-key, two Sentry token rules, heroku-api-key-v2, two routable GitLab token rules, and sonar-api-token.

    Blast radius: zero

    All 66 callers were cloned with full history and scanned with both versions, replicating what the workflow does (fetch the org config to .gitleaks.org.toml, copy to .gitleaks.toml only where the repo has none, then gitleaks git --redact -v --config .gitleaks.toml).

    0 findings on 8.21.2. 0 findings on 8.30.1. No repo turns red, and no allowlist entries are needed — so no repo needs to fork the org config either. 145 MB and ~4,900 commits scanned.

    report files: 132 | files with >0 findings: 0
    $ grep -E 'WRN leaks found' reports/*.log
    (none)
    

    Three checks behind that null result:

    1. The harness reports when there is something to report — the control above returned 2 findings through the same path.
    2. The eight callers that commit their own .gitleaks.toml also gain the coverage: injecting a synthetic key into one gives 1 finding on 8.21.2 and 2 on 8.30.1, because useDefault = true chains the new built-ins through. No edits needed there.
    3. It is genuinely full history: gitleaks git defaults to --all, so on the largest caller it scanned 412 commits where rev-list HEAD is 364.

    No true positives. Nothing is being withheld from this thread.

    Fix

    #31 takes option (1) from the description. default: moves to 8.30.1 and carries:

    # renovate: datasource=github-releases depName=gitleaks/gitleaks extractVersion=^v(?<version>.+)$
    

    Renovate's own customManagers:githubActionsVersions preset uses exactly this comment convention but its matchString only accepts a key ending in _VERSION, so a workflow input default: never matches — hence a small customManager in renovate.json, written by shape rather than naming gitleaks. extractVersion is load-bearing: the tag is v8.30.1 and the curl URL supplies its own v.

    Proven by running renovate itself against the branch (--platform=local --dry-run=lookup, renovate 44.61.5): extraction goes from 4 files / 5 deps on main to 6 files / 7 deps, with "depName": "gitleaks/gitleaks", "currentValue": "8.30.1", "datasource": "github-releases", "currentVersion": "8.30.1", "updates": []. It also concatenates with rather than replacing the org preset's uv tool install manager — verified against mergeChildConfig, which returns parent entries then child entries for customManagers.

    The org preset's automerge rules do not match custom.regex, so future releases open a reviewable PR rather than automerging.

    CI on the PR confirms it end to end: gitleaks version → 8.30.1, no leaks found.

    Two things found alongside

    One behaviour change to know about: --max-decode-depth defaults to 5 in 8.30.1 versus 0 in 8.21.2, so nested base64 is now decoded and scanned. It produced no new findings on any caller, but it is the likeliest source of a future false positive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions