
Sandbox de JavaScript aislado para Node.js que ejecuta código no confiable con acceso restringido a módulos integrados y recursos del host mediante la intercepción basada en Proxy.
vm2 es un sandbox que puede ejecutar código no confiable con los módulos integrados de Node permitidos en la lista blanca.
npm install vm2
## Ejemplos rápidos```js
import { VM } from 'vm2';
const vm = new VM();
vm.run(`process.exit()`); // TypeError: process.exit is not a function
Aquí tienes la traducción al español del fragmento 5 de 55:
Nota: El fragmento proporcionado está vacío. No hay contenido que traducir.```js import { NodeVM } from 'vm2';
const vm = new NodeVM({ require: { external: true, root: './', }, });
vm.run(
var request = require('request'); request('http://www.google.com', function (error, response, body) { console.error(error); if (!error && response.statusCode == 200) { console.log(body); // Show the HTML for the Google homepage. } });,
'vm.js',
);
## Aviso de seguridad importante
**Antes de usar vm2, debes entender cómo funciona y sus limitaciones.**
vm2 intenta aislar código JavaScript no confiable **dentro del mismo proceso de Node.js** que tu aplicación. Lo hace mediante una compleja red de [Proxies](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) que interceptan y median cada interacción entre el sandbox y el entorno anfitrión.
### El desafío fundamental
JavaScript es un lenguaje extraordinariamente dinámico. Se puede acceder a los objetos a través de cadenas de prototipos, se puede llegar a los constructores mediante objetos de error, los símbolos proporcionan enlaces de protocolo y la ejecución asíncrona crea ventanas de sincronización. La gran cantidad de formas de pasar de un objeto a otro en JavaScript hace que construir un sandbox hermético dentro del proceso sea extremadamente difícil.
**Somos honestos sobre esta realidad:** A pesar de nuestros mejores esfuerzos, los investigadores y profesionales de seguridad descubren continuamente nuevas formas de escapar del sandbox de vm2. Parcheamos activamente estas vulnerabilidades a medida que se reportan, pero la naturaleza de gato y ratón del sandboxing dentro del proceso significa que:
1. **Es probable que se descubran nuevas evasiones en el futuro.** Consulta nuestros [avisos de seguridad](https://github.com/patriksimek/vm2/security/advisories) para conocer las vulnerabilidades conocidas.
2. **Debes mantener vm2 actualizado** para beneficiarte de los últimos parches de seguridad. Suscríbete a los avisos de seguridad y actualiza con prontitud.
3. **vm2 no debe ser tu única línea de defensa.** La defensa en profundidad es esencial al ejecutar código no confiable.
### Alternativas más robustas
Si necesitas garantías de aislamiento más sólidas, considera estas alternativas que proporcionan **aislamiento real a nivel de proceso o hardware**:
| Solución | Enfoque | Rendimiento | Compensaciones |
|----------|---------|-------------|----------------|
| **[isolated-vm](https://github.com/laverdet/isolated-vm)** | Aislamientos V8 separados (heap V8 diferente) | Rápido | En modo de mantenimiento; requiere actualizaciones manuales de V8 |
| **Proceso separado / Worker** | `child_process` o hilos Worker con permisos limitados | Medio | Mayor sobrecarga de IPC; los datos deben serializarse |
| **Contenedores / VMs** | Docker, gVisor, Firecracker | Lento | Sobrecarga de inicio; uso intensivo de recursos |
| **Servicios gestionados** | Ejecución de código basada en la nube (p. ej., AWS Lambda, Cloudflare Workers) | Variable | Latencia de red; dependencia externa |
### Cuándo vm2 puede seguir siendo apropiado
vm2 puede ser adecuado cuando:
- Necesitas una integración estrecha con objetos del anfitrión y comunicación síncrona rápida
- El código no confiable proviene de una fuente relativamente confiable (p. ej., herramientas internas, sistemas de plugins con autores verificados)
- Combinas vm2 con otras capas de seguridad (aislamiento de red, restricciones del sistema de archivos, límites de recursos)
- Aceptas el riesgo y supervisas activamente las actualizaciones de seguridad
**Si ejecutas código de fuentes completamente no confiables (p. ej., envíos de usuarios arbitrarios), recomendamos encarecidamente usar una solución con garantías de aislamiento más sólidas.**
## Entornos de ejecución
| Entorno | Estado |
|---------|--------|
| Node.js | Compatible. El sandbox es un límite de seguridad. |
| Bun | **Experimental.** Compatibilidad funcional parcial — **no** es un límite de seguridad. |
Se aplican dos limitaciones separadas a Bun, y ninguna implica la otra.
**No es un límite de seguridad.** El modelo de amenazas de vm2, el catálogo de ataques en
[`docs/ATTACKS.md`](https://github.com/patriksimek/vm2/blob/main/docs/ATTACKS.md) y cada prueba de regresión en `test/ghsa/`
se derivan de los internals de V8. JavaScriptCore, que usa Bun, tiene sus propios
equivalentes, y ninguno ha sido auditado contra el puente de vm2. Que la suite pase
bajo Bun demuestra compatibilidad, no que el sandbox se mantenga allí. **No uses
vm2 en Bun para aislar código no confiable.**
**La compatibilidad es parcial, no paridad.** Una ejecución exitosa en Bun cubre solo las pruebas
que realmente se ejecutan allí. `test/bun-skips.js` enumera lo que se excluye y por qué,
y las brechas de comportamiento conocidas incluyen:
- `Buffer.from(arrayLike)` devuelve un buffer de longitud cero
- Los metadatos `filename` / `lineOffset` / `columnOffset` de `VMScript` no son
observables, porque los objetos CallSite de JSC no llevan métodos
- `Object.freeze` en un objeto anfitrión congelado con un accessor no configurable
lanza un `TypeError` de invariante de proxy donde V8 no lo hace
- algunas operaciones de `Buffer` a través del límite del sandbox son drásticamente más lentas —
un `allocUnsafe` de 64 MB tarda más de 400 segundos frente a 1.7 en Node, lo suficientemente lento
como para parecer un cuelgue
Trata la compatibilidad con Bun como un esfuerzo de mejor compatibilidad para código confiable, y consulta la
lista de omisiones antes de depender de cualquier comportamiento particular.
## Características
- Ejecuta código no confiable de forma segura en un solo proceso junto a tu código
- Control total sobre la salida de consola del sandbox
- El sandbox tiene acceso limitado a los métodos del proceso
- Es posible requerir módulos (integrados y externos) desde el sandbox
- Puedes limitar el acceso a ciertos módulos integrados (o a todos)
- Puedes llamar métodos de forma segura e intercambiar datos y callbacks entre sandboxes
- Mantenido activamente con parches para métodos de escape conocidos (consulta [Aviso de seguridad](#important-security-disclaimer))
- Soporte de transpilador
## Cómo funciona
- Utiliza el módulo VM interno para crear un contexto seguro.
- Utiliza [Proxies](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) para evitar escapar del sandbox.
- Sobrescribe el require integrado para controlar el acceso a los módulos.
Para un análisis en profundidad de los internals de vm2, consulta [docs/ATTACKS.md](https://github.com/patriksimek/vm2/blob/main/docs/ATTACKS.md).
## ¿Cuál es la diferencia entre el vm de Node y vm2?
Pruébalo tú mismo:```js
import { runInNewContext } from "node:vm";
runInNewContext('this.constructor.constructor("return process")().exit()');
console.log('Never gets executed.');
I need the actual content of chunk 9 to translate it. Please provide the Markdown text you want translated.```js import { VM } from 'vm2';
new VM().run('this.constructor.constructor("return process")().exit()'); // Throws ReferenceError: process is not defined
## Documentación
- [VM](#vm)
- [NodeVM](#nodevm)
- [VMScript](#vmscript)
- [Manejo de errores](#manejo-de-errores)
- [Depuración de código en sandbox](#depuración-de-código-en-sandbox)
- [Objetos de solo lectura](#objetos-de-solo-lectura-experimental)
- [Objetos protegidos](#objetos-protegidos-experimental)
- [Relaciones entre sandboxes](#relaciones-entre-sandboxes)
- [CLI](#cli)
- [Cambios de 2.x a 3.x](https://github.com/patriksimek/vm2/wiki/2.x-to-3.x-changes)
- [Documentación de 1.x y 2.x](https://github.com/patriksimek/vm2/wiki/1.x-and-2.x-docs)
- [Contribuciones](https://github.com/patriksimek/vm2/wiki/Contributing)
## VM
VM es un sandbox simple para ejecutar código no confiable de forma síncrona sin la función `require`. Solo están disponibles los objetos integrados de JavaScript y el `Buffer` de Node. Las funciones de programación (`setInterval`, `setTimeout` y `setImmediate`) no están disponibles por defecto.
**Opciones:**
- `timeout` - Tiempo de espera del script en milisegundos. **ADVERTENCIA**: Es posible que desees usar esta opción junto con `allowAsync=false`. Además, operar sobre objetos devueltos desde el sandbox puede ejecutar código arbitrario y eludir el tiempo de espera. Se debe comprobar si el objeto devuelto es un primitivo con `typeof` y descartarlo por completo (registrar o crear mensajes de error con dicho objeto también podría ejecutar código arbitrario nuevamente) en el otro caso.
- `sandbox` - Objeto global de VM.
- `compiler` - `javascript` (predeterminado), `typescript`, `coffeescript` o una función de compilador personalizada. La biblioteca espera que tengas el compilador preinstalado si el valor se establece en `typescript` o `coffeescript`. **`typescript` requiere `typescript@6` o anterior** — consulta [Compiladores](#compiladores).
- `eval` - Si se establece en `false`, cualquier llamada a `eval` o a constructores de funciones (`Function`, `GeneratorFunction`, etc.) lanzará un `EvalError` (predeterminado: `true`).
- `wasm` - Si se establece en `false`, cualquier intento de compilar un módulo WebAssembly lanzará un `WebAssembly.CompileError` (predeterminado: `true`). Nota: `WebAssembly.JSTag` se elimina dentro del sandbox por razones de seguridad, por lo que el código wasm no puede capturar excepciones de JavaScript.
- `allowAsync` - Si se establece en `false`, cualquier intento de ejecutar código usando `async` lanzará un `VMError` (predeterminado: `true`).
- `bufferAllocLimit` - Tamaño máximo en bytes para una sola solicitud de `Buffer.alloc` / `Buffer.allocUnsafe` / `Buffer.allocUnsafeSlow` / `Buffer(N)` / `new Buffer(N)` desde dentro del sandbox. Las solicitudes que superen este límite lanzan un `RangeError` de forma síncrona sin realizar la asignación en el host. Predeterminado: `Infinity` (sin límite, totalmente compatible con versiones anteriores). Los integradores que ejecutan código no confiable en entornos con memoria limitada (Docker / Kubernetes / Lambda / serverless) deberían optar por un límite finito (p. ej., `32 * 1024 * 1024`) como parte de la defensa en capas contra DoS, de la misma manera que optan por `timeout`. Consulta [Recomendaciones de endurecimiento](#recomendaciones-de-endurecimiento) a continuación.
**IMPORTANTE**: El tiempo de espera solo es efectivo en código síncrono que ejecutes mediante `run`. El tiempo de espera **NO** funciona en ningún método devuelto por VM. Hay algunas situaciones en las que el tiempo de espera no funciona; consulta [#244](https://github.com/patriksimek/vm2/pull/244).```js
import { VM } from 'vm2';
const vm = new VM({
timeout: 1000,
allowAsync: false,
sandbox: {},
});
vm.run('process.exit()'); // throws ReferenceError: process is not defined
También puedes recuperar valores de la VM.```js let number = vm.run('1337'); // returns 1337
**TIP**: Consulta las pruebas para ver más ejemplos de uso.
## NodeVM
A diferencia de `VM`, `NodeVM` te permite requerir módulos de la misma manera que lo harías en el contexto normal de Node.
**Opciones:**
- `console` - `inherit` para habilitar la consola, `redirect` para redirigir a eventos, `off` para deshabilitar la consola (por defecto: `inherit`).
- `sandbox` - El objeto global de la VM.
- `compiler` - `javascript` (por defecto), `typescript`, `coffeescript` o una función de compilador personalizada (que recibe el código y su ruta de archivo). La librería espera que tengas el compilador preinstalado si el valor se establece en `typescript` o `coffeescript`. **`typescript` requiere `typescript@6` o anterior** — consulta [Compiladores](#compilers).
- `eval` - Si se establece en `false`, cualquier llamada a `eval` o a constructores de funciones (`Function`, `GeneratorFunction`, etc.) lanzará un `EvalError` (por defecto: `true`).
- `wasm` - Si se establece en `false`, cualquier intento de compilar un módulo WebAssembly lanzará un `WebAssembly.CompileError` (por defecto: `true`). Nota: `WebAssembly.JSTag` se elimina dentro del sandbox por razones de seguridad, por lo que el código wasm no puede capturar excepciones de JavaScript.
- `bufferAllocLimit` - Misma semántica que en `VM` — tamaño máximo en bytes para una sola solicitud de la familia `Buffer.alloc` desde dentro del sandbox. Por defecto: `Infinity`. Consulta [Recomendaciones de endurecimiento](#hardening-recommendations).
- `sourceExtensions` - Array de extensiones de archivo a tratar como código fuente (por defecto: `['js']`).
- `require` - `true`, un objeto o un Resolver para habilitar el método `require` (por defecto: `false`).
- `require.external` - Los valores pueden ser `true`, un array de módulos externos permitidos, o un objeto (por defecto: `false`). Todas las rutas que coincidan con `/node_modules/${cualquier_módulo_externo_permitido}/(?!/node_modules/)` pueden ser requeridas.
- `require.external.modules` - Array de módulos externos permitidos. También admite comodines, por lo que especificar `['@scope/*-ver-??]`, por ejemplo, permitirá usar todos los módulos que tengan un nombre de la forma `@scope/algo-ver-aa`, `@scope/otro-ver-11`, etc. El comodín `*` no coincide con separadores de ruta.
- `require.external.transitive` - Booleano que indica si las dependencias transitivas de los módulos externos están permitidas (por defecto: `false`). **ADVERTENCIA**: Cuando un módulo se requiere de forma transitiva, cualquier módulo puede entonces requerirlo normalmente, incluso si esto no era posible antes de que se cargara.
- `require.builtin` - Array de módulos integrados permitidos, acepta ["\*"] para todos (por defecto: ninguno). **ADVERTENCIA**: "\*" puede ser peligroso ya que se pueden añadir nuevos módulos integrados.
- `require.root` - Ruta(s) restringida(s) donde se pueden requerir módulos locales (por defecto: cualquier ruta).
- `require.mock` - Colección de módulos simulados (tanto externos como integrados).
- `require.context` - `host` (por defecto) para requerir módulos en el host y proxyarlos hacia el sandbox. `sandbox` para cargar, compilar y requerir módulos en el sandbox. `callback(moduleFilename, ext)` para elegir dinámicamente un contexto por módulo. El valor por defecto será sandbox si no se especifica nada. Excepto para `events`, los módulos integrados siempre se requieren en el host y se proxyan hacia el sandbox.
- `require.import` - Un array de módulos que se cargarán en NodeVM al inicio.
- `require.resolve` - Una función de búsqueda adicional en caso de que un módulo no se encuentre en una de las rutas de búsqueda tradicionales de node.
- `require.customRequire` - Se usa en lugar de la función `require` para cargar módulos desde el host.
- `require.strict` - `false` para no forzar el modo estricto en los módulos cargados por require (por defecto: `true`).
- `require.fs` - Implementación personalizada del sistema de archivos.
- `nesting` - **ADVERTENCIA**: Permitir esto es un riesgo de seguridad ya que los scripts pueden crear una NodeVM que pueda requerir cualquier módulo del host. `true` para habilitar el anidamiento de VMs (por defecto: `false`).
- `wrapper` - `commonjs` (por defecto) para envolver el script en un wrapper CommonJS, `none` para recuperar el valor devuelto por el script.
- `argv` - Array que se pasará a `process.argv`.
- `env` - Objeto que se pasará a `process.env`.
- `strict` - `true` para cargar módulos en modo estricto (por defecto: `false`).
**IMPORTANTE**: El tiempo de espera no es efectivo para NodeVM, por lo que no es inmune a `while (true) {}` o males similares.
**RECUERDA**: Cuantos más módulos permitas, más frágil se vuelve tu sandbox.```js
import { NodeVM } from 'vm2';
const vm = new NodeVM({
console: 'inherit',
sandbox: {},
require: {
external: true,
builtin: ['fs', 'path'],
root: './',
mock: {
fs: {
readFileSync: () => 'Nice try!',
},
},
},
});
// Sync
let functionInSandbox = vm.run('module.exports = function(who) { console.log("hello "+ who); }');
functionInSandbox('world');
// Async
let functionWithCallbackInSandbox = vm.run('module.exports = function(who, callback) { callback("hello "+ who); }');
functionWithCallbackInSandbox('world', greeting => {
console.log(greeting);
});
Cuando wrapper está establecido en none, NodeVM se comporta más como VM para código síncrono.```js
assert.ok(vm.run('return true') === true);
**TIP**: Consulta los tests para ver más ejemplos de uso.
### Cargar módulos mediante ruta relativa
Para cargar módulos mediante ruta relativa, debes pasar la ruta completa del script que estás ejecutando como segundo argumento al método `run` de vm si el script es una cadena. El nombre del archivo se mostrará entonces en cualquier traza de pila generada por el script.```js
vm.run('require("foobar")', '/data/myvmscript.js');
Si el script que está ejecutando es un VMScript, la ruta se proporciona en el constructor de VMScript.```js const script = new VMScript('require("foobar")', { filename: '/data/myvmscript.js' }); vm.run(script);
### Resolver
Se puede crear un resolver mediante `makeResolverFromLegacyOptions` y utilizarlo para múltiples instancias de `NodeVM`, lo que permite compartir el código de módulos compilados y potencialmente acelerar los tiempos de carga. El primer ejemplo de `NodeVM` se puede reescribir usando `makeResolverFromLegacyOptions` de la siguiente manera.```js
const resolver = makeResolverFromLegacyOptions({
external: true,
builtin: ['fs', 'path'],
root: './',
mock: {
fs: {
readFileSync: () => 'Nice try!',
},
},
});
const vm = new NodeVM({
console: 'inherit',
sandbox: {},
require: resolver,
});
Puede aumentar el rendimiento utilizando scripts precompilados. El VMScript precompilado puede ejecutarse varias veces. Es importante tener en cuenta que el código no está vinculado a ninguna VM (contexto); más bien, se vincula antes de cada ejecución, solo para esa ejecución.```js import { VM, VMScript } from 'vm2';
const vm = new VM(); const script = new VMScript('Math.random()'); console.log(vm.run(script)); console.log(vm.run(script));
Funciona tanto para `VM` como para `NodeVM`.```js
import { NodeVM, VMScript } from 'vm2';
const vm = new NodeVM();
const script = new VMScript('module.exports = Math.random()');
console.log(vm.run(script));
console.log(vm.run(script));
Code is compiled automatically the first time it runs. One can compile the code anytime with script.compile(). Once the code is compiled, the method has no effect.
compiler accepts javascript (default), typescript, coffeescript, or your own function. The typescript and coffeescript compilers are optional — install the package yourself; vm2 does not depend on either.
The built-in typescript compiler requires typescript@6 or earlier.
vm2 transpiles through TypeScript's transpileModule() API. TypeScript 7 removed it from the package entry point — require('typescript') there resolves to { version, versionMajorMinor } only, and the replacement API lives behind the explicitly unstable typescript/unstable/* subpaths, none of which provide a single-file transpile equivalent. There is therefore nothing for vm2 to fall back to on 7.x.
Selecting compiler: 'typescript' with TypeScript 7 installed throws at new VMScript(...) / new VM(...):```
VMError: The installed TypeScript (7.0.2) does not expose the transpileModule() API that
vm2's built-in TypeScript compiler uses; it was removed from the package entry point in
TypeScript 7. Install typescript@6 or earlier, or pass your own transpiler as a function:
{ compiler: (code, filename) => javaScriptSource }.
O bien fija `typescript@6`, o proporciona tu propio transpilador — cualquier función que devuelva JavaScript funciona, por lo que el `tsc` de TypeScript 7, esbuild, swc, o un type-stripper son todos válidos:```js
import { VM, VMScript } from 'vm2';
import { transformSync } from 'esbuild';
const script = new VMScript('const x: number = 1; x', {
compiler: (code, filename) => transformSync(code, { loader: 'ts', format: 'cjs' }).code,
});
new VM().run(script);
Un compilador personalizado recibe (code, filename) y debe devolver código fuente JavaScript. Se ejecuta en el ámbito del host, antes del sandboxing — trátalo como código de confianza y nunca construyas uno a partir de entrada no confiable.
Requiere que coffee-script esté instalado. Se compila con { header: false, bare: true }; cualquier compilerOptions que pases se fusiona sobre esos.
Los errores en la compilación de código y en la ejecución síncrona de código pueden manejarse con try-catch. Los errores en la ejecución asíncrona de código pueden manejarse adjuntando el manejador de eventos uncaughtException al process de Node.```js
try {
var script = new VMScript('Math.random()').compile();
} catch (err) {
console.error('Failed to compile script.', err);
}
try { vm.run(script); } catch (err) { console.error('Failed to execute script.', err); }
process.on('uncaughtException', err => { console.error('Asynchronous error caught.', err); });
## Depuración de código en sandbox
Puedes depurar o inspeccionar código que se ejecuta en la sandbox como si se ejecutara en un proceso normal.
- Puedes usar puntos de interrupción (lo que requiere que especifiques un nombre de archivo de script)
- Puedes usar la palabra clave `debugger`.
- Puedes usar paso a paso para entrar en el código que se ejecuta en la sandbox.
### Ejemplo
/tmp/main.js:```js
import { VM, VMScript } from 'vm2';
import { readFileSync } from 'node:fs';
const file = `${__dirname}/sandbox.js`;
// By providing a file name as second argument you enable breakpoints
const script = new VMScript(readFileSync(file), file);
new VM().run(script);
/tmp/sandbox.js```js const foo = 'ahoj';
// The debugger keyword works just fine everywhere. // Even without specifying a file name to the VMScript object. debugger;
## Objetos de solo lectura (experimental)
Para evitar que los scripts en sandbox añadan, cambien o eliminen propiedades de los objetos proxy, puedes usar los métodos `freeze` para hacer que el objeto sea de solo lectura. Esto solo es efectivo dentro de la VM. Los objetos congelados se ven afectados profundamente. Los tipos primitivos no se pueden congelar.
**Ejemplo sin usar `freeze`:**```js
const util = {
add: (a, b) => a + b,
};
const vm = new VM({
sandbox: { util },
});
vm.run('util.add = (a, b) => a - b');
console.log(util.add(1, 1)); // returns 0
Ejemplo con el uso de freeze:```js
const vm = new VM(); // Objects specified in the sandbox cannot be frozen.
vm.freeze(util, 'util'); // Second argument adds object to global.
vm.run('util.add = (a, b) => a - b'); // Fails silently when not in strict mode. console.log(util.add(1, 1)); // returns 2
**IMPORTANTE:** No es posible congelar objetos que ya han sido proxyados a la VM.
## Objetos protegidos (experimental)
A diferencia de `freeze`, este método permite que los scripts en la sandbox añadan, cambien o eliminen propiedades de los objetos, con una excepción: no es posible adjuntar funciones. Por lo tanto, los scripts en la sandbox no pueden modificar métodos como `toJSON`, `toString` o `inspect`.
**IMPORTANTE:** No es posible proteger objetos que ya han sido proxyados a la VM.
## Relaciones entre sandboxes```js
const assert = require('assert');
const { VM } = require('vm2');
const sandbox = {
object: new Object(),
func: new Function(),
buffer: new Buffer([0x01, 0x05]),
};
const vm = new VM({ sandbox });
assert.ok(vm.run(`object`) === sandbox.object);
assert.ok(vm.run(`object instanceof Object`));
assert.ok(vm.run(`object`) instanceof Object);
assert.ok(vm.run(`object.__proto__ === Object.prototype`));
assert.ok(vm.run(`object`).__proto__ === Object.prototype);
assert.ok(vm.run(`func`) === sandbox.func);
assert.ok(vm.run(`func instanceof Function`));
assert.ok(vm.run(`func`) instanceof Function);
assert.ok(vm.run(`func.__proto__ === Function.prototype`));
assert.ok(vm.run(`func`).__proto__ === Function.prototype);
assert.ok(vm.run(`new func() instanceof func`));
assert.ok(vm.run(`new func()`) instanceof sandbox.func);
assert.ok(vm.run(`new func().__proto__ === func.prototype`));
assert.ok(vm.run(`new func()`).__proto__ === sandbox.func.prototype);
assert.ok(vm.run(`buffer`) === sandbox.buffer);
assert.ok(vm.run(`buffer instanceof Buffer`));
assert.ok(vm.run(`buffer`) instanceof Buffer);
assert.ok(vm.run(`buffer.__proto__ === Buffer.prototype`));
assert.ok(vm.run(`buffer`).__proto__ === Buffer.prototype);
assert.ok(vm.run(`buffer.slice(0, 1) instanceof Buffer`));
assert.ok(vm.run(`buffer.slice(0, 1)`) instanceof Buffer);
Antes de poder usar vm2 en la línea de comandos, instálalo globalmente con npm install vm2 -g.```sh
vm2 ./script.js
## Recomendaciones de endurecimiento
vm2 previene escapes del sandbox (código no confiable que obtiene acceso al realm del host). **No** previene, por sí solo, toda forma de agotamiento de recursos o denegación de servicio. Quienes integren código no confiable deberían añadir las siguientes defensas en capas alrededor del sandbox.
### 1. Limitar la asignación de memoria con `bufferAllocLimit`
Una sola llamada a `Buffer.alloc(N)` con `N` controlado por el atacante se ejecuta como una asignación síncrona de C++ del host que el `timeout` de V8 no puede interrumpir. En entornos con memoria limitada, una carga útil del sandbox de ~100 bytes puede provocar un salto de RSS del host de más de 100 MB y bloquear el proceso del host por OOM. Establece `bufferAllocLimit` (p. ej. `32 * 1024 * 1024`) para limitar las asignaciones individuales:```js
const vm = new VM({
timeout: 1000,
bufferAllocLimit: 32 * 1024 * 1024,
allowAsync: false,
});
El límite también se aplica a las rutas obsoletas Buffer(N) y new Buffer(N). Ten en cuenta que el agotamiento agregado (muchas asignaciones pequeñas, Buffer.concat, Uint8Array, String.repeat, Array(n).fill(), etc.) no está cubierto por este límite: combínalo con un límite de memoria del lado del host (--max-old-space-size, límite del contenedor, cgroup) para una cobertura completa.
unhandledRejection del lado del hostExiste una clase de DoS por aborto del proceso host en la que el código del sandbox crea una async function, async function* o await using cuyo cuerpo lanza un valor que desencadena un error del realm del host durante el formateo de la pila (p. ej. e.name = Symbol(); e.stack). V8 crea la promesa de rechazo mediante el Promise intrínseco del realm, lo que evita el envoltorio de la subclase Promise de vm2, por lo que el rechazo escapa al host como unhandledRejection. En Node 15+ el comportamiento predeterminado es terminar el proceso.
Cerrar esto requiere cambiar el comportamiento observable del host, por lo que vm2 no incluye una corrección por defecto. Los integradores deben instalar un manejador a nivel de proceso que trague (o registre) los rechazos originados en el sandbox:```js // Recommended: filter rejections that originated inside vm2 and swallow them, // while letting your own host-side rejections propagate. process.on('unhandledRejection', (reason, promise) => { // Heuristic: rejections from the sandbox frequently surface as values // without proper Error semantics, or with stacks pointing at vm.js. // Adjust the predicate to match your application. if (looksLikeSandboxOrigin(reason)) { return; // swallow — don't terminate the process } // Otherwise: handle (or rethrow) as normal for your host code. yourLogger.error('unhandled rejection', reason); });
Si tu aplicación no tiene otra fuente de rechazos no manejados, un trago general + registro es aceptable:```js
process.on('unhandledRejection', reason => {
yourLogger.warn('swallowed sandbox rejection', reason);
});
Una corrección acotada podría publicarse detrás de una marca de activación opcional swallowSandboxUnhandledRejections en una futura versión menor; hasta entonces, el manejador del lado del host es la mitigación recomendada.
Incluso con bufferAllocLimit configurado, ejecuta el proceso del host con --max-old-space-size (o un límite de memoria de contenedor equivalente) dimensionado para la carga de trabajo. El límite protege contra la primitiva de asignación única; el límite a nivel de SO protege contra el agotamiento agregado y contra cualquier primitiva de asignación futura que vm2 aún no haya limitado.
require.builtin: ['*'] como una configuración que no es de sandboxEl comodín '*' se expande a la mayoría de los módulos integrados de Node, incluidos child_process, fs, dgram, net, http y dns. Estos son primitivas completas de capacidad del host — require('child_process').execSync('id') es alcanzable desde el sandbox bajo '*'. La semántica de '*' de vm2 es intencional (algunos integradores ejecutan código confiable pero aislado), pero no debería usarse como predeterminado para código no confiable. Prefiere una lista blanca explícita del conjunto más pequeño de módulos que tu sandbox realmente necesita.
nesting: true es una vía de escapenesting: true permite que el código del sandbox haga require('vm2') y construya NodeVMs anidadas. La configuración de require de la VM anidada es elegida por el código del sandbox que la construye, no restringida por la VM externa. Concretamente:```js
const vm = new NodeVM({ nesting: true, require: { builtin: [] } });
vm.run( const { NodeVM: NVM } = require('vm2'); // Inner VM's config is whatever the sandbox writes here: const inner = new NVM({ require: { builtin: ['child_process'] } }); inner.run('require("child_process").execSync("id")'); // RCE);
Si estableces `nesting: true`, has otorgado efectivamente al sandbox el mismo nivel de confianza que tienes tú. **No habilites `nesting: true` para código no confiable.** Úsalo solo cuando confíes en el propio código del sandbox pero quieras semántica de ejecución tipo VM (global nuevo, timeouts controlados) por razones no relacionadas con la seguridad.
`nesting: true` **requiere un objeto de configuración `require` explícito** (p. ej. `require: { builtin: [] }` o `require: {}`). Cualquier otra forma — `require: false`, `require: undefined`, `require: null`, u omitir `require` por completo — lanza `VMError` en la construcción (GHSA-m4wx-m65x-ghrr, que reemplaza a GHSA-8hg8-63c5-gwmx). Todas esas formas producen un resolver solo-NESTING_OVERRIDE: el sandbox puede `require('vm2')` pero nada más, lo cual es una primitiva de escape pura sin uso legítimo. Para denegar todos los requires, elimina `nesting: true`. Para permitir VMs anidadas, proporciona una configuración `require` explícita para que la compensación sea visible en el punto de llamada.
## Problemas conocidos
- No es posible definir una clase que extienda una clase proxificada. Esto incluye usar una clase proxificada en `Object.create`.
- El `eval` directo no funciona.
- Al registrar arrays del sandbox, se repetirá la parte del array en las propiedades.
- Las transformaciones del código fuente pueden resultar en una cadena de origen diferente para una función.
- Hay formas de bloquear el proceso de node desde dentro del sandbox. Consulta [Recomendaciones de endurecimiento](#hardening-recommendations).
- El compilador integrado `typescript` no funciona con TypeScript 7 o superior, que eliminó la API `transpileModule()` que usa vm2. Usa `typescript@6` o pasa tu propio transpilador — consulta [Compiladores](#compilers).
[npm-image]: https://img.shields.io/npm/v/vm2.svg
[npm-url]: https://www.npmjs.com/package/vm2
[license-image]: https://img.shields.io/npm/l/vm2.svg
[license-url]: https://raw.githubusercontent.com/patriksimek/vm2/resurrection/LICENSE.md
[downloads-image]: https://img.shields.io/npm/dm/vm2.svg
[downloads-url]: https://www.npmjs.com/package/vm2
[snyk-image]: https://snyk.io/test/github/patriksimek/vm2/badge.svg
[snyk-url]: https://snyk.io/test/github/patriksimek/vm2