Skip to content

ci: allowlist two verified gitleaks false positives - #97

Merged
TheAbider merged 1 commit into
masterfrom
ci/gitleaks-allowlist-false-positives
Aug 3, 2026
Merged

ci: allowlist two verified gitleaks false positives#97
TheAbider merged 1 commit into
masterfrom
ci/gitleaks-allowlist-false-positives

Conversation

@TheAbider

Copy link
Copy Markdown
Owner

Problem

The weekly full-history gitleaks scan has failed every Monday since at least 2026-05-25 — eleven consecutive runs — always on the same two findings.

Run Result
2026-08-03 failure
2026-07-27 failure
2026-07-20 failure
2026-07-13 failure
2026-07-06 failure
2026-06-29 failure
2026-06-22 failure
2026-06-15 failure
2026-06-08 failure
2026-06-01 failure
2026-05-25 failure (oldest run still listed)

The push and pull_request runs stay green because gitleaks only scans new commits on those events. Only the schedule run does git log -p --full-history --all, so it re-reports the same two historical commits forever.

A permanently red secret scanner is worse than no scanner: a genuine leak would arrive as "3 findings" in a job everyone has learned to skip.

The two findings are not credentials

Tests/Pester/Logging.Tests.ps1:133 — the fixture for the test asserting the logger redacts data keys named Token. The value is deliberately Stripe-shaped so the test is meaningful, and the assertion is that it never reaches the log file:

It 'redacts a key named "Token"' {
    Write-StructuredLog -Message 'auth' -Data @{ Token = 'sk_live_abcd1234' }
    (Read-LogContent $script:logPath) | Should -Not -Match 'sk_live_abcd1234'
}

Sibling cases in the same block use hunter2-plaintext and topsecret-xyz.

docs/fileserver-docker.md:145 — shows readers the shape of a Cloudflare tunnel token. The base64 payload decodes to:

{"a":"abcdef...","t":"a1b2c3d4...","s":"ABCDEF..."}

Literal ellipses, no live tunnel, no account identifier. The next line of that document reads "Never commit .env to version control."

Approach

Allowlist by exact literal value, not by path and not by commit, so a real secret added to either file in any future commit still fails.

Note the file uses the singular [allowlist] table. gitleaks 8.24.3 silently ignores the plural [[allowlists]] array at global scope — it parses without error, emits no warning, and suppresses nothing. Since push/PR runs skip history, a broken allowlist would not surface until the following Monday, so the file carries a maintenance warning and a re-verification command.

Verification

Run locally against full history with gitleaks 8.24.3 — the exact version CI pins — after checksum-verifying the release archive.

Scenario Findings Exit
Baseline, no config (reproduces CI) 2 2
Plural [[allowlists]] form 2 2
Singular [allowlist] form (this PR) 0 0
This PR + 3 planted secrets 3 2

The last row is the one that matters. Three secrets were planted and all three were caught while the two known false positives stayed suppressed — 3 findings, not 5:

  1. A high-entropy TUNNEL_TOKEN appended to docs/fileserver-docker.md — an allowlisted file. Caught, proving the suppression is not path-scoped.
  2. A near-miss of the allowlisted literal, sk_live_9f3c7a2e8b1d4506, in Tests/Pester/Logging.Tests.ps1. Caught, proving exact-value scoping.
  3. A Google-shaped api_key in a new file. Caught.

The mutation commit was discarded and never pushed.

Follow-up

After merge, the gitleaks workflow should be dispatched manually on master to confirm the full-history path is green, since no required check exercises it.

The weekly full-history gitleaks scan has failed every Monday since at
least 2026-05-25, always on the same two findings. Neither is a
credential:

- Tests/Pester/Logging.Tests.ps1 uses 'sk_live_abcd1234' as the fixture
  for the test asserting Write-StructuredLog redacts keys named "Token".
  The assertion is that the value does not reach the log file.
- docs/fileserver-docker.md shows readers the shape of a Cloudflare
  tunnel token. The base64 payload decodes to
  {"a":"abcdef...","t":"a1b2c3d4...","s":"ABCDEF..."} -- literal
  ellipses, no live tunnel.

A permanently red secret scanner is worse than no scanner, because a
genuine leak arrives as "3 findings" in a job everyone has learned to
skip. The push and pull_request runs stay green because they only scan
new commits, so the weekly job was the only one reporting and it was
reporting noise.

Allowlist both by exact literal value rather than by path or commit, so
a real secret added to either file still fails the scan.

Note the singular [allowlist] table. gitleaks 8.24.3 silently ignores
the plural [[allowlists]] array at global scope -- it parses without
error and suppresses nothing -- and because push/PR runs skip history, a
broken allowlist would not surface until the following Monday. The file
carries a maintenance warning to that effect.

Verified locally with gitleaks 8.24.3 over all 589 commits: 2 findings
before, 0 after, and a planted secret in an allowlisted file still
fails the scan.
@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@TheAbider
TheAbider merged commit d6a4d32 into master Aug 3, 2026
6 checks passed
@TheAbider
TheAbider deleted the ci/gitleaks-allowlist-false-positives branch August 3, 2026 22:45
TheAbider added a commit that referenced this pull request Aug 3, 2026
PR #97 fixed the weekly full-history scan, but nothing prevents that fix
from being silently undone. The push and pull_request gitleaks runs scan
only the new commits, so a broken allowlist yields a green PR and does
not surface until the following Monday.

Section 207 asserts the invariants that keep the suppression safe:

- The singular [allowlist] table is used and [[allowlists]] is not.
  gitleaks 8.24.3 silently ignores the plural array at global scope: it
  parses, warns about nothing, and suppresses nothing. Upstream docs show
  the plural form, so this is easy to "modernise" into a no-op.
- No `paths` or `commits` entries, which would blind the scanner to whole
  files or whole commits instead of to two exact values.
- useDefault stays true, so the allowlist is not layered over an empty
  ruleset that scans for nothing.
- Exactly two reviewed entries, and no catch-all regex.
- The workflow keeps its schedule trigger and fetch-depth: 0, without
  which nothing ever scans history at all.

The section deliberately does not embed the two allowlisted literals; it
counts entries instead, so the test file does not reintroduce the strings
it exists to describe.

Mutation-verified: violating all six config invariants and both workflow
invariants at once fails 9 of the 10 asserts and the suite exits 1. The
tenth ("file present") correctly still passes, since the file existed.

Structural test count 5428 -> 5438; README badge synced by the suite.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant