fix(sources): don't skip files on path match when allowlist condition is AND - #2227
Open
BasitS-hash wants to merge 1 commit into
Open
Conversation
A global allowlist using `condition = "AND"` with both `paths` and
`regexes` currently causes matching files to be skipped entirely during
enumeration, before any content is read. Every secret in those files goes
unreported.
shouldSkipPath iterated the global allowlists and skipped on PathAllowed
alone, without consulting MatchCondition. The detector already handles
this correctly (detect.go checks AllowlistMatchAnd in both
checkCommitOrPathAllowed and checkFindingAllowed), so the two layers
disagreed: the file was dropped before the detector could evaluate the
other half of the AND.
The failure is silent and points the wrong way for a secret scanner — a
config written to allowlist one pattern within some files instead stops
those files being scanned at all.
Only skip on a path match when the path is sufficient on its own: under
"OR" (unchanged), or under "AND" when paths are the sole criterion.
Regexes and stopwords are content-based and have to reach the detector.
Commits are not treated as content-based, since they are known before a
file is read and the git source filters allowed commits separately.
Reproduction, against a directory holding a real AWS key, a high-entropy
token and a GitHub PAT:
[[allowlists]]
condition = "AND"
paths = ['''\.md$''']
regexes = ['''EXAMPLE_PLACEHOLDER_TOKEN''']
before: scanned ~0 bytes, no leaks found
after: leaks found: 3
The same config with the default "OR" condition still skips, as
documented.
BasitS-hash
requested review from
bryanbeverly,
dustin-decker,
dxa4481 and
zricethezav
as code owners
August 1, 2026 17:41
There was a problem hiding this comment.
Pull request overview
This PR fixes a silent false-negative scenario in the file-enumeration layer where global allowlists using condition = "AND" could cause files to be skipped purely on a path match, preventing their contents from ever reaching the detector for content-based allowlist evaluation.
Changes:
- Updates
shouldSkipPathto only skip early on a path match when a path match is sufficient given the allowlist condition. - Introduces
pathIsSufficientToSkipto centralize the “path-only is enough” logic for allowlists. - Adds table-driven tests covering
ORvsANDbehavior and a nil-config case.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
sources/common.go |
Prevents early skipping on AND allowlists that require content-based checks by gating path-only skipping logic. |
sources/common_test.go |
Adds coverage to ensure enumeration-level skipping matches intended allowlist semantics. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+75
to
+80
| func pathIsSufficientToSkip(a *config.Allowlist) bool { | ||
| if a.MatchCondition != config.AllowlistMatchAnd { | ||
| return true | ||
| } | ||
| return len(a.Regexes) == 0 && len(a.StopWords) == 0 | ||
| } |
| "bufio" | ||
| "bytes" | ||
| "io" | ||
| "regexp" |
Comment on lines
+136
to
+144
| "and condition, paths are the only criterion": { | ||
| // Nothing else has to be checked, so the path is sufficient. | ||
| allowlist: &config.Allowlist{ | ||
| MatchCondition: config.AllowlistMatchAnd, | ||
| Paths: mustCompile(`\.md$`), | ||
| }, | ||
| path: "docs/README.md", | ||
| want: true, | ||
| }, |
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.
Problem
A global allowlist that uses
condition = "AND"with bothpathsandregexescauses matching files to be skipped entirely during enumeration, before any content is read. Every secret in those files goes unreported.Against a directory whose
notes.mdcontains a real AWS key, a high-entropy token, and a GitHub PAT — none of which match the allowlist regex:scanned ~0 bytes— the file is never opened. TheANDcannot possibly be satisfied ( 8000 the regex matches nothing in it), yet the file is dropped anyway.Cause
shouldSkipPathinsources/common.goiterates the global allowlists and skips onPathAllowedalone, without consultingMatchCondition:The detector already handles this correctly —
detect.gochecksconfig.AllowlistMatchAndin bothcheckCommitOrPathAllowed(L784) andcheckFindingAllowed(L849). So the two layers disagree, and the enumeration layer wins by dropping the file before the detector can evaluate the other half of theAND.This seems worth treating as more than a cosmetic bug because the failure is silent and points the wrong way for a secret scanner: a config written to allowlist one pattern within some files instead stops those files being scanned at all. Nothing in the output distinguishes "clean" from "not looked at" other than the byte count.
Fix
Only skip on a path match when the path is sufficient on its own:
condition = "OR"(the default) — unchanged, a single criterion allowlists the finding, so skipping early is correct and stays a useful optimisation.condition = "AND"— only skip whenpathsis the sole criterion. Ifregexesorstopwordsare also set they are content-based and must reach the detector.commitsis deliberately not treated as content-based: it is known before a file is read, and the git source filters allowed commits separately.Verification
pathsonly, defaultORpaths+regexes,condition = "AND"go test -race ./...— 7 packages ok, 0 failuresgo vet ./sources/clean,gofmtcleanTestShouldSkipPathas a table test covering both conditions, the paths-onlyANDcase, and the stopwords variant, plus a nil-config caseNote on the docs
The README documents
conditionunder[[rules.allowlists]]but not under the global[[allowlists]]block, so it is easy to assume the field is rule-scoped. Happy to add that to the README in this PR or a separate one, whichever you prefer.I ran into this while allowlisting placeholder bearer tokens in a project's docs — the scan went green and it took a planted-secret test to notice the files were not being read.