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.

360°node360-static · pre-prod
+
360°node360-rasp · production
=
720°total coverage

move the mousescroll — the logo recolors

01 — THE IDEA

One core. One shell.
Nothing gets in.

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.

INNER
Pre-production
node360-static · SAST / taint · every line before deploy · node360 scan
360°
OUTER
Production
node360-rasp · semantic sink-side enforcement · version-gated virtual patching
360°
outer · production shell — 360°
inner · pre-prod core — 360°
DETECTOR SPOTLIGHT · SQL · CWE-89

Watch one detector think.

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.
Tainted in a value slot … WHERE id = '‹taint›' PASS — data only, no new tokens
Tainted as an instruction … ` 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.

1,115
Real 2025–26 CVEs
PoC-hunted corpus
91/100
node360-static score
held-out vulns · 0 FP
73%
RASP blocks of
addressable CVEs
420/420
Tests passing
59 real-CVE fixtures
02 — TWO CIRCLES, TWO ENGINES

One detector core, scanned twice.

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.

● INNER · PRE-PRODUCTION
node360-static
A false-positive-safe SAST / taint reachability scanner. Reuses the runtime detectors byte-for-byte to find source→sink flows in your code — no runtime trigger required.
  • Inter-procedural + cross-file taint
  • Guard-aware (escapers, validators, allowlists, path-containment)
  • SARIF or text output: node360 scan ./src --format sarif
  • IAST bridge upgrades reachable → confirmed with a runtime trace
360°
● OUTER · PRODUCTION
node360-rasp
In-process Runtime Application Self-Protection. Wraps the concrete dangerous APIs and decides at the sink, with request provenance, whether attacker input changed an operation's meaning.
  • Semantic sink-side enforcement (child_process, fs, vm, sqlite)
  • Containment-based taint provenance via AsyncLocalStorage
  • Version-gated virtual patching of known CVEs
  • Zero-code-change bootstrap via NODE_OPTIONS
360°
03 — COVERAGE, STATED HONESTLY

Measured against 1,115 real CVEs.

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 classCVEsRASP blocks% of addressable
SSRF13710185%
Command injection1438881%
SQL injection453680%
Path traversal1509473%
Prototype pollution1499170%
Open redirect382687%
Code injection822660%
Cross-site scripting2274759%

// 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.

04 — THE DETECTOR CORE

Sixteen semantic detectors.

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.

SQL
CWE-89
Taint-gated tokenizer differential — "tainted input added SQL instructions".
Command
CWE-78
Shell-argv metacharacter analysis; argv[0] taint when shell:false.
Path
CWE-22
Canonicalize-and-contain; only attacker-influenced reads outside the root.
Proto
CWE-1321
Prototype pollution narrowed to real gadget shapes — FP-free.
Deserialize
CWE-502
FP-free literal-marker scan before a function-reviving deserializer.
NoSQL
CWE-943
Operator injection & query-operator code execution.
SSRF
CWE-918
Inspects only user-controlled destinations; internal calls pass.
Template
CWE-1336
Server-side template injection (ejs / pug / handlebars / liquid).
Code
CWE-94/95
Untrusted input reaching vm.*, Function(), or a library eval.
XML
CWE-611/776
XXE external-entity & entity-expansion (billion laughs).
JWT
CWE-347
Signature-bypass / "alg:none" detector.
SAML XSW
CWE-347
XML signature-wrapping structural auth-bypass.
Redirect
CWE-601
User-controlled external redirect targets only.
Mass-assign
CWE-915
Mass-assignment / BOPLA (OWASP API3).
Upload
CWE-434
Type-confusion via MIME-trust on file upload.
Supply-chain
CWE-506/829
Runtime-behavior detector for malicious package activity.
05 — HOW THE RASP WORKS

Provenance in. Decision at the sink.

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.

Source
Mark untrusted input
The Express / Koa adapter marks request data as untrusted at the boundary; advanced sources cover RAG context, agent memory, tool args and values read back from a datastore.
Layer 3
Request context + taint set
An AsyncLocalStorage store carries per-request provenance and a containment-based taint set across awaits.
src/context.js · src/taint.js
Normalize
Normalize once, fully
Multi-pass decode, NFKC and null-byte handling before any decision — so encoding tricks can't slip a sink.
src/normalize.js
Layer 2
Sink wrappers decide
The concrete dangerous APIs (child_process, fs, vm, node:sqlite) are wrapped; at the sink a detector asks whether attacker input changed the operation's meaning, and blocks before it executes.
src/install.js · src/detectors/*
Rules
Virtual patch CVEs
A class-A request-signal evaluator applies hot-loadable, version-gated virtual patches that fail closed.
src/rules/engine.js
06 — BEYOND THE OWASP TOP 10

Virtual patches, LLM & agentic threats.

Virtual patching
CVE-2025-29927
Next.js middleware auth bypass via x-middleware-subrequest. The rule strips the header before app logic so middleware always runs — fails closed, hot-loadable, version-gated.
OWASP LLM Top 10
Prompt & output guard
Secret redaction on outbound responses (LLM02 / LLM07), RAG context marked untrusted (LLM08), and supply-chain runtime behavior (LLM03).
Agentic threats
Tool-dispatch allowlist
guardToolDispatch blocks non-allowlisted tools and taints LLM-controlled arguments (LLM06 / Agentic-T7); taintMemory defends memory poisoning (T1).
07 — GET STARTED

Two lines in. 360° + 360°.

Production — node360-rasp

// 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

Pre-production — node360-static

# 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.

08 — HONEST POSTURE (READ THIS)

What it is — and isn't.

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

The Node.js Vulnerability Landscape.

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.

  • 1,142 real CVEs
  • 28% on AI platforms
  • 0% invented
  • 13 pages
Get the PDF

Where should we send it?

No spam — the report, plus the occasional Node.js security finding. Unsubscribe anytime.