fix(leak-scan): repair four rules that could never fire - #127
Merged
Conversation
The deny-list matches with `grep -E`, but every test asserted against
Python's `re`. Those engines disagree silently, so patterns using
Perl-only syntax passed the whole suite while matching nothing in
production. Proven with one pattern and one input, 10.42.0.0/16:
python re matches, GNU grep -E matches 0.
Dead under grep -E, now fixed:
ipv4_literal \d -> [0-9] (entirely dead)
ipv6_literal (?:...) -> (...) (both patterns dead; source of
the "? at start of expression"
warnings in every run)
cgnat \d -> [0-9] (100.70-119 undetected)
rfc1918 2\d -> 2[0-9] (172.20-29 undetected)
The ssh_keys rule failed differently: `-----BEGIN .* PRIVATE KEY-----`
begins with '-', so grep parsed it as an option and exited with a usage
error. A private key in a diff scanned clean. Both call sites now pass
patterns with `-e`.
EXCLUDE_PATHS is documented as "Only effective in pr-diff mode" and was
wired into every mode except pr-diff, so callers passing exclude-paths
to suppress a known false-positive class got no exclusion at all. It is
now applied to the added-line scan, matching the path scan's literal
prefix semantics.
Fixing that exposed a fourth bug in the exclusion counter: `grep -c .`
prints 0 AND exits 1 on no match, so `|| echo 0` appended a second zero
and the arithmetic saw "1 - 0\n0". Excluding every changed file did not
skip the scan, it crashed it -- a docs-only PR in a repo passing
exclude-paths would have failed on a shell syntax error.
The new test module shells out to grep rather than trusting `re`, which
is the actual defect class here. Verified it fails against the previous
patterns (6 subtest failures, one per dead pattern) before it passed
against these. End-to-end against real run.sh with gitleaks present: a
private key now fails the scan, an IPv4 literal now fails, an excluded
path is skipped, a clean diff still passes.
Blast radius of the newly-live IP rules, measured over the last 17
commits on fleet-infra main: zero would have been blocked under either
the old or the new patterns.
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.
Summary
The deny-list matches with
grep -E, but every test asserts against Python'sre. Those engines disagree silently, so patterns using Perl-only syntax passed the whole suite while matching nothing in production.Same pattern, same input
10.42.0.0/16:re(what the tests use)grep -E(what actually runs)Four separate defects, all of which meant a rule reported clean when it should have fired:
1.
ssh_keys— private keys were undetectable.-----BEGIN .* PRIVATE KEY-----begins with-, sogrepparsed it as an option and exited with a usage error visible in every run:A private key added in a diff scanned clean. Both call sites now pass patterns with
-e.2. Perl-only syntax in six patterns.
\dand(?:...)are meaningless in POSIX ERE:ipv4_literal\d{1,3}ipv6_literal(?:...)? at start of expressionwarningscgnat[7-9]\d,1[01]\d100.70–119undetectedrfc19182\d172.20–29undetected3.
EXCLUDE_PATHSwas wired into every mode except the one it documents. The input says "Only effective in pr-diff mode", butrun_diff_deny_list_scannever read it — callers passingexclude-pathsto suppress a known false-positive class got no exclusion at all. Now applied to the added-line scan, using the same literal-prefix semantics as the path scan.4. The exclusion counter crashed the scan. Found while fixing 3.
grep -c .prints0and exits 1 on no match, so|| echo 0appended a second zero and the arithmetic saw1 - 0\n0:Excluding every changed file didn't skip the scan, it crashed it — a docs-only PR in a repo passing
exclude-pathswould have failed on a shell syntax error.Validation
The new test module shells out to
grep, notre— that engine divergence is the actual defect class, and no amount of Python-side testing would have caught any of this.run.shwithgitleaksinstalled, GNU grep,linux/amd64:Risk
The IP rules going live is the real behavioural change here, so I measured it rather than guessing. Over the last 17 commits on
fleet-inframain, deny-list hits on added lines:0 would have been blocked under the old patterns, and 0 under the new ones.
Repos that legitimately carry subnet literals now have a working valve for the first time, since
exclude-pathsactually functions.Consumers pin by tag (
fleet-infrais on@v0.12.9), so nothing changes for anyone until they bump — this cannot surprise a repo mid-flight.Links
JorisJonkers-dev/fleet-infra#167, where two lines of standard RFC1918except:boilerplate in a NetworkPolicy block the required check.