node720-static · SAST / taint reachability
node720-static traces untrusted input from source to sink across your code — the same detectors that run in production at runtime, no trigger required. Validated on the npm packages you already depend on: it stays quiet on clean code and fires on the real bugs.
node720-static reuses the runtime detectors byte-for-byte to find source→sink flows in your code — no runtime trigger required. Point it at a directory or a file:
require/import boundaries to the sink.Two separate tests — precision and recall — kept apart on purpose. For a SAST scanner they are not the same question, and a number that blends them hides the only thing that matters: did it find the real bug, and only the real bug?
A · PRECISION — 150 latest popular packages (audited)
These libraries are heavily audited and patched, so there were no real vulnerabilities to find — and node720 found none. The honest metric here is whether it stays quiet:
B · RECALL — 13 packages pinned to known-vulnerable VERSIONS
A different set — the same packages (and others) pinned to the versions that had documented CVEs — to prove it fires on the real bug, and stays silent on the patched build:
// The two are kept separate on purpose. Blending them into "150 packages, 13 CVEs" would imply the CVEs live in the latest packages — they do not: across the 150 audited packages the real-vulnerability count is 0 (there were none to find), and the 13/13 is a by-VERSION battery. Audited code → 0 false positives; vulnerable code → 13/13 recall (the real bug, not the fix). Held-out real-vuln corpus: 92/100, false-positive rate 0/63; every gap is a re-runnable regression test (threat-intel/realworld-corpus.js).
Two headline traces — a real SQL-injection confirmed end-to-end, and a code-injection caught in the vulnerable version but not the patch — then the full ledger of which detector found which real issue.
req.query.appends{ appends } into .find()[email protected] (vulnerable): 1 confirmed code-injection. ejs@latest (patched): 0. Same scan, same rule.
On the vulnerable-version battery the code detector also caught node-serialize’s
eval RCE; the proto floors caught the lodash/minimist/set-value family;
the ReDoS detector caught marked’s catastrophic regex — 13/13.
The full ledger — detector → the real issue it surfaced:
new Function(opts.localsName…) · node-serialize eval RCE — and the vulnerable liquidjs CLI re-tiered honestly (operator input ≠ deploy boundary).LEFT JOIN.(a+)+ / ( …\n*)+) in marked’s inline grammar.spawn(process.argv[0]) were re-tiered from confirmed to honest reachability — argv[0] is the node binary, not attacker input.__proto__ tests), generated bundles (xml2js’s 3.4 MB js_of_ocaml output), and DOM .find() mistaken for a Mongo query (cheerio) — all now excluded by construction.node720-static reuses the runtime detectors byte-for-byte to find source→sink flows — no runtime trigger required. SARIF or text output, guard-aware, inter-procedural and cross-file.