Skip to content

Releases: patriksimek/vm2

v3.11.5

18 May 15:35
7a1f510

Choose a tag to compare

What's Changed

Bug fixes

  • #566 — Restore util.inspect output on Node 26+. console.log(vm.run(...)) was rendering as Proxy(Proxy({})) / Proxy(Proxy([])) instead of the underlying value. Triggered by Node 26's stricter handling of nested proxies in the inspector.
  • #567 — Restore array iteration on vm.freeze()'d host arrays. Calling .map() / .filter() / .forEach() etc. inside the sandbox on a frozen host object containing arrays threw
    TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target. Regression from the 3.11.0 proxy-invariant hardening.
  • #568 — Fix .node extension handler key in lib/resolver.js (the key was ' .node' with a leading space, so native addon resolution silently fell through to the default path). Thanks to @cherr-cc.

Upgrade Notes

Drop-in replacement for 3.11.4. No API or configuration changes.

Full Changelog: v3.11.4...v3.11.5

v3.11.4

18 May 12:42

Choose a tag to compare

Ten advisories closed. Patch release — no API changes for valid configurations.

What's Changed

Security fixes

  • GHSA-c4cf-2hgv-2qv6 — Bridge set trap ignoring ECMA-262 §9.5.9 Receiver, letting Object.create(hostObj) children and Reflect.set(hostObj, k, v, custom) writes leak onto the host object (write-channel → RCE).
  • GHSA-m5q2-4fm3-vfqp — Cross-realm Symbol.for namespace leak + missing dangerous-symbol guards on the bridge's write traps (set / defineProperty / deleteProperty), enabling sandbox-installed nodejs.util.promisify.custom / stream brand / webstream hooks on host objects (RCE).
  • GHSA-v6mx-mf47-r5wg — Host prototype mutation via Function.prototype.{call,apply,bind} and Reflect.{apply,construct} indirection through Object.prototype.__proto__ setter, severing host intrinsic prototype chains and escaping via thisEnsureThis proto-walk fallthrough (RCE).
  • GHSA-q3fm-4wcw-g57x — Defense Invariant #11 violation in defaultSandboxPrepareStackTrace (second variant of GHSA-9qj6-qjgg-37qq in a different file): sandbox-installed Array.prototype[N] setter / Array.prototype.join override could observe bridge-internal stack-trace state.
  • GHSA-76w7-j9cq-rx2j — Promise species hijack in localPromise's swallow-tail, hijacking the downstream child constructor to capture V8's internal (resolve, reject) capability and reach a raw host-realm error → host Function (RCE).
  • GHSA-m4wx-m65x-ghrr — NodeVM constructor patch bypass of GHSA-8hg8-63c5-gwmx: any truthy nesting paired with a non-real-config require produced a NESTING_OVERRIDE-only resolver → inner NodeVM with attacker-chosen requirechild_process RCE.
  • GHSA-6j2x-vhqr-qr7q — WebAssembly JSPI (WebAssembly.promising / WebAssembly.Suspending, Node 24+ behind a flag, Node 26+ default) producing Promise objects with a host-realm [[Prototype]] chain and no bridge interposition; species hijack delivers a raw host-realm rejection to sandbox .catch → host Function (RCE).
  • GHSA-rp36-8xq3-r6c4 — NodeVM builtin denylist bypass via process (whose getBuiltinModule(name) reloads any core module regardless of allow/deny config) and inspector/promises (whose Session().post('Runtime.evaluate', ...) evaluates attacker JS in the host realm). Supersedes GHSA-947f-4v7f-x2v8.
  • GHSA-r9pm-gxmw-wv6p — NodeVM builtin: ['*'] wildcard exposing Node's undocumented underscored network builtins (_http_client, _http_server, _tls_*, _stream_*) even when the documented -http/-https/-net/-tls exclusions were used — SSRF-class capability bypass (CVSS 8.6).
  • GHSA-9g8x-92q2-p28f — NodeVM builtin allowlist surfacing four process-wide observability builtins (diagnostics_channel, async_hooks, perf_hooks, v8) that read state of the entire host process rather than sandbox-local state — HTTP header / async-context / perf-mark / heap-snapshot exfiltration.

Documentation

  • docs/ATTACKS.md extended through Category 35, plus two new Defense Invariants: #12 ("No sandbox-visible object has a host-realm prototype chain without bridge interposition") and #13 ("The NodeVM builtin allowlist is a closed system").

Upgrade Notes

  • If you constructed NodeVM({ nesting: <truthy> }) without an explicit require config object, new NodeVM(...) now throws (GHSA-m4wx-m65x-ghrr). This covers every shape that previously silently produced a vm2-only resolver: omitting require, or setting it to any falsy value (false/undefined/null/0/'') or any truthy non-object value (true/number/string/symbol/function); and also any truthy nesting value, not only nesting: true. Either drop nesting, or pass an explicit require config object (e.g. require: { builtin: [] }) to acknowledge that vm2 will be requireable from inside the sandbox. The error message is actionable and links to the README hardening section.
  • No other valid configurations are affected. Embedders who explicitly listed any of process / inspector / worker_threads / cluster / vm / repl / module / trace_events / wasi / diagnostics_channel / async_hooks / perf_hooks / v8 in builtin were already running an unsandboxed sandbox; those names now throw at load time and can be re-introduced as safe wrappers via mock / override / SPECIAL_MODULES.

Full Changelog: v3.11.3...v3.11.4

v3.11.3

11 May 02:17

Choose a tag to compare

What's Changed

Security fix

Documentation

Full Changelog: v3.11.2...v3.11.3

v3.11.2

03 May 21:33

Choose a tag to compare

What's Changed

Security fixes

  • GHSA-9vg3-4rfj-wgcm — Sandbox-realm null-proto write-through via bridge.from() set trap (RCE)
  • GHSA-2cm2-m3w5-gp2f — Internal state reachable via computed-key access on globalThis
  • GHSA-9qj6-qjgg-37qq — Bridge saved-state leak via sandbox-installed Array.prototype[N] setter (RCE)

Documentation

  • docs/ATTACKS.md updated through Category 28, plus a new Defense Invariant
    ("Bridge-internal containers must not invoke sandbox code").

Full Changelog: v3.11.1...v3.11.2

v3.11.1

01 May 21:21

Choose a tag to compare

Single advisory closed plus prominent documentation of an existing escape hatch. Patch release — no API changes for valid configurations.

Embedders running untrusted code with nesting: true should read the new README section.

What's Changed

Security fix

  • GHSA-8hg8-63c5-gwmxnesting: true bypassed require: false, allowing sandbox-to-host RCE via inner NodeVM construction. The contradictory option pair { nesting: true, require: false } now throws VMError at new NodeVM(...) time citing the advisory. Same shape as the GHSA-cp6g eager FileSystem-contract probe — surface contradictory configuration at the API surface, not silently produce an unsandboxed sandbox.

Documentation

  • New README section "nesting: true is an escape hatch" under Hardening recommendations. Spells out the inner-VM independence: a nested VM's require config is chosen by the sandbox code that constructs it, not constrained by the outer VM. Do not enable nesting: true for untrusted code.
  • JSDoc on the nesting option (lib/nodevm.js) upgraded to match.
  • docs/ATTACKS.md gains Category 25 documenting the configuration trap, plus a matching row in the "How The Bridge Defends" table.

Upgrade Notes

  • If you set { nesting: true, require: false } anywhere in your codebase, new NodeVM(...) now throws. Either drop nesting: true (if you wanted deny-all), or replace require: false with an explicit require config (e.g. require: { builtin: [] }) to acknowledge that vm2 will be requireable. The error message is actionable and links to the README section.
  • No other configurations are affected. Bare new NodeVM({ nesting: true }) continues to work as documented; this is the documented escape hatch and is not closed by this patch (out of scope — would change nesting: true semantics substantially).

What This Fix Does NOT Close

nesting: true itself remains an escape hatch for any non-trivial require config. The fix closes the specific contradictory pair flagged by the advisory; the broader recommendation is in the new README section: do not enable nesting: true when running untrusted code. Constraint propagation from outer to inner NodeVM (where the outer's require config would constrain inner construction) was considered and deferred — it would change the documented semantics of nesting: true and is a major-version-shaped change.

Full Changelog: v3.11.0...v3.11.1

v3.11.0

01 May 20:37

Choose a tag to compare

Coordinated security release closing 13 advisories, plus a new bufferAllocLimit option and a realpath() method on the FileSystem adapter contract. Minor version bump because of the new public option and the FileSystem contract addition; no incompatible changes to the existing public API surface.

Embedders running untrusted code should upgrade. Several of the advisories close full sandbox-escape RCE primitives.

What's Changed

Security fixes

Plus two related hardenings discovered during pre-release red-team:

  • trace_events added to the dangerous-builtins denylist — createTracing({categories: [Proxy<Array>]}) triggered a C++ assertion that aborted the host process.
  • wasi added to the denylist — experimental syscall surface (filesystem preopens, host clock/random, network) too broad for default '*' exposure.

New options

  • bufferAllocLimit (VM, NodeVM) — non-negative number or Infinity. Caps individual Buffer.alloc family requests from inside the sandbox. Default: Infinity (fully backwards-compatible). Embedders running untrusted code should opt into a finite cap as part of layered DoS defense.

FileSystem contract

  • DefaultFileSystem.realpath() and VMFileSystem.realpath() added. require.root boundary checks now canonicalise candidate paths via realpath before the prefix check, closing CWE-59 (symlinks inside the allowed root pointing outside it). Especially relevant for pnpm / npm-workspaces / npm link layouts where every node_modules entry is a symlink by design.

Upgrade Notes

  • Custom fs adapters with require.root must implement realpathSync (or realpath() on a fully custom FileSystem class). Without it, new NodeVM({require: {root, fs: customAdapter}}) now throws a VMError at construction citing GHSA-cp6g-6699-wx9c. Default fs users are unaffected.
  • Embedders running untrusted async code should install a host-side unhandledRejection handler. The GHSA-hw58 fix closes synchronous executor throws but cannot reach async-function / async-generator / await using rejection paths (V8 creates rejection promises via the realm's intrinsic Promise). See Hardening recommendations.
  • Embedders in memory-constrained environments should opt into a finite bufferAllocLimit (e.g. 32 * 1024 * 1024).

Documentation

Full Changelog: v3.10.5...v3.11.0

v3.10.5

17 Feb 01:14

Choose a tag to compare

What's Changed

  • fix: allow Object.setPrototypeOf on sandbox-local objects
  • fix: block Function constructor access via getOwnPropertyDescriptor
  • fix: block Function constructor from crossing bridge regardless of access path
  • fix: block all code-executing constructors from crossing bridge via property descriptors
  • fix: block Function constructor extraction via nested property descriptors
  • fix: prevent proxy unwrapping to block Function constructor extraction via Object.entries
  • fix: prevent sandbox escape via doPreventExtensions exposure in util.inspect
  • fix: prevent sandbox escape via getFactory exposure in util.inspect
  • fix: sanitize SuppressedError sub-errors to prevent sandbox escape
  • fix: block host Function constructor leak via direct handler.get() call
  • fix: block WebAssembly.JSTag to prevent wasm-level exception catch sandbox escape in Node 25

Full Changelog: v3.10.4...v3.10.5

v3.10.4

04 Feb 22:48

Choose a tag to compare

What's Changed

  • fix: prevent sandbox escape via Promise static method stealing
  • fix: prevent sandbox escape via Reflect.construct Promise species bypass
  • fix: prevent sandbox escape via proxy handler exposure in util.inspect
  • fix: prevent sandbox escape via fromOtherWithContext exposure in util.inspect

Full Changelog: v3.10.3...v3.10.4

v3.10.3

25 Jan 23:08

Choose a tag to compare

What's Changed

  • fix: prevent sandbox escape via Symbol.for cross-realm symbols
  • fix: prevent Symbol.for bypass via hasOwnProperty override
  • fix: prevent Symbol.for bypass via object key coercion
  • fix: prevent cross-realm symbol extraction via Object.getOwnPropertySymbols
  • fix: prevent cross-realm symbol extraction via spread operator on bridge proxies
  • fix: prevent sandbox escape via Promise species manipulation
  • fix: prevent Symbol.species getter TOCTOU bypass in Promise species reset
  • fix: eliminate Promise species TOCTOU by unconditional constructor override
  • fix: prevent sandbox escape via Promise.try static method stealing

Full Changelog: v3.10.2...v3.10.3

v3.10.2

17 Jan 14:51
4b009c2

Choose a tag to compare

What's Changed

  • fix: use Reflect.apply instead of .call() in Promise handlers by @patriksimek in #549

Full Changelog: v3.10.1...v3.10.2