vm2: NodeVM builtin allowlist bypass via `module` builtin's `Module._load` allows sandbox escape
Collected exploitation and prediction evidence
Risk source coverageCISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.
No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.
cve-program · CVE-2026-43999
Publishedvm2 is an open source vm/sandbox for Node.js. Prior to 3.11.0, NodeVM's builtin allowlist can be bypassed when the module builtin is allowed (including via the '*' wildcard). The module builtin exposes Node's Module._load(), which loads any module by name directly in the host context, completely bypassing vm2's builtin restriction. This allows sandboxed code to load excluded builtins like child_process and achieve remote code execution. This vulnerability is fixed in 3.11.0.
- Source published
- 13 May 2026 17:21:22 UTC
- Source modified
- 7 Sep 2026 12:05:02 UTC
- Observed here
- 23 Oct 2026 15:48:27 UTC
- Source terms
- CVE Program Terms of Use
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 3.1 · 9.9 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
euvd · EUVD-2026-30069
Publishedvm2 is an open source vm/sandbox for Node.js. Prior to 3.11.0, NodeVM's builtin allowlist can be bypassed when the module builtin is allowed (including via the '*' wildcard). The module builtin exposes Node's Module._load(), which loads any module by name directly in the host context, completely bypassing vm2's builtin restriction. This allows sandboxed code to load excluded builtins like child_process and achieve remote code execution. This vulnerability is fixed in 3.11.0.
EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.
Original publisher date fields
datePublished: May 13, 2026, 5:21:22 PM
dateUpdated: Sep 7, 2026, 12:05:02 PM
1 related source bulletins · separate from aliases
RHSA-2026:50850: Red Hat Security Advisory: Red Hat Ansible Automation Platform 2.1 security update
CVE-2026-9277 · CVE-2026-43997 · CVE-2026-43998 · CVE-2026-43999 · CVE-2026-44000 · CVE-2026-44001 · CVE-2026-44002 · CVE-2026-44003 · CVE-2026-44004 · CVE-2026-44005 · CVE-2026-44006 · CVE-2026-44007 · CVE-2026-44008 · CVE-2026-44009 · CVE-2026-45411 · CVE-2026-47131 · CVE-2026-47135 · CVE-2026-47137 · CVE-2026-47139 · CVE-2026-47140 · CVE-2026-47141 · CVE-2026-47209 · CVE-2026-48068 · CVE-2026-48801 · CVE-2026-59877
- Source published
- May 13, 2026, 5:21:22 PM (timezone not stated)
- Source modified
- Sep 7, 2026, 12:05:02 PM (timezone not stated)
- Observed here
- 7 Nov 2026 09:37:44 UTC
- Source terms
- ENISA EUVD · source terms and attribution apply
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: euvd · ENISA EUVD · source terms and attribution apply. Source text is retained; field grouping and change labels are provided by ChangeIntel.
EUVD CVSS 3.1 · 9.9 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
nvd · CVE-2026-43999
Publishedvm2 is an open source vm/sandbox for Node.js. Prior to 3.11.0, NodeVM's builtin allowlist can be bypassed when the module builtin is allowed (including via the '*' wildcard). The module builtin exposes Node's Module._load(), which loads any module by name directly in the host context, completely bypassing vm2's builtin restriction. This allows sandboxed code to load excluded builtins like child_process and achieve remote code execution. This vulnerability is fixed in 3.11.0.
NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.
Source analysis status: Modified
Weakness classifications: CWE-863 · CWE-829
Original publisher date fields
published: 2026-05-13T18:16:16.450
lastModified: 2026-10-26T17:10:03.270
1 source CPE criteria · context only
- cpe:2.3:a:vm2_project:vm2:*:*:*:*:*:node.js:*:*
The original JSON retains the logical configuration operators, exclusions and version bounds.
- Source published
- 13 May 2026 18:16:16 UTC
- Source modified
- 26 Oct 2026 17:10:03 UTC
- Observed here
- 27 Oct 2026 11:12:09 UTC
- Source terms
- NIST NVD usage terms; underlying source attribution retained
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 3.1 · [email protected] · Secondary · 9.9 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
CVSS 3.1 · 0b0ca135-0b70-47e7-9f44-1890c2a1c46c · Secondary · 9.9 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
osv · GHSA-947f-4v7f-x2v8
Published## Summary NodeVM's `builtin` allowlist can be bypassed when the `module` builtin is allowed (including via the `'*'` wildcard). The `module` builtin exposes Node's `Module._load()`, which loads any module by name directly in the host context, completely bypassing vm2's builtin restriction. This allows sandboxed code to load excluded builtins like `child_process` and achieve remote code execution. ## Severity **Critical** (CVSS 3.1: 9.9) `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H` - **Attack Vector:** Network — sandboxed code is typically received from external sources (user-submitted scripts, plugin code) - **Attack Complexity:** Low — no special conditions required; `['*', '-child_process']` is a common, documented pattern - **Privileges Required:** Low — attacker needs only the ability to submit code to the sandbox, which is the intended use case - **User Interaction:** None - **Scope:** Changed — escape from sandbox boundary to host system - **Confidentiality Impact:** High — arbitrary command execution on the host - **Integrity Impact:** High — arbitrary command execution on the host - **Availability Impact:** High — arbitrary command execution on the host ## Affected Component - `lib/builtin.js` — `makeBuiltinsFromLegacyOptions()` (lines 109-117) — includes `module` in `'*'` expansion - `lib/builtin.js` — `addDefaultBuiltin()` (lines 86-90) — loads `module` with generic readonly wrapper - `lib/builtin.js` — `SPECIAL_MODULES` (line 61) — does NOT include `module` ## CWE - **CWE-863**: Incorrect Authorization ## Description ### Root Cause: The `module` builtin provides unrestricted host module loading When `builtin: ['*', '-child_process']` is configured, `makeBuiltinsFromLegacyOptions` iterates over `BUILTIN_MODULES` and adds all modules not explicitly excluded: ```js // lib/builtin.js:40 const BUILTIN_MODULES = (nmod.builtinModules || Object.getOwnPropertyNames(process.binding('natives'))) .filter(s=>!s.startsWith('internal/')); // lib/builtin.js:109-117 if (Array.isArray(builtins)) { const def = builtins.indexOf('*') >= 0; if (def) { for (let i = 0; i < BUILTIN_MODULES.length; i++) { const name = BUILTIN_MODULES[i]; if (builtins.indexOf(`-${name}`) === -1) { addDefaultBuiltin(res, name, hostRequire); } } } ``` Node's `builtinModules` includes `'module'` (verified: `require('module').builtinModules.includes('module')` → `true`). Since only `'-child_process'` is excluded, `'module'` passes the filter and gets added. The `module` builtin is NOT in `SPECIAL_MODULES` (which only covers `events`, `buffer`, `util`), so it gets the generic loader: ```js // lib/builtin.js:86-90 function addDefaultBuiltin(builtins, key, hostRequire) { if (builtins.has(key)) return; const special = SPECIAL_MODULES[key]; builtins.set(key, special ? special : vm => vm.readonly(hostRequire(key))); } ``` This wraps Node's `Module` class in a readonly proxy and hands it to the sandbox. ### The readonly proxy does not prevent method calls `ReadOnlyHandler` (bridge.js:940-983) only overrides mutation traps: `set`, `setPrototypeOf`, `defineProperty`, `deleteProperty`, `isExtensible`, `preventExtensions`. It does NOT override `get` or `apply`, which are inherited from `BaseHandler`. `BaseHandler.apply()` (bridge.js:665-677) forwards function calls directly to the host context: ```js apply(target, context, args) { const object = getHandlerObject(this); let ret; try { context = otherFromThis(context); args = otherFromThisArguments(args); ret = otherReflectApply(object, context, args); } catch (e) { throw thisFromOtherForThrow(e); } return thisFromOther(ret); } ``` So `Module._load('child_process')` is forwarded to Node's native `Module._load` in the host context, which loads `child_process` without any vm2 allowlist check. ### Inconsistent defense: some builtins are isolated, `module` is not The codebase IS aware that certain builtins need special handling: - `events`: Gets a complete sandbox-native reimplementation via `lib/events.js` - `buffer`: Custom loader that only exposes the `Buffer` class - `util`: Custom loader that replaces `inherits` with a sandbox-safe version But `module` — which provides access to the host's entire module loading infrastructure via `Module._load`, `Module._resolveFilename`, etc. — gets no special treatment at all. ### Full execution chain 1. Host configures `NodeVM` with `builtin: ['*', '-child_process']` 2. `makeBuiltinsFromLegacyOptions` adds `'module'` to allowed builtins (not excluded) 3. Sandbox code calls `require('module')` → resolver finds `'module'` in builtins → `loadBuiltinModule('module')` 4. Loader calls `vm.readonly(hostRequire('module'))` → returns readonly proxy of Node's `Module` class 5. Sandbox reads `Module._load` → `BaseHandler.get()` returns proxied function 6. Sandbox calls `Module._load('child_process')` → `BaseHandler.apply()` forwards to host 7. Host's `Module._load` loads `child_process` natively (no vm2 check involved) 8. `child_process` module proxied back to sandbox 9. Sandbox calls `child_process.execSync('id')` → executes on host → RCE ## Proof of Concept ```js const { NodeVM } = require('vm2'); // Developer thinks child_process is blocked const vm = new NodeVM({ require: { builtin: ['*', '-child_process'], external: false, }, }); const out = vm.run(` const Module = require('module'); // Module._load bypasses vm2's builtin allowlist entirely const cp = Module._load('child_process'); module.exports = cp.execSync('id').toString(); `, 'poc.js'); console.log(out.trim()); // prints host uid/gid — RCE achieved ``` ## Impact - **Complete builtin allowlist bypass**: Any configuration that allows the `module` builtin (including `['*', '-X']` patterns) can load ANY builtin, including explicitly excluded ones. - **Remote code execution**: Sandboxed code can execute arbitrary commands on the host via `child_process.execSync`. - **Common configuration affected**: The `['*', '-child_process', '-fs']` pattern is documented and widely used by developers who want "all builtins except dangerous ones." - **No special conditions**: Unlike environment-dependent attacks, this works on every Node.js version, every OS, and every vm2 deployment that uses the `'*'` wildcard. - **Additional attack surfaces via `module`**: Beyond `_load`, the `Module` class also exposes `_resolveFilename`, `_cache`, `_pathCache`, and other internals that could be abused. ## Recommended Remediation ### Option 1: Exclude `module` from `BUILTIN_MODULES` entirely (Preferred) The `module` builtin provides unrestricted host module loading and should never be exposed to the sandbox: ```js // lib/builtin.js:40 const DANGEROUS_BUILTINS = new Set(['module', 'worker_threads', 'cluster']); const BUILTIN_MODULES = (nmod.builtinModules || Object.getOwnPropertyNames(process.binding('natives'))) .filter(s => !s.startsWith('internal/') && !DANGEROUS_BUILTINS.has(s)); ``` This prevents `module` from being included even with the `'*'` wildcard. Consider also blocking `worker_threads` and `cluster` which can spawn processes. ### Option 2: Add `module` to `SPECIAL_MODULES` with a safe wrapper If `module` must be accessible, provide a sandbox-safe version that only exposes safe APIs: ```js // lib/builtin.js const SPECIAL_MODULES = { events: { /* ... existing ... */ }, buffer: defaultBuiltinLoaderBuffer, util: defaultBuiltinLoaderUtil, module: function defaultBuiltinLoaderModule(vm) { // Only expose safe, read-only metadata — no _load, no _resolveFilename return vm.readonly({ builtinModules: [...nmod.builtinModules], // Omit _load, _resolveFilename, _cache, createRequire, etc. }); } }; ``` **Tradeoff**: Breaks sandbox code that legitimately uses `Module` APIs, but those APIs are inherently unsafe in a sandbox context. ## Credit This vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai).
- Source published
- 7 May 2026 04:08:55 UTC
- Source modified
- 14 May 2026 20:49:43 UTC
- Observed here
- 24 Oct 2026 13:17:17 UTC
- Source terms
- GitHub Advisory Database · CC-BY-4.0
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS_V3 · CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Publisher rating · CRITICAL