Editorial

vm2 Was Abandoned After a CVSS 10.0 Sandbox Escape. Millions of Projects Still Depend on It.

RobertUpdated May 7, 20265 min read
Dark terminal-style feature image. White text reads: It was abandoned. Not patched. Millions still depend on it. Below it: vm2 in teal monospace. Attestd branding bottom left.

vm2 Was Abandoned After a CVSS 10.0 Sandbox Escape. Millions of Projects Still Depend on It.#

Four JavaScript runtimes and sandboxes are now live on Attestd. Coverage moves from 39 to 43 products.

New products: vm2, Node.js, Deno, Hermes

The story that motivates this batch is vm2. The others are the broader coverage expansion. But vm2 is worth understanding on its own terms before anything else.


The vm2 situation#

vm2 is a Node.js sandbox that runs untrusted JavaScript in a separate V8 isolate. It was widely used in any server that evaluates user-supplied scripts -- plugin systems, low-code platforms, agent backends, eval-as-a-service tools. The premise was simple: run untrusted code safely by isolating it from the host process.

Two back-to-back CVEs broke that premise entirely.

CVE-2022-36067 (CVSS 10.0): A crafted JavaScript payload escapes the sandbox and executes arbitrary code on the host. No authentication required. The mechanism exploits the way vm2 handles certain JavaScript constructs at the V8 boundary.

CVE-2023-32314 (CVSS 9.8): A sandbox escape via prototype chain manipulation. Again, host-level code execution relative to the parent process.

After CVE-2023-32314 the maintainers issued a final stub release, posted a notice that the sandbox is not fixable in its current architecture, and abandoned the project. The notice is still on the GitHub repository.

The npm package still has millions of weekly downloads.

curl "https://api.attestd.io/v1/check?product=vm2&version=3.9.10" \
  -H "Authorization: Bearer YOUR_API_KEY" | jq .
{
  "product": "vm2",
  "version": "3.9.10",
  "supported": true,
  "risk_state": "critical",
  "actively_exploited": false,
  "remote_exploitable": true,
  "authentication_required": false,
  "patch_available": true,
  "fixed_version": "3.9.11",
  "cve_ids": ["CVE-2022-36067", "CVE-2023-32314"],
  "confidence": 0.95
}

One clarification on fixed_version: the API returns 3.9.11 as the CVE patch boundary, but 3.9.11 predates the final stub release and the abandonment notice. There is no version of vm2 that represents ongoing maintenance. The correct action is migration, not upgrade. Any active vm2 dependency is a migration candidate regardless of what version you are pinned to.

# Post-abandonment versions have no NVD CVEs
curl "https://api.attestd.io/v1/check?product=vm2&version=4.0.0" \
  -H "Authorization: Bearer YOUR_API_KEY"
# → risk_state: "none"

risk_state: none for 4.0.0 means NVD has no CVEs mapped to that version range. It does not mean vm2 is safe. It means the CVE database has not caught up with a version that was released as an abandonment stub. Context matters.


Node.js#

The largest runtime in this batch by CVE volume. CVEs span the HTTP stack, bundled dependency exposures (V8, libuv, OpenSSL land as Node CVEs), and permission model bypasses in newer versions.

One technical detail worth noting: NVD has two CPE namespaces for Node.js. The canonical cpe:2.3:a:nodejs:node.js and a secondary nodejs:nodejs namespace that produces overlapping noisy results when merged. Attestd uses only the canonical namespace to avoid sentinel inflation. This is a small example of why accurate CVE coverage is not simply a matter of hitting the NVD API -- namespace selection materially affects result quality.

curl "https://api.attestd.io/v1/check?product=nodejs&version=18.0.0" \
  -H "Authorization: Bearer YOUR_API_KEY"

Notable CVE: CVE-2023-39332 (CVSS 7.8) -- HTTP request smuggling via the Undici/Node Fetch path, affecting versions before 18.18.2.

Current results:

  • nodejs@18.20.0 returns risk_state: high, fixed_version: 20.20.0
  • nodejs@20.18.3 returns risk_state: high, fixed_version: 20.20.0

Deno#

Rust and V8 runtime with a permission model and modern HTTP stack. NVD's deno:deno CPE has cleaner semver alignment than most other products in this batch, with fewer legacy naming issues.

CVE surface is smaller than Node.js but includes critical issues when memory safety or sandbox boundaries fail. CVE-2022-24783 (CVSS 10.0) is a WebAssembly memory exposure in affected Deno 1.x releases, patched in 1.20.3.

Current results:

  • deno@1.0.0 returns risk_state: high, 12 CVEs, fixed_version: 2.1.13
  • deno@2.0.0 returns risk_state: high, 9 CVEs, fixed_version: 2.1.13

Hermes#

Meta's JavaScript engine for React Native, optimised for ahead-of-time bytecode compilation and fast startup. Hermes is bundled inside React Native apps rather than exposed as a standalone server, but NVD tracks facebook:hermes with clean version ranges.

CVEs cluster around compiler and runtime memory handling. CVE-2020-1911 (CVSS 9.8) is an out-of-bounds write in the Hermes compiler triggered by crafted source input, affecting versions before 0.4.3.

Modern Hermes releases return risk_state: none. The coverage is relevant for React Native teams shipping a bundled JavaScript engine -- same supply chain hygiene argument as server-side, different deployment context.


Using the JS SDK#

This batch ships alongside the Attestd JavaScript SDK. All four products are queryable from Node.js with the same typed interface:

import { Client } from '@attestd/sdk';
 
const client = new Client({ apiKey: process.env.ATTESTD_API_KEY });
 
const runtimes = [
  { product: 'vm2', version: '3.9.10' },
  { product: 'nodejs', version: '18.20.0' },
  { product: 'deno', version: '1.0.0' },
];
 
for (const { product, version } of runtimes) {
  const result = await client.check(product, version);
  if (result.riskState === 'critical' || result.riskState === 'high') {
    console.log(`${product}@${version}: ${result.riskState}`);
    if (result.fixedVersion) {
      console.log(`  Fix: ${result.fixedVersion}`);
    }
  }
}

What to know about these results#

Two clarifications that apply across this entire batch.

risk_state: none does not mean safe. For vm2@4.0.0 it means NVD has no CVE entries for that version range. The project was abandoned. For hermes modern releases it means the same thing -- no open CVE ranges in NVD. Neither is a clean bill of health in the conventional sense.

fixed_version reflects the CVE patch boundary, not a maintenance commitment. For vm2, the patch boundary version predates the abandonment notice. Upgrade guidance for vm2 is migrate, not patch.

Full product reference at attestd.io/docs/products.

Get an API key at api.attestd.io/portal/login. Free tier, 1,000 calls a month, no credit card required.