feat(scan): LDAP/XPath/NoSQL/host-header rules — coverage 79.8% → 83.0% (v0.11.0) - #104
Merged
Conversation
Detection coverage 79.8% to 83.0% (103 to 107 of 129), FPR still 0%, and zero
new findings on capacitor (10), the self-scan (67), ShortsStudio (0) or
koajs/router (0).
Two of the rules shipped in 0.10.0 passed their unit tests but did nothing on
the real corpus — the tests used simplified fixtures. Both fixed, and the tests
now reproduce the real conditions:
- py-ldap-injection: the whole fixture is under `if False:` with a
module-level `from ldap.filter import escape_filter_chars`, and the
`/escap/i` guard matched that import in every window — exonerating the
unescaped filter too. The guard is now an escaper *call* (`escape…(`), which
the import is not.
- py-xpath-injection: the `"`-delimited f-string contains a `'`
(`text()='{x}'`), and a class excluding both quotes stopped before the
interpolation. Now one variant per delimiter quote.
Two new, precise rules:
- js-nosql-injection: a request object passed straight to `find`/`update`
(CWE-943) — a Mongo operator-injection / auth bypass.
- js-host-header-trust: a URL built from `req.headers.host` (CWE-346) — the
password-reset-poisoning shape.
Also built and dropped: a recursive-merge prototype-pollution rule. The bare
`target[key] = source[key]` copy is the safe allow-listed shape
(`updates[field] = body[field]` over an `allowedFields` list) as often as the
vulnerable one — whether it is a sink depends on the key's origin and which of
endless guard idioms filters it, which line matching cannot decide. It flagged
legitimate merges in Capacitor and in this repo's own web app, so it is left to
KNOWN_GAPS; `js-prototype-pollution` still catches the explicit `__proto__`
literal.
Gate floor moves 76 to 80. 157 tests, up from 154.
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. |
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.11.0. The third and final coverage tier: 79.8% → 83.0% true-positive against the testbed, FPR still 0%, zero new findings on capacitor (10), the self-scan (67), ShortsStudio (0) or koajs/router (0).
Two rules from 0.10.0 that never actually fired
They passed their unit tests but did nothing on the real corpus — the tests used simplified fixtures. The testbed caught what the unit tests didn't, which is exactly why the coverage gate exists.
py-ldap-injection— the fixture is underif False:with a module-levelfrom ldap.filter import escape_filter_chars, and my/escap/iguard matched that import in every window, exonerating the unescaped filter too. The guard is now an escaper call (escape…(), which an import is not.py-xpath-injection— the"-delimited f-string contains a'(text()='{x}'), and a class excluding both quotes stopped before the interpolation. Now one variant per delimiter quote.Both tests now reproduce the real conditions (import-present, inner-quote) so they'd fail against the old rules.
Two new precise rules
js-nosql-injection(CWE-943) — a request object passed straight tofind/update, i.e. Mongo operator injection / auth bypass.js-host-header-trust(CWE-346) — a URL built fromreq.headers.host, the password-reset-poisoning shape.Built and dropped: recursive-merge prototype pollution
High-value for npm, so I tried hard — three rounds of tightening. But the bare
target[key] = source[key]copy is the safe allow-listed shape (updates[field] = body[field]over anallowedFieldslist) as often as the vulnerable one. Whether it's a sink depends on where the key comes from and which of endless guard idioms filters it — a whole-function question line matching can't decide. It flagged legitimate merges in Capacitor and in this repo's own web app, so it's left toKNOWN_GAPS.js-prototype-pollutionstill catches the explicit__proto__literal.That's the discipline the whole effort runs on: a rule that FPs on real code gets a scanner uninstalled, so one ambiguous class isn't worth two testbed lines.
Where coverage stands
The remaining ~22 misses are now almost entirely the classes that genuinely need whole-function reasoning — CSRF, IDOR, TOCTOU, missing authorization, session fixation, integer overflow, and the sanitize-upstream family whose vulnerable and safe lines are identical. Chasing them with line matching spends the 0% FPR that makes the tool worth installing.
Verification
tsc --noEmitclean.