vm2 Ships 12 Sandbox Escapes; piscina, devalue and NestJS Fixes
vm2 3.11.7 fixes twelve sandbox escapes to host RCE. Also: a piscina prototype-pollution RCE gadget, a devalue SSR memory leak, a NestJS middleware bypass.
vm2 published twelve sandbox-escape advisories on October 1, ten of them critical, and every one is fixed in 3.11.7. If your app runs user- or model-supplied JavaScript through vm2, sandboxed code can reach the host process and run commands. The same 48 hours brought a critical prototype-pollution gadget in piscina, a memory leak in devalue that can ship another user's request into your SSR HTML, a middleware bypass in NestJS's Fastify adapter, and a certificate check gap in @grpc/grpc-js.
What shipped
vm2: twelve ways out of the sandbox
- Package:
vm2. Every advisory covers some part of the 3.x line up to 3.11.6 (several start at 3.11.3 or 3.11.4; the oldest range goes back further). Fixed in 3.11.7. - Severity: ten critical, two high. Examples: GHSA-647f-g98j-qq25 (CVE-2026-92937, CVSS 10.0), GHSA-8686-vhfx-7r3j (CVE-2026-92957, 9.9), GHSA-27g9-p43v-cw3v (CVE-2026-92944, 9.8). The full list is in Sources.
- Impact: untrusted code gets host privileges. The routes differ. One gets around the fix for an earlier advisory by registering a promise rejection handler through
Function.prototype.call. One abuses a stale V8 protector on Node.js 26. One works because a deny rule written as-node:child_processnever blocks plainchild_process. Others go throughnode:sqlite,crypto.setEngine, or the TLS trust store. Thevm2CLI also turns out to run scripts with no isolation at all (GHSA-jxxv-8r27-vm4p). - Do:
npm install vm2@latest, then read the explainer below before you count on it again.
piscina: prototype pollution becomes code execution in workers
- Package:
piscina. Affected below 4.9.4, 5.0.0 through 5.3.1, and 6.0.0-rc.1 through rc.4. Fixed in 4.9.4 / 5.3.2 / 6.0.0-rc.5. - Severity: critical, CVSS 9.2. GHSA-67c8-pqhq-4rmx (CVE-2026-102992).
- Impact:
ThreadPool.optionsis a plain object, so any option without a default is read fromObject.prototype. If something else in the process lets an attacker pollute the prototype, they controlexecArgv,envand other worker options, which means code execution in the worker threads. piscina doesn't create the pollution. It turns a pollution bug somewhere else into RCE. - Do:
npm install piscina@latest(orpiscina@4.9.4on the 4.x line).
devalue: a 2-byte Buffer can leak 64 KB of someone else's request
- Package:
devalue, the serializer that SvelteKit and Nuxt use for SSR payloads. The memory leak affects 5.1.0 through 5.9.2. Fixed in 5.9.3. - Severity: high, CVSS 7.5. GHSA-j22f-vq7h-c4qm (CVE-2026-92708).
- Impact:
stringifyandunevalserialize a typed array's whole backingArrayBuffer. For a NodeBuffer, that buffer is Node's shared process-wide pool. If a public page'sload()returns a smallBuffer, the HTML can include up to 64 KB of unrelated memory: other requests' bodies andAuthorizationheaders. No login needed. The same release fixes a quadratic expansion inuneval(GHSA-mcm9-63f2-9j32) and an unhandled rejection instringifyAsync(GHSA-x5rw-q4pp-hg5g). The advisory itself calls the second one very hard to exploit. - Do: refresh your lockfile so devalue resolves to 5.9.3. Until then, wrap Buffers before returning them:
new Uint8Array(buffer).
@nestjs/platform-fastify: absolute-form URLs skip path middleware
- Package:
@nestjs/platform-fastify. Affected below 11.2.4 and 12.0.0 through 12.0.1. Fixed in 11.2.4 / 12.0.2; the advisory recommends 11.2.5 / 12.0.3. - Severity: high, CVSS 7.4. GHSA-9c5c-9qcx-q35q.
- Impact: send
GET http://host/admin HTTP/1.1instead ofGET /adminand the handler runs, but middleware bound withforRoutes()doesn't. If that middleware does your auth checks, they never happen. A reverse proxy that rewrites the request target to origin-form reduces the exposure. - Do:
npm install @nestjs/platform-fastify@latest. Move auth into guards, which run per route.
@grpc/grpc-js: unverified client certificates reported as authorized
- Package:
@grpc/grpc-js. Affected below 1.13.6 and 1.14.0 through 1.14.4. Fixed in 1.13.6 / 1.14.5. - Severity: high, CVSS 7.4. GHSA-m9gg-hp2v-232j (CVE-2026-101916).
- Impact: if a server is created with
requireClientCertificate: false,getAuthContext()can't tell verified certificates from unverified ones. Any server that authenticates from that value, including xDS RBAC setups, accepts an attacker's self-presented certificate as an identity. - Do: upgrade. As a workaround, set
requireClientCertificate: true.
In-process sandboxes, explained
A sandbox escape is what happens when code you meant to confine reaches the capabilities of the process hosting it: process, require, the filesystem, the network. In Node.js the usual setup is to run untrusted code in the same process as your app, behind a JavaScript layer such as vm2 or plain node:vm, and hope the layer holds.
It mostly doesn't, and the reason is built in. Sandbox and host share one heap, one set of engine internals and one event loop. Every object that crosses the boundary — a returned value, a thrown error, a promise — can carry a reference back to the host. One leaked process object is enough. vm2's twelve advisories are twelve such references, and some of them get around fixes for earlier ones. Node's own documentation says node:vm is not a security mechanism.
This keeps showing up in AI-built apps because "let the model write code and run it" is now an ordinary feature: chart generators, data-cleaning agents, plugin systems, "code interpreter" tools. Ask a coding agent to execute generated JavaScript safely and the shortest working answer is a vm2 or node:vm wrapper. It passes a demo and fails on the first hostile input.
// Vulnerable: model output runs inside your server process
import { NodeVM } from "vm2";
const vm = new NodeVM({ require: { builtin: ["*", "-node:child_process"] } });
const result = vm.run(llmGeneratedCode); // one escape = your env vars, your DB, your shell
// Safer: a separate, disposable process with nothing worth stealing
import { execFile } from "node:child_process";
execFile("node", ["--permission", "runner.js"], {
env: {}, // no secrets inherited
timeout: 5_000,
input: llmGeneratedCode,
});
// Better still: a container or microVM sandbox with no network and no credentials.
The fix that holds is a boundary the operating system enforces: a separate process with an empty environment, Node's permission model, or ideally a container or microVM with no network and no credentials. If the code escapes, it lands somewhere with nothing to take.
Review check: wherever untrusted input meets eval, new Function, vm.run, or a sandbox library, ask: if this code got full control of the process it runs in, what could it reach? If the answer includes your environment variables or database, the sandbox is your only defence, and today's advisories show how thin that is.
Check your own repo
npm ls vm2 piscina devalue @nestjs/platform-fastify @grpc/grpc-js # which are in your tree?
npm audit --omit=dev # npm's view of the same advisories
npx guardvibe@3.47.0 audit . # 3.47.0 flags the affected @grpc/grpc-js versions (VG1182)
grep -rn "vm2\|node:vm\|new Function(" src/ # where untrusted code might run
Sources
- vm2: GHSA-647f-g98j-qq25, GHSA-8686-vhfx-7r3j, GHSA-27g9-p43v-cw3v, GHSA-qhwx-74w5-xhxq, GHSA-h85j-hv3c-qfgq, GHSA-c48m-32m9-vx93, GHSA-8hr7-r645-pc6w, GHSA-6w8r-xxw2-g3hx, GHSA-46pr-c5wc-xffx, GHSA-98xx-8mx4-x7cm, GHSA-jxxv-8r27-vm4p, GHSA-6rh5-qq4q-97xh
- piscina: GHSA-67c8-pqhq-4rmx
- devalue: GHSA-j22f-vq7h-c4qm, GHSA-mcm9-63f2-9j32, GHSA-x5rw-q4pp-hg5g
- NestJS: GHSA-9c5c-9qcx-q35q
- gRPC: GHSA-m9gg-hp2v-232j
- Node.js documentation, node:vm: "The node:vm module is not a security mechanism."