Skip to content

Bump vm2 from 3.11.5 to 3.12.2 in /whtree - #13

Open
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/npm_and_yarn/whtree/vm2-3.12.2
Open

dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/npm_and_yarn/whtree/vm2-3.12.2

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Sep 25, 2026

Copy link
Copy Markdown

Bumps vm2 from 3.11.5 to 3.12.2.

Release notes

Sourced from vm2's releases.

v3.12.2

Three advisories closed, and vm2 can now be shipped by single-file bundlers. Patch release — no API changes, with observable behaviour changes for host buffers and host promises handed to the sandbox (see Upgrade Notes).

What's Changed

Security fixes

  • GHSA-5h3f-q97h-ccvc — a NodeVM with a custom require.resolve recorded each resolver answer as a raw string prefix, so resolving an allowlisted package authorized every prefix-sharing sibling beside it (.../node_modules/foo authorized .../node_modules/foo2/index.js), and the {module, path} return shape authorized the whole search directory; under the default context: 'host' the sibling's top-level code ran with host authority. Resolver answers are now recorded as boundary-matched base paths (plus exact extension spellings for extension-probed answers), the object shape authorizes only the resolved package's directory, and an authorization is withdrawn again when its load finds nothing.
  • GHSA-2v2p-6j97-cjg9 — a host promise reaching the sandbox through a constructor return (new HostFn()), a host getter or data property, or a callback argument carried no rejection reaction, so sandbox code that simply dropped it terminated the host process under Node's unhandledRejection policy; the GHSA-gjq8 fix had covered only apply returns. Every host promise is now marked handled once at the single host→sandbox delivery chokepoint (a cheap prototype brand check that never invokes .then on non-promises and recognises promises from a second host realm), and the construct trap carries the same unconditional mark as apply. The sandbox still observes rejections through its own sanitized .catch.
  • GHSA-489w-w794-jq94 — a host-allocated Buffer (a builtin's return value such as zlib.deflateSync, an embedder-supplied buffer, a callback argument) exposed Node's shared 64 KiB allocation pool through .buffer or its legacy twin .parent, letting the sandbox read and overwrite unrelated host buffers — host memory disclosure and corruption. The GHSA-fcqc backing-store ownership rule now applies at the bridge for every host view, keyed on the identity of the delivered value so every alias of the store is covered by one rule; the raw buffer / parent / offset getters are no longer deliverable, the gate fails closed if the bridge cannot resolve ArrayBuffer.isView, and a foreign store planted on a view is refused.

Fixed

  • Single-file bundlers (Bun compile, esbuild, pkg, ...) can now ship vm2. The sandbox bootstrap files had to be read from disk at runtime, so a compiled binary failed with ENOENT as soon as the package directory was not on disk. They are now embedded as string literals in the generated lib/sources.js, and the sandbox-compiled scripts use a fixed virtual filename so bootstrap frames stay redacted from sandbox-visible stack traces.

Maintenance

  • Dev dependency @humanfs/node bumped from 0.16.6 to 0.16.8.

Documentation

  • Categories 46 (custom-resolver sibling authorization, with two documented residuals), 22 (host promises on every delivery route) and 41 (host views and the shared pool, with the observable behaviour changes and two documented residuals) are extended in docs/attacks/, with matching rows in the "How The Bridge Defends" table.

Upgrade Notes

  • Host buffers that do not own their whole backing store are delivered bounded. For such a view, .buffer / .parent is an exact-size copy (not identity-stable, not write-through), and byteOffset / offset read as 0 so Buffer.from(v.buffer, v.byteOffset, v.length) keeps working. Sub-views the sandbox creates from a host-backed buffer lose .buffer aliasing with their parent (index writes still alias), and SharedArrayBuffer sub-views are delivered as copies — hand over a view spanning the whole store for live sharing. Buffers that own their store (Buffer.alloc(n), large buffers) are unchanged.
  • Ignored host promises are silent on every route. Embedders no longer see unhandledRejection for a host promise handed to the sandbox through a getter, callback argument or constructor return, exactly as for call returns since GHSA-gjq8; attach an explicit .catch() to debug rejections.
  • A custom resolver's {module, path} answer must name a package inside path. A module that is absolute, relative or contains .. is now refused and reports module-not-found; return the string shape to name a path directly.
  • Bundling: nothing to configure — lib/sources.js is part of the published package. If you patch a bootstrap file in a fork, run npm run build:sources (or npm test, which regenerates it) so the embedded copy does not go stale.
  • No other valid configurations are affected.

Full Changelog: patriksimek/vm2@v3.12.1...v3.12.2

v3.12.1

Four advisories closed. Patch release — no API changes, with one deliberate behaviour change for NodeVM embedders who granted child_process (see Upgrade Notes).

What's Changed

Security fixes

  • GHSA-6454-5x88-m6jw — an embedder-exposed host Promise could be turned into a sandbox escape. Sandbox code wrote constructor[Symbol.species] onto the raw host promise and then called .then / .catch / .finally with the settlement-direction handler omitted; V8 built the result capability through SpeciesConstructor off the raw object (no bridge trap on that read) and its internal Thrower/Identity reaction delivered the raw host settlement — e.g. process — to a sandbox-captured capability, with no callback slot for the rejection sanitizer to wrap. Host RCE. The bridge now shadows the host promise's constructor for the duration of every sandbox-initiated then / catch / finally call, so the result capability is always a genuine host %Promise%; the indirection peel also covers host Reflect.apply and .finally.
  • GHSA-j89j-5m6r-cr2q — any ordinary sloppy-mode host function exposed to the sandbox leaked the host realm's global object when called with a nullish receiver (greet(), .call(null), Reflect.apply(fn, undefined, []), bind(null)()): V8's OrdinaryCallBindThis substituted the host global for this, and the bridge wrapped and delivered it — greet().process.getBuiltinModule('child_process') was host RCE. The host global is now cached at bridge init and refused at every host→sandbox coercion chokepoint, delivered as undefined, which is exactly what a strict-mode function's this already looked like.
  • GHSA-x3v6-43hc-82mc — a NodeVM that allowlisted the crypto builtin let guest code call crypto.setFips, flipping the FIPS mode of the entire host process; the read-only wrap stops property writes but forwards host calls with full authority. Same process-wide-mutator class as crypto.setEngine (GHSA-46pr) and tls.setDefaultCACertificates (GHSA-98xx). setFips is now replaced with a throwing stub alongside setEngine; getFips() and the rest of crypto are unchanged. Configuration-integrity issue, not RCE.
  • GHSA-pq68-rvw4-xp4r — NodeVM's hard denylist blocked every other host-process launcher (cluster, worker_threads, node:test) but omitted child_process, so require: { builtin: ['*'] }, ['*', '-fs'], an explicit ['child_process'], and the node: spellings all handed the sandbox the real host module — one execSync call was host RCE. child_process now joins DANGEROUS_BUILTINS: filtered from the wildcard, refused on explicit request, and covered by the node: / family normalization.

Documentation

  • The attack catalog is now split by mechanism family: docs/ATTACKS.md is the index and common material, and each family lives under docs/attacks/. Category numbers are permanent and unchanged. test/docs-catalog.js fails the suite on a renumbering, a dead link, or a missing metadata line.
  • New Categories 53 (host-Promise @@species hijack with a missing handler) and 54 (host global leak via a sloppy function's nullish this), plus extensions to Categories 21 (child_process joins the denylist, with the upgrade path) and 40 (crypto.setFips), and matching rows in the "How The Bridge Defends" table.

... (truncated)

Changelog

Sourced from vm2's changelog.

[3.12.2]

Security fixes

  • GHSA-5h3f-q97h-ccvc — NodeVM custom-resolver authorization admitted prefix-sharing siblings: resolving an allowlisted package recorded a raw ^<path> prefix, so .../node_modules/foo authorized .../node_modules/foo2/index.js (and the {module, path} shape authorized the whole search directory), running the sibling's top-level code in the host realm under context: 'host'. Structural fix in lib/resolver-compat.js: resolver answers are recorded as boundary-matched base paths (plus exact extension spellings), the object shape authorizes only the resolved package directory, and a failed load withdraws its authorization. Behavior change: a {module, path} answer whose module is absolute, relative or contains .. is now refused. See ATTACKS.md Category 46 and test/ghsa/GHSA-5h3f-q97h-ccvc/.
  • GHSA-2v2p-6j97-cjg9 — a host promise delivered into the sandbox through a constructor return, a host getter or data property, or a callback argument carried no rejection reaction, so sandbox code that dropped it terminated the host process under Node's unhandledRejection policy (GHSA-gjq8-xm47-88rc covered only apply returns). Structural fix in lib/bridge.js: every host promise is marked handled once at the delivery chokepoint (prototype brand check, cross-realm aware), and the construct trap gains the same unconditional mark as apply. Behavior change: embedders no longer see unhandledRejection for host promises handed to the sandbox on any route; debug rejections need an explicit .catch(). See ATTACKS.md Category 22 and test/ghsa/GHSA-2v2p-6j97-cjg9/.
  • GHSA-489w-w794-jq94 — host memory disclosure and corruption: a host-allocated Buffer (a builtin's return value such as zlib.deflateSync, an embedder-supplied buffer, a callback argument) exposed Node's shared 64 KiB pool through .buffer / .parent, letting the sandbox read and overwrite unrelated host buffers. Structural fix in lib/bridge.js: the GHSA-fcqc backing-store ownership rule now applies at the bridge for every host view, keyed on the delivered value's identity (so every alias of the store is covered), with the raw buffer / parent / offset getters undeliverable and a fail-closed gate. Behavior change: for a host view that does not own its whole store, .buffer / .parent is a bounded copy (not identity-stable, not write-through), byteOffset / offset read as 0, sandbox-created sub-views lose .buffer aliasing with their parent, and SharedArrayBuffer sub-views are delivered as copies; owning buffers are unchanged. See ATTACKS.md Category 41 and test/ghsa/GHSA-489w-w794-jq94/.

Fixed

  • Single-file bundlers (Bun compile, esbuild, pkg, ...) can now ship vm2. The sandbox bootstrap files (bridge.js, setup-sandbox.js, setup-node-sandbox.js, events.js) must reach the sandbox realm as source text and were read from disk at runtime with fs.readFileSync(\${__dirname}/...`), which a bundler cannot follow — a compiled binary failed with ENOENTas soon as the package directory was not on disk. They are now embedded as string literals in the generatedlib/sources.js (npm run build:sources, regenerated by pretest/prepublishOnlyand guarded by a staleness test). The sandbox-compiled scripts use the fixed virtual filename/vm2/lib/` instead of the host install path, so bootstrap frames stay redacted from sandbox-visible stack traces.

Maintenance

  • Dev dependency @humanfs/node bumped from 0.16.6 to 0.16.8.

[3.12.1]

Security fixes

  • GHSA-6454-5x88-m6jw — sandbox-to-host RCE through an embedder-exposed host Promise. Writing constructor[Symbol.species] on the raw host promise and calling .then / .catch / .finally with the settlement-direction handler omitted made V8 deliver the raw host settlement (e.g. process) to a sandbox-captured capability, with no callback slot for the rejection sanitizer to wrap. Structural fix in lib/bridge.js: neutralizeHostPromiseSpeciesOn shadows the host promise's constructor across the call so the result capability is always a genuine host %Promise%, and the indirection peel now also covers host Reflect.apply and .finally. See ATTACKS.md Category 53 and test/ghsa/GHSA-6454-5x88-m6jw/.
  • GHSA-j89j-5m6r-cr2q — sandbox escape to host RCE through any embedder-exposed sloppy-mode host function. Calling it with a nullish receiver (greet(), .call(null), Reflect.apply(fn, undefined, []), bind(null)()) makes V8 bind this to the host realm's global object, which the bridge then wrapped and delivered to the sandbox (greet().process.getBuiltinModule('child_process')). Structural fix in lib/bridge.js: the host global is cached at bridge init and refused at the three host→sandbox coercion chokepoints, returning undefined so strict-function semantics are preserved. See ATTACKS.md Category 54 and test/ghsa/GHSA-j89j-5m6r-cr2q/.
  • GHSA-x3v6-43hc-82mc — a NodeVM that allowlists the crypto builtin let guest code call crypto.setFips, flipping the FIPS mode of the entire host process; the read-only wrap stops property writes but forwards host calls with full authority, the same process-wide-mutator class as crypto.setEngine and tls.setDefaultCACertificates. Fix in lib/builtin.js: sanitizeCryptoModule replaces setFips with a throwing stub alongside setEngine; getFips() and the rest of crypto are untouched. Configuration-integrity issue, not RCE. See ATTACKS.md Category 40 and test/ghsa/GHSA-x3v6-43hc-82mc/.
  • GHSA-pq68-rvw4-xp4r — NodeVM's hard denylist omitted child_process, so require: { builtin: ['*'] }, ['*', '-fs'], an explicit ['child_process'], and the node: spellings all handed the sandbox the real host module and one execSync call was host RCE. Fix in lib/builtin.js: child_process joins DANGEROUS_BUILTINS, denied under the wildcard and on explicit request like cluster / worker_threads / node:test. Behavior change: embedders running trusted scripts that need it re-expose it through require.mock (the real module or a narrower facade); a bare builtin: ['child_process'] no longer grants it. See ATTACKS.md Category 21 and test/ghsa/GHSA-pq68-rvw4-xp4r/.

[3.12.0]

Added

  • Experimental Bun support (test suite and CI only). The suite now runs under Bun, with engine-keyed assertion messages so patterns stay exactly as strict on Node, a central test/bun-skips.js listing every JavaScriptCore divergence, and a non-blocking CI job whose output is verified complete before it is believed. Bun is not a supported security boundary — vm2's threat model is derived from V8 internals and JavaScriptCore has not been audited against the bridge. See the README Runtimes section.

Maintenance

  • global.Proxy install guarded for JavaScriptCore, and the sealed-slot attributes left implicit — lib/setup-sandbox.js installs the handler-sanitising Proxy with a bare assignment to a slot the Object.defineProperties(global, ...) block above declares as undefined. What that write does is engine-specific: on Node >= 10 the slot is genuinely sealed and the write is a silent no-op, so the sandbox has no Proxy at all; on Node 8 the old V8 global proxy leaves the slot writable and the write is live, which is what gives that sandbox its Proxy; and JavaScriptCore (Bun) implements the strict-mode write correctly and throws, aborting sandbox setup before the first run(). The write is now wrapped in try/catch — the failure outcome is "no Proxy in the sandbox", which is strictly more restrictive, never less. test/vm.js gains an AST-based guard (via acorn, already a runtime dependency) that fails if any write to Error, Promise or Proxy is left outside a try block, including through a local alias, and pins the sealed-slot descriptors on Node >= 10 where the seal actually takes. Correction to 3.11.8's entry: that release described the assignment as dead code and removed it, and separately spelled out writable: false, configurable: false on the three sealed slots for readability, claiming no behaviour change. The second change was the damaging one — those attributes are the spec defaults, so the forms are equivalent on a current V8 but not on the Node 8 global proxy, where only the explicit form actually seals the slot. That silently removed Proxy from Node 8 sandboxes and broke six tests. The attributes are omitted again, with a comment saying why they must stay that way, and the assignment is restored.

[3.11.8]

Security fixes

  • GHSA-3vgf-8m4q-q4qr (dup: GHSA-59g5-pmg6-5gr4) — default VM host intrinsic prototype pollution of the binary-data and iterator families. The protected inventory omitted ArrayBuffer / SharedArrayBuffer / DataView / every TypedArray / the abstract %TypedArray%.prototype, and the array/string/map/set/regexp-string iterator prototypes plus the shared %IteratorPrototype%. Because Buffer extends Uint8Array, the Category 20 proto-walk from a host Buffer reached those unprotected host prototypes and Reflect.defineProperty polluted them globally, corrupting every host-realm typed array and iterator. lib/bridge.js now lists the binary-data globals in globalsList and resolves the abstract intrinsic prototypes structurally into thisGlobalPrototypes, routing all of them into protectedHostObjects, the proto-mapping table, and the identity map so the write traps refuse sandbox set/defineProperty. See ATTACKS.md Category 20 (extended) and test/ghsa/GHSA-3vgf-8m4q-q4qr/.
  • GHSA-88hf-g992-jg85 — NodeVM default-config (console: 'inherit') sandbox escape. The sandbox extracted the raw host Object.prototype.__proto__ getter (via Buffer.call.call(__lookupGetter__, …, '__proto__'), the GHSA-v6mx/cfcw primitive) and, because that getter was never classified dangerous like the setter, climbed console._stdout's host prototype chain to the non-intrinsic EventEmitter.prototype, overwrote emit, and had the host invoke it with this === process → RCE. Closed with two independent layers in lib/bridge.js: (1) the raw host proto-readers (__proto__ getter, Object.getPrototypeOf, Reflect.getPrototypeOf) are denied delivery at thisFromOtherWithFactory/thisEnsureThis/thisFromOtherForThrow and the apply trap, so the sandbox can no longer climb host chains; (2) host [[Prototype]] objects are marked at delivery and sandbox function/accessor writes to them are diverted off the raw host object in BaseHandler.set/defineProperty. Legitimate Object.getPrototypeOf on host proxies and data/leaf writes are unchanged. See ATTACKS.md Category 50 and test/ghsa/GHSA-88hf-g992-jg85/.
  • GHSA-f8gf-w286-fmq2 — allowAsync: false async-execution boundary bypassed via Promise thenable assimilation. Blocking Promise.prototype.then left every native resolve capability open: Promise.resolve/all/race/any/allSettled/try, new Promise(r => r(thenable)), withResolvers().resolve, Array.fromAsync, and the realm-intrinsic base reached via Object.getPrototypeOf(Promise) all let V8's PromiseResolveThenableJob run an attacker .then in a microtask after run() returned, outside the configured timeout. Structural fix in lib/setup-sandbox.js, gated entirely to allowAsync: false: a TOCTOU-safe resolve-capability guard (refuses object/function values without ever reading .then), synchronous throws on the assimilating static methods, a non-configurable throwing Array.fromAsync stub, and a construct-guard Proxy on localPromise's prototype that makes the native base un-constructable from the sandbox. allowAsync: true is untouched. See ATTACKS.md Category 51 and test/ghsa/GHSA-f8gf-w286-fmq2/.
  • GHSA-gjq8-xm47-88rc — an embedder-exposed host function (or a host builtin such as events.once) that returns a rejected host Promise crashed the entire host process when sandbox code called it and ignored the result. The bridge handed the sandbox a wrapped promise but left the underlying host promise without a rejection reaction of its own, so Node's default unhandledRejection policy (Node 15+) tore the process down — a sandbox-triggered host DoS from a single line of untrusted code. Sibling of the parent advisory GHSA-hw58-p9xv-2mjh, which hardened the opposite (sandbox→host) direction. lib/bridge.js now attaches a benign no-op reaction to the underlying host promise on the host side, at the apply-trap boundary (markHostPromiseHandled). Promises multicast, so the sandbox's own GHSA-55hx-sanitized .then/.catch still fires and still observes the sanitized rejection; the no-op onRejected returns undefined, so it never creates a new unhandled rejection; fulfilled promises are untouched. See ATTACKS.md Category 22 (extended) and test/ghsa/GHSA-gjq8-xm47-88rc/.
  • GHSA-r273-hxvj-fxhp — NodeVM exposed host util to the sandbox as an unfiltered Object.assign({}, util), so util.getCallSites() (Node >= 22.9) handed sandboxed code the host process call stack — absolute paths including vm2's own lib/ and the embedder entrypoint — bypassing the GHSA-v27g host-frame redaction, which only covers sandbox-realm Error stacks. getCallSite / setTraceSigInt / private internals rode the same wholesale copy, and the sys alias leaked identically via the generic loader. lib/builtin.js now builds the exposed util from a vetted, forward-safe allowlist (SAFE_UTIL_MEMBERS, presence-gated Node 8→26) routed through the BUILTIN_MEMBER_SANITIZERS chokepoint for both util and sys, so no unreviewed host member reaches the sandbox. Information disclosure only. See ATTACKS.md Category 52 and test/ghsa/GHSA-r273-hxvj-fxhp/.

... (truncated)

Commits

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    You can disable automated security fix PRs for this repo from the Security Alerts page.

Bumps [vm2](https://github.com/patriksimek/vm2) from 3.11.5 to 3.12.2.
- [Release notes](https://github.com/patriksimek/vm2/releases)
- [Changelog](https://github.com/patriksimek/vm2/blob/main/CHANGELOG.md)
- [Commits](patriksimek/vm2@v3.11.5...v3.12.2)

---
updated-dependencies:
- dependency-name: vm2
  dependency-version: 3.12.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot
dependabot Bot requested a review from a team September 25, 2026 08:26
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code labels Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants