🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

vm2

Package Overview
Dependencies
Maintainers
1
Versions
77
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

vm2 - npm Package Compare versions

Comparing version
3.10.3
to
3.10.4
+54
-40
lib/bridge.js

@@ -91,6 +91,5 @@ 'use strict';

const ThisWeakMap = WeakMap;
const {
get: thisWeakMapGet,
set: thisWeakMapSet
} = ThisWeakMap.prototype;
const thisWeakMapProto = ThisWeakMap.prototype;
const thisWeakMapGet = thisWeakMapProto.get;
const thisWeakMapSet = thisWeakMapProto.set;
const ThisMap = Map;

@@ -236,3 +235,21 @@ const thisMapGet = ThisMap.prototype.get;

const protoName = new ThisMap();
// Store wrapped objects in a WeakMap keyed by handler instance.
// This prevents exposure of raw objects via util.inspect with showProxy:true,
// which can leak the handler's internal state.
const handlerToObject = new ThisWeakMap();
// Closure-scoped function to retrieve the wrapped object from a handler.
// This is NOT a method on BaseHandler, so it cannot be called by attackers
// even if they obtain a reference to the handler via showProxy.
function getHandlerObject(handler) {
return thisReflectApply(thisWeakMapGet, handlerToObject, [handler]);
}
// Closure-scoped function to convert other-realm objects to this-realm with factory.
// This is NOT a method on BaseHandler, so it cannot be called by attackers
// even if they obtain a reference to the handler via showProxy.
function handlerFromOtherWithContext(handler, other) {
return thisFromOtherWithFactory(handler.getFactory(), other);
}
function thisAddProtoMapping(proto, other, name) {

@@ -345,3 +362,3 @@ // Note: proto@this(unsafe) other@other(unsafe) name@this(unsafe) throws@this(unsafe)

}
return handler.fromOtherWithContext(ret);
return handlerFromOtherWithContext(handler, ret);
}

@@ -365,9 +382,11 @@

super();
this.objectWrapper = () => object;
// Store the object in a WeakMap instead of as an instance property.
// This prevents leaking the raw object via util.inspect with showProxy:true,
// which exposes proxy handlers and their properties.
// NOTE: There is intentionally NO getObject() method on this class.
// The object is retrieved via the closure-scoped getHandlerObject() function,
// which is not accessible to attackers even if they obtain a handler reference.
thisReflectApply(thisWeakMapSet, handlerToObject, [this, object]);
}
getObject() {
return this.objectWrapper();
}
getFactory() {

@@ -377,7 +396,2 @@ return defaultFactory;

fromOtherWithContext(other) {
// Note: other@other(unsafe) throws@this(unsafe)
return thisFromOtherWithFactory(this.getFactory(), other);
}
doPreventExtensions(target, object, factory) {

@@ -406,8 +420,8 @@ // Note: target@this(unsafe) object@other(unsafe) throws@this(unsafe)

if (desc.get || desc.set) {
desc.get = this.fromOtherWithContext(desc.get);
desc.set = this.fromOtherWithContext(desc.set);
desc.get = handlerFromOtherWithContext(this,desc.get);
desc.set = handlerFromOtherWithContext(this,desc.set);
} else if (typeof object === 'function' && (key === 'caller' || key === 'callee' || key === 'arguments')) {
desc.value = null;
} else {
desc.value = this.fromOtherWithContext(desc.value);
desc.value = handlerFromOtherWithContext(this,desc.value);
}

@@ -435,3 +449,3 @@ } else {

// Note: target@this(unsafe) key@prim receiver@this(unsafe) throws@this(unsafe)
const object = this.getObject(); // @other(unsafe)
const object = getHandlerObject(this); // @other(unsafe)
switch (key) {

@@ -473,3 +487,3 @@ case 'constructor': {

}
return this.fromOtherWithContext(ret);
return handlerFromOtherWithContext(this,ret);
}

@@ -479,3 +493,3 @@

// Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe)
const object = this.getObject(); // @other(unsafe)
const object = getHandlerObject(this); // @other(unsafe)
if (key === '__proto__' && !thisOtherHasOwnProperty(object, key)) {

@@ -504,3 +518,3 @@ return this.setPrototypeOf(target, value);

// Note: target@this(unsafe) context@this(unsafe) args@this(safe-array) throws@this(unsafe)
const object = this.getObject(); // @other(unsafe)
const object = getHandlerObject(this); // @other(unsafe)
let ret; // @other(unsafe)

@@ -519,3 +533,3 @@ try {

// Note: target@this(unsafe) args@this(safe-array) newTarget@this(unsafe) throws@this(unsafe)
const object = this.getObject(); // @other(unsafe)
const object = getHandlerObject(this); // @other(unsafe)
let ret; // @other(unsafe)

@@ -533,3 +547,3 @@ try {

// Note: target@this(unsafe) prop@prim desc@other{safe} throws@this(unsafe)
const object = this.getObject(); // @other(unsafe)
const object = getHandlerObject(this); // @other(unsafe)
if (desc && typeof object === 'function' && (prop === 'arguments' || prop === 'caller' || prop === 'callee')) desc.value = null;

@@ -543,3 +557,3 @@ return desc;

if (isDangerousCrossRealmSymbol(prop)) return undefined;
const object = this.getObject(); // @other(unsafe)
const object = getHandlerObject(this); // @other(unsafe)
let desc; // @other(safe)

@@ -560,4 +574,4 @@ try {

__proto__: null,
get: this.fromOtherWithContext(desc.get),
set: this.fromOtherWithContext(desc.set),
get: handlerFromOtherWithContext(this,desc.get),
set: handlerFromOtherWithContext(this,desc.set),
enumerable: desc.enumerable === true,

@@ -569,3 +583,3 @@ configurable: desc.configurable === true

__proto__: null,
value: this.fromOtherWithContext(desc.value),
value: handlerFromOtherWithContext(this,desc.value),
writable: desc.writable === true,

@@ -592,3 +606,3 @@ enumerable: desc.enumerable === true,

// Note: target@this(unsafe) prop@prim desc@this(unsafe) throws@this(unsafe)
const object = this.getObject(); // @other(unsafe)
const object = getHandlerObject(this); // @other(unsafe)
if (!thisReflectSetPrototypeOf(desc, null)) throw thisUnexpected();

@@ -625,4 +639,4 @@

__proto__: null,
get: this.fromOtherWithContext(otherDesc.get),
set: this.fromOtherWithContext(otherDesc.set),
get: handlerFromOtherWithContext(this,otherDesc.get),
set: handlerFromOtherWithContext(this,otherDesc.set),
enumerable: otherDesc.enumerable,

@@ -634,3 +648,3 @@ configurable: otherDesc.configurable

__proto__: null,
value: this.fromOtherWithContext(otherDesc.value),
value: handlerFromOtherWithContext(this,otherDesc.value),
writable: otherDesc.writable,

@@ -648,3 +662,3 @@ enumerable: otherDesc.enumerable,

// Note: target@this(unsafe) prop@prim throws@this(unsafe)
const object = this.getObject(); // @other(unsafe)
const object = getHandlerObject(this); // @other(unsafe)
try {

@@ -661,3 +675,3 @@ return otherReflectDeleteProperty(object, prop) === true;

if (isDangerousCrossRealmSymbol(key)) return false;
const object = this.getObject(); // @other(unsafe)
const object = getHandlerObject(this); // @other(unsafe)
try {

@@ -672,3 +686,3 @@ return otherReflectHas(object, key) === true;

// Note: target@this(unsafe) throws@this(unsafe)
const object = this.getObject(); // @other(unsafe)
const object = getHandlerObject(this); // @other(unsafe)
try {

@@ -687,3 +701,3 @@ if (otherReflectIsExtensible(object)) return true;

// Note: target@this(unsafe) throws@this(unsafe)
const object = this.getObject(); // @other(unsafe)
const object = getHandlerObject(this); // @other(unsafe)
let res; // @other(unsafe)

@@ -715,3 +729,3 @@ try {

// Note: target@this(unsafe) throws@this(unsafe)
const object = this.getObject(); // @other(unsafe)
const object = getHandlerObject(this); // @other(unsafe)
try {

@@ -730,3 +744,3 @@ if (!otherReflectPreventExtensions(object)) return false;

// Note: target@this(unsafe) throws@this(unsafe)
const object = this.getObject(); // @other(unsafe)
const object = getHandlerObject(this); // @other(unsafe)
let res; // @other(unsafe)

@@ -738,3 +752,3 @@ try {

}
return this.fromOtherWithContext(res);
return handlerFromOtherWithContext(this,res);
}

@@ -846,3 +860,3 @@

// Note: target@this(unsafe) key@prim receiver@this(unsafe) throws@this(unsafe)
const object = this.getObject(); // @other(unsafe)
const object = getHandlerObject(this); // @other(unsafe)
const mock = this.mock;

@@ -849,0 +863,0 @@ if (thisReflectApply(thisObjectHasOwnProperty, mock, key) && !thisOtherHasOwnProperty(object, key)) {

@@ -166,3 +166,11 @@ /* global host, bridge, data, context */

const resetPromiseSpecies = (p) => {
if (p instanceof globalPromise) {
// Note: We do not use instanceof to check if p is a Promise because
// Reflect.construct(Promise, [...], FakeNewTarget) can create a real Promise
// (with internal slots) whose prototype does not include globalPromise.prototype,
// bypassing the instanceof check entirely.
//
// Instead, we unconditionally set the constructor property on any object.
// This ensures species resolution always uses localPromise, regardless of
// how the promise was constructed.
if (p !== null && (typeof p === 'object' || typeof p === 'function')) {
// Always define an own data property for 'constructor' to eliminate

@@ -173,3 +181,10 @@ // any TOCTOU vulnerability. Accessor properties (getters) on either the

// while V8 internally sees a malicious species on subsequent reads.
if (!localReflectDefineProperty(p, 'constructor', { __proto__: null, value: localPromise, writable: true, configurable: true })) {
let success;
try {
success = localReflectDefineProperty(p, 'constructor', { __proto__: null, value: localPromise, writable: true, configurable: true });
} catch (e) {
// If defineProperty throws (e.g., Proxy with throwing trap), treat as failure
success = false;
}
if (!success) {
throw new LocalError('Unsafe Promise species cannot be reset');

@@ -747,13 +762,16 @@ }

// Secure Promise.try to prevent species attacks via static method stealing.
// Promise.try is uniquely vulnerable because it catches errors thrown by the callback
// INSIDE V8's Promise executor, passing them directly to the FakePromise's reject
// handler without going through bridge sanitization or transformer-instrumented catch blocks.
// Secure Promise static methods to prevent species attacks via static method stealing.
//
// Other Promise static methods are NOT vulnerable:
// - Promise.reject/withResolvers: errors come from user catch blocks (transformer-sanitized)
// - Promise.all/race/any/allSettled: use .then() internally (wrapped with ensureThis)
// - Promise.resolve: FakePromise doesn't implement proper thenable resolution
// Several methods are vulnerable because they catch errors during iteration/resolution
// and pass them directly to the result promise's reject handler. If the attacker does:
// FakePromise.all = Promise.all; FakePromise.all(iterable);
// Then `this` inside Promise.all is FakePromise, so it creates the result promise using
// `new FakePromise(executor)`. When iteration throws a host error (e.g., from accessing
// error.stack with error.name = Symbol()), Promise.all catches it and passes it to
// FakePromise's reject handler, which receives the unsanitized host error.
//
// We wrap Promise.try to always use localPromise as constructor regardless of `this`.
// The fix wraps ALL Promise static methods to always use localPromise as the constructor,
// ignoring `this`. This provides defense in depth even for methods like reject/withResolvers
// that aren't currently known to be exploitable.
//
const globalPromiseTry = globalPromise.try;

@@ -766,2 +784,43 @@ if (typeof globalPromiseTry === 'function') {

const globalPromiseAll = globalPromise.all;
globalPromise.all = function all(iterable) {
return apply(globalPromiseAll, localPromise, [iterable]);
};
const globalPromiseRace = globalPromise.race;
globalPromise.race = function race(iterable) {
return apply(globalPromiseRace, localPromise, [iterable]);
};
const globalPromiseAllSettled = globalPromise.allSettled;
if (typeof globalPromiseAllSettled === 'function') {
globalPromise.allSettled = function allSettled(iterable) {
return apply(globalPromiseAllSettled, localPromise, [iterable]);
};
}
const globalPromiseAny = globalPromise.any;
if (typeof globalPromiseAny === 'function') {
globalPromise.any = function any(iterable) {
return apply(globalPromiseAny, localPromise, [iterable]);
};
}
const globalPromiseResolve = globalPromise.resolve;
globalPromise.resolve = function resolve(value) {
return apply(globalPromiseResolve, localPromise, [value]);
};
const globalPromiseReject = globalPromise.reject;
globalPromise.reject = function reject(reason) {
return apply(globalPromiseReject, localPromise, [reason]);
};
const globalPromiseWithResolvers = globalPromise.withResolvers;
if (typeof globalPromiseWithResolvers === 'function') {
globalPromise.withResolvers = function withResolvers() {
return apply(globalPromiseWithResolvers, localPromise, []);
};
}
// Freeze globalPromise to prevent Symbol.hasInstance override

@@ -768,0 +827,0 @@ // (which would bypass the instanceof check in resetPromiseSpecies).

@@ -16,3 +16,3 @@ {

],
"version": "3.10.3",
"version": "3.10.4",
"main": "index.js",

@@ -19,0 +19,0 @@ "sideEffects": false,

@@ -5,2 +5,39 @@ # vm2 [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![License][license-image]][license-url] [![Node.js CI](https://github.com/patriksimek/vm2/actions/workflows/test.yml/badge.svg)](https://github.com/patriksimek/vm2/actions/workflows/test.yml) [![Known Vulnerabilities][snyk-image]][snyk-url]

## Important Security Disclaimer
**Before using vm2, you should understand how it works and its limitations.**
vm2 attempts to sandbox untrusted JavaScript code **within the same Node.js process** as your application. It does this through a complex network of [Proxies](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) that intercept and mediate every interaction between the sandbox and the host environment.
### The Fundamental Challenge
JavaScript is an extraordinarily dynamic language. Objects can be accessed through prototype chains, constructors can be reached via error objects, symbols provide protocol hooks, and async execution creates timing windows. The sheer number of ways to traverse from one object to another in JavaScript makes building an airtight in-process sandbox extremely difficult.
**We are honest about this reality:** Despite our best efforts, researchers and security professionals continuously discover new ways to escape the vm2 sandbox. We actively patch these vulnerabilities as they are reported, but the cat-and-mouse nature of in-process sandboxing means that:
1. **New bypasses will likely be discovered in the future.** Check our [security advisories](https://github.com/patriksimek/vm2/security/advisories) for known vulnerabilities.
2. **You must keep vm2 updated** to benefit from the latest security fixes. Subscribe to security advisories and update promptly.
3. **vm2 should not be your only line of defense.** Defense in depth is essential when running untrusted code.
### More Robust Alternatives
If you require stronger isolation guarantees, consider these alternatives that provide **true process or hardware-level isolation**:
| Solution | Approach | Performance | Trade-offs |
|----------|----------|-------------|------------|
| **[isolated-vm](https://github.com/laverdet/isolated-vm)** | Separate V8 isolates (different V8 heap) | Fast | In maintenance mode; requires manual V8 updates |
| **Separate process / Worker** | `child_process` or Worker threads with limited permissions | Medium | Higher IPC overhead; data must be serialized |
| **Containers / VMs** | Docker, gVisor, Firecracker | Slow | Startup overhead; resource-heavy |
| **Managed services** | Cloud-based code execution (e.g., AWS Lambda, Cloudflare Workers) | Variable | Network latency; external dependency |
### When vm2 May Still Be Appropriate
vm2 can be suitable when:
- You need tight integration with host objects and fast synchronous communication
- The untrusted code comes from a relatively trusted source (e.g., internal tools, plugin systems with vetted authors)
- You combine vm2 with other security layers (network isolation, filesystem restrictions, resource limits)
- You accept the risk and actively monitor for security updates
**If you're running code from completely untrusted sources (e.g., arbitrary user submissions), we strongly recommend using a solution with stronger isolation guarantees.**
## Features

@@ -14,3 +51,3 @@

- You can securely call methods and exchange data and callbacks between sandboxes
- Is immune to all known methods of attacks
- Actively maintained with patches for known escape methods (see [Security Disclaimer](#important-security-disclaimer))
- Transpiler support

@@ -17,0 +54,0 @@