A benchmark score for a JavaScript security scanner is only as good as the items behind it, and a new benchmark called x.js changes what those items are. Described in a single arXiv preprint (2610.18585), x.js is a live, continuously updated benchmark of Node.js taint-style vulnerabilities: 1,159 validated exploits across 965 npm packages in its current release, refreshed as new bugs are disclosed rather than frozen at publication. Everything specific to x.js here is author-reported from that one preprint, whose data runs through mid-2026 (retrieved 2026-11-05), and not yet independently replicated, so treat its numbers as a construction description, not settled fact.
The practical finding worth carrying into any tool decision is this: a live benchmark score is evidence about whether a tool keeps detecting newly disclosed, proof-of-concept-validated bugs. It is not evidence about how the tool will perform on your private codebase. Those are different questions, and the history of Node.js static analysis, discussed below, shows exactly how large the gap can be. The rest of this guide walks through how x.js builds its items, what it deliberately leaves out, and how to weight a live score against the frozen-set numbers vendors have historically advertised.
What x.js is: a live benchmark by the numbers
The preprint describes the benchmark in one sentence: “We presented x.js, a live benchmark of Node.js taint-style vulnerabilities in four classes: code injection, OS command injection, prototype pollution, and path traversal.” Those four classes are the classic source-to-sink problems of the npm ecosystem: attacker-controlled input flowing into eval-like execution, shell invocation, prototype chain mutation, or filesystem paths.
The scale of the current release is concrete: “Its current release contains 1,159 validated exploits for 965 packages,” per the preprint, each expressed in a lifecycle form so tools can be run against the vulnerable state and, when a patch exists, the patched state. Because the benchmark is live and versioned, that count is a snapshot of this preprint’s release, not a permanent figure; anyone citing it later should pull the current release.
The motivating trend in the same paper is worth noting before trusting any older dataset: “vulnerabilities reported in recent years affect substantially larger packages, with the median vulnerable package size increasing from approximately 250 LoC in 2021–2022 to 6,100 LoC in 2026 (§5).” A roughly 24-fold increase in the median size of the code an analyzer must reason about means a tool tuned and marketed on small, older packages is being tested on an easier problem than the one you have.
How items are sourced and verified
The construction pipeline is the first thing to audit in any benchmark, and x.js’s is unusual: “Then an LLM agent writes a candidate PoC, which is then validated. Manual inspection is needed only when a candidate PoC fails validation.” That introduction compresses the detail. PoCs already attached to GHSA or Snyk advisories are collected and reused, and the paper describes PoC creation as “either through an LLM agent or by hand,” with the agent drafting one only “If no source provides a PoC.” Whatever its origin, each candidate is then checked by automated validity analyses, with human review as the fallback rather than the default.
Two consequences follow. First, this is what makes the benchmark live at all: machine-driven sourcing and validation can keep pace with disclosure volume in a way hand-curated datasets cannot. Second, the ground truth is machine-checked rather than expert-adjudicated on the happy path. Validation confirms a PoC works; it does not certify that the PoC represents the most important or most realistic exploit of the underlying bug. When you read a score against x.js, you are reading performance against attack instances that were drafted by an LLM, reused from an advisory, or written by hand, then verified by program analysis. That is a real and useful signal, but it is a specific one.
What the benchmark leaves out, and why exclusions define your threat model
Every benchmark score carries an implicit scope, and x.js is explicit about its edges: “x.js also excludes vulnerabilities whose harmful operation occurs only in a consuming application, as with the shell-quote example in §4.3, and those that require the exploit to be delivered through a user interface.”
The consuming-application exclusion matters more than it may look. It removed 74 of the 1,738 advisories the pipeline collected, under the label “Insufficient on its own,” meaning the package produces something unsafe but the harm occurs only when another component consumes it. A package that is fine in isolation and dangerous only in how an application wires it up is exactly the shape of bug this drops, and a tool that scores perfectly on x.js has demonstrated nothing about detecting it. The UI exclusion is different in kind and far smaller: six advisories, dropped because triggering them “requires interacting with a user interface, which our headless environment cannot reproduce.” That is a limit of the headless validation environment, not a statement about vulnerability classes; a UI-delivered code-injection exploit is inside the four classes and outside the benchmark all the same.
Before giving a score any weight, map the exclusions onto your own incident history and threat model. If your team’s actual bugs skew toward integration mistakes in consuming code, x.js measures the wrong slice of your risk, and a high vendor score on it should not move your decision at all.
Ground truth is partial: what a blocked PoC proves
The preprint is candid about the strength of its own negative cases: “Likewise, a patched version that blocks the PoC provides a negative case for that particular attack, not proof that every variant has been eliminated.”
This cuts in both directions when you read results. A tool that flags the vulnerable version has caught one demonstrated attack path, not the whole vulnerability family. And the benchmark’s own method of confirming a fix, the patched version blocking the PoC, establishes only that this specific PoC no longer works. Variant exploits, the ones an attacker reaches after reading your mitigation, are not what the lifecycle pair tests. Hold both facts when a vendor reports a high detection rate: the rate is computed against items whose “fixed” side is itself a partial guarantee.
Frozen versus live: the comparison frame for any scanner benchmark
The structural argument for liveness is about economics, not freshness for its own sake. A frozen benchmark is a fixed test set. Once it circulates, vendors can tune against it, knowingly or not, and language models trained on public data may have absorbed it. Scores on it drift upward without any corresponding improvement on unseen bugs. A live benchmark keeps adding PoC-validated items after any tuning date, which raises the cost of overfitting. It does not eliminate overfitting; nothing in the evidence shows that. It changes who bears the burden: the vendor must keep detecting newly disclosed bugs rather than defending a historical number.
| Reading question | Frozen benchmark | Live benchmark (x.js pattern) |
|---|---|---|
| What does a score certify? | Performance on a fixed, possibly memorized item set | Performance on items that keep arriving after tuning |
| Overfitting risk | High and invisible: tuning target is public and static | Lower but not zero: items refresh, machine-validated |
| Ground-truth strength | Varies by dataset; often CVE-linked labels | PoC-validated exploits; patched side is one negative case, not variant elimination |
| Coverage statement | Whatever the curators chose at freeze time | Four taint classes, with explicit consuming-app and UI exclusions |
| How to use it | As a floor: expect decay on unseen code | As a currency signal: is the tool keeping up with disclosure |
Use this frame on any scanner benchmark you encounter, not just x.js. Ask what the items are, who verified them, when they stop updating, and what the negative cases actually prove.
The precision wall: reading 57.6% detection against 0.11% precision
The strongest counterweight to any detection-rate headline, live or frozen, comes from a study of nine static analysis tools on Node.js packages (arXiv:2301.05097): “The three best performing tools combined only detect up to 57.6% of all vulnerabilities in the dataset, but at a very low precision of 0.11%.”
Read that pairing slowly. Even combining the three best tools, recall topped out at 57.6% on a 957-vulnerability dataset. And precision of 0.11% means roughly one true positive per nine hundred alerts. The recall number is the one that fits on a marketing slide; the precision number is the one your team lives with, because alert volume is what determines whether anyone keeps reading the scanner’s output in week three.
This is why a detection score on curated, PoC-validated exploits does not transfer as your recall. Benchmark items are selected to be real, reachable, and demonstrable. Your codebase is full of code that merely looks dangerous. The precision problem is a property of static analysis on real code, not of any benchmark’s item selection, so a live benchmark cannot fix it and a good live score should not be read as if it had. The actionable step is unchanged and unavoidable: run the candidate tool on your own repository and measure precision yourself before purchase or rollout.
Why the stakes are structural
A centrality-based study of the npm network (arXiv:2606.14036) modeled 53,481 packages and 78,520 dependency edges, and reports on prior findings that “a single vulnerable package can transitively expose over 100,000 dependent projects” with “an average vulnerable dependency depth of 5.4 hops.” The centrality claim that protecting a selected 534 nodes preserves ecosystem integrity is the study’s own model output and should be read as such.
The operational meaning of 5.4 average hops is that the vulnerable code in your dependency tree is rarely code you chose. It sits several layers down, pulled in by packages pulled in by packages, which is precisely the territory taint-style analysis and dependency auditing are supposed to cover. Combined with the package-size trend from the x.js paper, 250 to 6,100 median LoC, the picture is consistent: more code to analyze, deeper in the tree, refreshed continuously. That is the environment a benchmark is trying to stand in for, and it is why liveness is a genuine property rather than a marketing adjective.
Operational pairing: snapshots, dependency triage, and false-positive filtering
Benchmark scores aside, production detection coverage is an assembly job, and working tools say so themselves. The README for omega-sast, a static analysis engine for JavaScript bundles, states: “Static CVE list: The dependency scanner uses a curated snapshot (30 packages). For production use, pair with npm audit or OSV.dev.” A curated 30-package snapshot is not a CVE feed, and its authors are explicit that npm audit or OSV.dev must run alongside. Treat that as the documented baseline pattern: static analysis for taint flows in your code and bundles, dependency auditing for known-vulnerable versions across the transitive tree.
On the alert-volume side, machine-assisted triage is emerging but its evidence does not transfer freely. FPPredictor (arXiv:2603.10558), a graph-convolutional model for predicting static-analysis false positives, reports “an overall accuracy of up to 96.6%” on CryptoAPI-Bench and 100% on a CamBenchCAP test split. Both are Java cryptography benchmarks. Those numbers say nothing about triaging Node taint alerts, and citing them in a Node.js purchasing argument would be a category error.
How much weight should a live benchmark score carry?
Give it the weight of one specific, valuable input, and no more.
A live, PoC-validated benchmark like x.js answers a question frozen benchmarks answer poorly: does this tool keep detecting newly disclosed taint-style npm bugs? If you are choosing between scanners, a maintained score on a continuously refreshed item set is better evidence of vendor currency than any number earned on a frozen set, and it is reasonable to ask vendors why they are not reporting one.
But three limits bound that weight. The nine-tool study’s 0.11% precision means no benchmark detection rate substitutes for measuring precision on your own repository. The benchmark’s four-class scope and explicit exclusions mean a score says nothing about consuming-application wiring bugs, UI-delivered exploits, or other CWE families; check the exclusions against your threat model before the score means anything. And every x.js-specific claim here rests on one unreplicated preprint that reports no per-tool scores or baselines, and does not say whether static analyzers, LLM-based detectors, or both were evaluated. Nobody can yet tell you which tools do well on it, and any ranking attributed to it today is invented.
I would use a live benchmark score the way I would use a vendor’s changelog discipline: as a filter, not a verdict. Shortlist on it. Then decide on the precision measurement from your own repo, the exclusion check against your incident history, and the pairing with npm audit or OSV.dev for dependency-depth coverage. The reading checklist, verify sourcing and validation, audit exclusions, distrust frozen-set scores, measure precision locally, weight transitive exposure, will outlast this particular benchmark, because the failure modes it guards against belong to security benchmarks generally, not to x.js alone.
Frequently Asked Questions
What are the four vulnerability classes covered by the x.js benchmark?
We presented x.js, a live benchmark of Node.js taint-style vulnerabilities in four classes: code injection, OS command injection, prototype pollution, and path traversal.
What types of vulnerabilities does x.js exclude from its scope?
x.js also excludes vulnerabilities whose harmful operation occurs only in a consuming application, as with the shell-quote example in §4.3, and those that require the exploit to be delivered through a user interface.
What is the recommended production setup for dependency scanning according to omega-sast?
Static CVE list: The dependency scanner uses a curated snapshot (30 packages). For production use, pair with npm audit or OSV.dev.

Join the discussion
Share a useful perspective or ask a question about this article.