feat(scan): path exclusion via --exclude and .threatcrushignore (v0.8.0) - #101
Merged
Conversation
Release 0.8.0.
Scanning the ThreatCrush repo with ThreatCrush reported 141 findings, and 57
of them were the scanner detecting its own reflection: the rule definitions
(regexes and example strings that match the very patterns they describe) and
the test fixtures (deliberately-vulnerable sample code). Real findings in the
product were buried under them.
Adds a general exclusion mechanism — useful to any consumer for generated
output or vendored trees, not just the self-scan:
- `--exclude <glob>` on `threatcrush scan`, repeatable
- a `.threatcrushignore` file at the scan root, read automatically and
merged with `--exclude`
Globs are gitignore-flavoured: a bare name matches at any depth, a pattern
with a slash is anchored to the root, `*` stays within a path segment and `**`
crosses them. A directory match prunes the whole subtree.
Excluding is not the same as finding nothing. `ScanReport.excluded` counts the
skipped paths — a pruned directory once, not per file — and the CLI prints it,
so a scan quieted by a broad glob cannot be mistaken for a clean one, the same
guarantee `suppressed` already carries.
A committed `.threatcrushignore` excludes this repo's rule sources and
fixtures. The self-scan drops 141 to 67, and the residual is product code plus
two example-config secrets — real targets, not reflections. The daemon and any
scan honour the file too, since it is read in `scanPath`.
Coverage gate unchanged (the testbed has no ignore file): TPR 65.9% / FPR 0%.
132 package tests, up from 125; the glob matcher is pinned in both directions
(over- and under-matching) by its own tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ThreatCrush Security Scan67 finding(s) HIGH/CRITICAL: 11 | MEDIUM: 55 | LOW: 1
…and 17 more. Full results in the Security tab. Snippets are redacted; ThreatCrush never prints matched credential material. |
ralyodio
force-pushed
the
feat/exclude-paths
branch
from
August 11, 2026 06:34
ed9e0ba to
a89ff03
Compare
CodeQL flagged js/polynomial-redos in the glob compiler added for --exclude,
and it was right twice over:
- whatever cross-segment regex the globs compiled to backtracks on a path
full of slashes, the exact shape this scanner's own redos-nested-quantifier
rule exists to catch;
- the trailing-slash trim `pattern.replace(/\/+$/, '')` is unanchored, so
`replace` retries at every start position and is quadratic on a pattern
value that is all slashes.
So the path is no longer run through one generated regex, and the trims no
longer use a backtracking one. `compileExcludes` splits both the pattern and
the path on `/` and aligns them with a two-pointer — the classic `**` match —
where `**` consumes zero or more segments and every other part matches exactly
one. Each per-segment matcher is a trivial `^…$` with single quantifiers
(consecutive `*` are collapsed so no `[^/]*[^/]*` adjacency survives), tested
against one bounded segment, never the whole path. Trailing slashes are trimmed
with a loop rather than an unanchored regex.
Semantics unchanged — every existing exclusion test still passes — plus cases
pinning a trailing `**` (matches files beneath, not the directory itself) and
that a 50,000-slash path and a 50,000-slash / 5,000-star pattern all resolve in
under a millisecond. walk.ts carries no findings and needs no suppression; the
ReDoS surface is gone, not silenced.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ralyodio
force-pushed
the
feat/exclude-paths
branch
from
August 11, 2026 06:39
a89ff03 to
d4f4b07
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.
Release 0.8.0. Adds path exclusion, and uses it to stop the self-scan drowning in the scanner detecting its own reflection.
The problem
Scanning the ThreatCrush repo with ThreatCrush reported 141 findings, and 57 were self-reference:
code-rules.ts,secret-rules.tscontain regexes and example strings that match the very patterns they describe;__tests__holds deliberately-vulnerable sample code.Real findings in the product code were buried under the tool detecting itself.
The feature
A general exclusion mechanism — useful to any consumer for generated output or vendored trees, not only the self-scan:
--exclude <glob>onthreatcrush scan, repeatable.threatcrushignoreat the scan root, read automatically and merged with--excludeGlobs are gitignore-flavoured: a bare name (
__tests__) matches at any depth, a pattern with a slash is anchored to the root,*stays within a segment and**crosses them. A directory match prunes the whole subtree.Excluding ≠ finding nothing
ScanReport.excludedcounts the skipped paths (a pruned directory once, not per file) and the CLI prints it:— the same "never silently quiet" guarantee
suppressedalready carries. A scan hushed by a broad glob can't be mistaken for a clean one.Applied to this repo
A committed
.threatcrushignoreexcludes the rule sources and fixtures:Self-scan: 141 → 67. The 57 self-references drop to 2 — and those 2 are real example-config secrets in
spend-guard, not rule definitions, so they're correctly left in. Everything read isscanPath, so the daemon and every scan honour the file, not just the CI workflow.Verification
exclude.test.tsasserts bare-name-at-any-depth, slash-anchoring,*vs**segment behaviour, subtree pruning, comment/blank handling, and that a.in a pattern is literal (not "any char").tsc --noEmitclean in package and CLI.