Node.js security · pre-production + production
720° of Node.js security. Two circles — a verified core scanned before deploy, wrapped by a runtime shell that stops attacks at the sink. One detector core. Zero blind spots.
move the mousescroll — the logo recolors
The inner circle is pre-production: node360-static scans every line of source with taint analysis before it ships. The outer circle is production: node360-rasp wraps the running app and decides — at the sink, with request provenance — whether attacker input changed the meaning of an operation. Both engines share the same detector core, byte-for-byte.
The SQL detector is not a regex over the payload. It is a taint-gated tokenizer differential: tainted input that fills a value slot is data and passes; tainted input that contributes a keyword, operator, comment or quote-break changed the statement's meaning — that is injection. Here it is on a real, non-obvious 2026 CVE.
// CVE-2026-41640 · @nocobase/database // attacker controls an association name; recursive eager- // loading concatenates it straight into SQL — past every value check. app.get('/api/:table', (req, res) => { const appends = req.query.appends // ← untrusted source · tainted db.repository(req.params.table) .find({ appends }) // recursive eager load }) // …deep in the ORM, once per association: sql += ` LEFT JOIN `+assoc+` ON …` // ← tainted span reaches the SQL sink // request: ?appends=x` ON 1=1; DROP TABLE users-- [node360] BLOCK sink=db.query detector=sql (CWE-89) tainted span contributed SQL tokens: IDENT-break · KEYWORD ON · OP = · SEP ; · KEYWORD DROP · COMMENT -- → not a value literal — statement meaning changed. fail-closed.
… WHERE id = '‹taint›'
PASS — data only, no new tokens
… ` ON 1=1; DROP TABLE …
BLOCK — keyword + operator + separator
CVE-2026-41640 — @nocobase/database, SQL injection via string concatenation through recursive eager loading. The attacker-controlled association name is concatenated into a JOIN deep inside the ORM, several calls away from the request. A value-only or regex check misses the laundered flow — the tokenizer differential sees the tainted span emit SQL instructions and blocks at the sink. The same detector module runs in node360-static to flag this before deploy.
The same 16 detectors run statically over your source and dynamically at the sink. What you fix before deploy and what you block in production come from one source of truth.
Every number comes from a hand-built coverage matrix over real, PoC-hunted 2025–2026 npm CVEs — joined deterministically from OSV.dev, FIRST.org EPSS and CISA KEV. No invented vulns. The RASP blocks 47% of all observed CVEs (523/1,115) and 73% of the 717 we judge addressable.
| Detector class | CVEs | RASP blocks | % of addressable | |
|---|---|---|---|---|
| SSRF | 137 | 101 | 85% | |
| Command injection | 143 | 88 | 81% | |
| SQL injection | 45 | 36 | 80% | |
| Path traversal | 150 | 94 | 73% | |
| Prototype pollution | 149 | 91 | 70% | |
| Open redirect | 38 | 26 | 87% | |
| Code injection | 82 | 26 | 60% | |
| Cross-site scripting | 227 | 47 | 59% |
// node360-static scores 91/100 on the held-out real-vuln corpus (reachability recall 49/54 = 90.7%, false-positive rate 0/64). 71 of the top-100 highest-risk Node.js exploits map to a wired node360 detector class. Source: threat-intel/coverage-360.json.
Each one reasons about meaning at the sink, not regex over the payload — taint-gated, structural floor, false-positive-aware. The same modules run in both engines.
Structural detection is the dependable floor; taint is an additive false-positive reducer. It is semantic sink-side enforcement — not a sound dataflow engine, and we say so.
// install sink wrappers + virtual patches const rasp = require('node360-rasp'); rasp.protect({ fsRoot: '/srv/app/data' }); app.use(express.json()); app.use(rasp.express()); // mark sources + patch CVEs /* ...routes... */ app.use(rasp.expressErrorHandler()); // blocks → 403
# or zero-code-change bootstrap NODE_OPTIONS='--require ./boot.cjs' node server.js
# scan source before you ship $ npx node360 scan ./src # machine-readable for CI $ npx node360 scan ./src \ --format sarif --fs-root ./src # aggressive reachability mode $ npx node360 scan ./src --aggressive
// Requires Node ≥ 22.15.0. For async Express routes, wrap with rasp.asyncRoute(...) so a block after an await still becomes a 403.
node360 is semantic sink-side enforcement + containment-based provenance + version-gated virtual patching, with structural detection as the dependable floor. It is deliberately not sold as a sound dataflow engine. The limits are part of the design.
Free threat report · 2025–2026
1,142 real CVEs analysed — the AI-platform surge (28% of all Node.js CVEs), exploitation signals, the most-targeted components, and what's runtime-defensible. 13 pages, free.