
vm2 v3.11.6
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 [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![License][license-image]][license-url]
[![Known Vulnerabilities][snyk-image]][snyk-url]
vm2 es un sandbox que puede ejecutar código no confiable con los módulos integrados de Node permitidos.
Aviso importante de seguridad
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 a través de una compleja red de Proxies 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 enorme cantidad de formas de pasar de un objeto a otro en JavaScript hace que construir un sandbox intra-proceso hermético sea extremadamente difícil.
Somos honestos acerca de esta realidad: A pesar de nuestros mejores esfuerzos, investigadores y profesionales de seguridad descubren continuamente nuevas formas de escapar del sandbox de vm2. Corregimos activamente estas vulnerabilidades a medida que se reportan, pero la naturaleza de "gato y ratón" del aislamiento intra-proceso implica que:
- Es probable que se descubran nuevas evasiones en el futuro. Consulta nuestros avisos de seguridad para conocer las vulnerabilidades conocidas.
- Debes mantener vm2 actualizado para beneficiarte de las últimas correcciones de seguridad. Suscríbete a los avisos de seguridad y actualiza con prontitud.
- 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 fuertes, considera estas alternativas que proporcionan aislamiento real a nivel de proceso o de hardware:
| Solución | Enfoque | Rendimiento | Ventajas y desventajas |
|---|---|---|---|
| isolated-vm | Isolates V8 separados (heap de V8 diferente) | Rápido | En modo de mantenimiento; requiere actualizaciones manuales de V8 |
| Proceso separado / Worker | Hilos child_process o Worker con permisos limitados | Medio | Mayor sobrecarga de IPC; los datos deben serializarse |
| Contenedores / MV | Docker, gVisor, Firecracker | Lento | Sobrecarga de inicio; uso intensivo de recursos |
| Servicios gestionados | Ejecución de código 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 de sistema de archivos, límites de recursos)
- Aceptas el riesgo y supervisas activamente las actualizaciones de seguridad
Si estás ejecutando código de fuentes completamente no confiables (p. ej., envíos arbitrarios de usuarios), te recomendamos encarecidamente usar una solución con garantías de aislamiento más fuertes.
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 el Aviso de seguridad)
- Soporte de transpiladores
Cómo funciona
- Usa el módulo interno VM para crear un contexto seguro.
- Usa Proxies para evitar escapar del sandbox.
- Sobrescribe el require integrado para controlar el acceso a los módulos.
Para un análisis detallado de los internals de vm2, consulta el archivo CONTRIBUTING.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.');
inyección de comandos. En algunos casos, incluso encontrará dos archivos `<target>.env.placeholder` para comparar tokens reales con marcadores de posición.
Todo lo que necesita proporcionar es: el `<target>` o cualquier otro nombre de herramienta o de categoría.
Para objetivos de endpoint, necesitará `<target>-plus-sec-files`, `<target>-plus-content-type`,
`<target>.env.placeholder`, etc. Estos se encuentran en el directorio `~/.gf/`.
> **Nota**: Hasta donde sé, `gf` no tiene patrones predeterminados; debe crearlos usted mismo.
El objetivo aquí es mejorar la calidad de vida. Ser eficiente al mismo tiempo que
se mantienen pruebas de alta calidad. Algo como `gf` cambia las reglas del juego.
Esta herramienta es simplemente `gf` con baterías incluidas.
---
# Instalación
## Manualmente
```bash
cp -r .gf ~/
Si ya tiene patrones, haga una copia de seguridad y luego cópielos:
cp -r .gf ~/.gf.bak
cp -r .gf ~/
O use Git:
git clone https://github.com/emadshanab/gf.git
Luego copie:
cp -r gf/.gf ~/
Completado de Bash
Añada la fuente a su ~/.bashrc, .zshrc o config.fish:
# gf-completion
source /path/to/.gf/.gf-completion.bash
Nota: Los completados no se aplicarán hasta que ejecute
.o recargue la configuración de su shell.
Esto habilita el completado de _gf_helper().
Uso```js
import { VM } from 'vm2';
new VM().run('this.constructor.constructor("return process")().exit()'); // Throws ReferenceError: process is not defined
## Instalación```sh
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
La herramienta es altamente configurable con diferentes modos de operación como Wiz, Open y Autonomous; también puedes ajustar los umbrales de diversidad y riesgo, dependiendo de tu caso de uso.
```bash
git clone https://github.com/<username>/Wolverine.git
cd Wolverine
pip install -r requirements.txt
python wolverine.py [options]
Por favor, consulta la documentación para instrucciones detalladas sobre instalación, uso y opciones compatibles.```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',
);
## Documentación
- [VM](#vm)
- [NodeVM](#nodevm)
- [VMScript](#vmscript)
- [Manejo de errores](#error-handling)
- [Depuración de código en un sandbox](#debugging-a-sandboxed-code)
- [Objetos de solo lectura](#read-only-objects-experimental)
- [Objetos protegidos](#protected-objects-experimental)
- [Relaciones entre sandboxes](#cross-sandbox-relationships)
- [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)
- [Contribuir](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 característica `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 desee 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 en caso contrario (hacer registros o crear mensajes de error con dicho objeto también podría ejecutar código arbitrario de nuevo).
- `sandbox` - El objeto global de VM.
- `compiler` - `javascript` (predeterminado), `typescript`, `coffeescript` o función de compilador personalizada. La librería espera que usted tenga el compilador preinstalado si el valor se establece en `typescript` o `coffeescript`.
- `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 única solicitud de `Buffer.alloc` / `Buffer.allocUnsafe` / `Buffer.allocUnsafeSlow` / `Buffer(N)` / `new Buffer(N)` desde dentro del sandbox. Las solicitudes que excedan 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 hacia atrás). Los integradores que ejecutan código no confiable en entornos con memoria restringida (Docker / Kubernetes / Lambda / serverless) deberían optar por un límite finito (p. ej. `32 * 1024 * 1024`) como parte de una defensa contra DoS por capas, de la misma manera que optan por `timeout`. Ver [Recomendaciones de endurecimiento](#hardening-recommendations) a continuación.
**IMPORTANTE**: El tiempo de espera solo es efectivo en código síncrono que se ejecute a través de `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; consulte [#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 los tests para 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 desactivar 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`.
- `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/${any_allowed_external_module}/(?!/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/something-ver-aa`, `@scope/other-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 integrados.
- `require.root` - Ruta(s) restringida(s) donde se pueden requerir módulos locales (por defecto: todas las rutas).
- `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 a cargar 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 a pasar a `process.argv`.
- `env` - Objeto a pasar a `process.env`.
- `strict` - `true` para cargar módulos en modo estricto (por defecto: `false`).
**IMPORTANTE**: El timeout 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);
**CONSEJO**: 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 un string. El nombre del archivo se muestra entonces en cualquier traza de pila generada por el script.```js
vm.run('require("foobar")', '/data/myvmscript.js');
Si el script que estás 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
Un resolver puede crearse mediante `makeResolverFromLegacyOptions` y utilizarse para varias instancias de `NodeVM`, lo que permite compartir código de módulos compilados y potencialmente acelerar los tiempos de carga. El primer ejemplo de `NodeVM` puede reescribirse 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,
});
VMScript
Puedes aumentar el rendimiento utilizando scripts precompilados. El VMScript precompilado puede ejecutarse múltiples 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));
El código se compila automáticamente la primera vez que se ejecuta. Se puede compilar el código en cualquier momento con script.compile(). Una vez que el código está compilado, el método no tiene efecto.
Manejo de errores
Los errores en la compilación de código y en la ejecución síncrona de código se pueden manejar con try-catch. Los errores en la ejecución asíncrona de código se pueden manejar 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); });
## Depurando código en un sandbox
Puedes depurar o inspeccionar código que se ejecuta en el sandbox como si se ejecutara en un proceso normal.
- Puedes usar puntos de interrupción (lo que requiere especificar un nombre de archivo de script)
- Puedes usar la palabra clave `debugger`.
- Puedes usar step-in para adentrarte en el código que se ejecuta en el 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 en profundidad. 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 pasados por proxy a la VM.
## Objetos protegidos (experimental)
A diferencia de `freeze`, este método permite que los scripts de 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 de la sandbox no pueden modificar métodos como `toJSON`, `toString` o `inspect`.
**IMPORTANTE:** No es posible proteger objetos que ya han sido pasados por proxy 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);
CLI
Antes de poder usar vm2 en la línea de comandos, instálalo globalmente con npm install vm2 -g.```sh
vm2 ./script.js
## Hardening recommendations
vm2 previene escapes de la sandbox (código no confiable que obtiene acceso al realm del host). No previene, **por sí solo**, cada forma de agotamiento de recursos o denegación de servicio. Quienes integren vm2 y ejecuten código no confiable deberían añadir las siguientes defensas en capas alrededor de la sandbox.
### 1. Limita la asignación de memoria con `bufferAllocLimit`
Una sola llamada a `Buffer.alloc(N)` con un `N` controlado por el atacante se ejecuta como una asignación síncrona en C++ del host que el `timeout` de V8 no puede interrumpir. En entornos con memoria limitada, un payload de la sandbox de ~100 bytes puede provocar un salto de más de 100 MB en el RSS del host y estrellar 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). Tenga 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ínelo con un límite de memoria del lado del host (--max-old-space-size, límite del contenedor, cgroup) para una cobertura completa.
2. Instalar un manejador unhandledRejection en el lado del host
Existe una clase de DoS por aborto del proceso host en la que el código de la sandbox crea una async function, async function* o await using cuyo cuerpo lanza un valor que dispara un error del realm del host durante el formateo de la pila (por ejemplo, e.name = Symbol(); e.stack). V8 crea la promesa de rechazo mediante el Promise intrínseco del realm, lo que evita el envoltorio de 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 deberían instalar un manejador a nivel de proceso que absorba (o registre) los rechazos originados en la 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 swallow general + registro es aceptable:```js
process.on('unhandledRejection', reason => {
yourLogger.warn('swallowed sandbox rejection', reason);
});
Una corrección limitada podría publicarse detrás de una marca opt-in swallowSandboxUnhandledRejections en una futura versión menor; hasta entonces, el manejador del lado del host es la mitigación recomendada.
3. Ejecuta con un límite de memoria a nivel de proceso
Incluso con bufferAllocLimit configurado, ejecuta el proceso anfitrión 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.
4. Trata require.builtin: ['*'] como una configuración no aislada
El comodín '*' se expande a la mayoría de los módulos integrados de Node, incluyendo child_process, fs, dgram, net, http y dns. Estos son primitivas completas de capacidad del host: require('child_process').execSync('id') es accesible desde el sandbox bajo '*'. La semántica de '*' de vm2 es intencional (algunos integradores ejecutan código de confianza pero aislado), pero no debería usarse como valor predeterminado para código no fiable. Prefiere una lista de permitidos explícita con el conjunto más pequeño de módulos que tu sandbox realmente necesita.
5. nesting: true es una vía de escape
nesting: true permite que el código del sandbox ejecute require('vm2') y construya NodeVMs anidados. La configuración de require de la VM anidada la elige el código del sandbox que la construye, y no está 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`, efectivamente le has otorgado 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, sustituye a GHSA-8hg8-63c5-gwmx). Todas esas formas producen un resolver de solo NESTING_OVERRIDE: el sandbox puede `require('vm2')` pero nada más, lo cual es una primitiva de escape pura sin ningún uso legítimo. Para denegar todos los require, elimina `nesting: true`. Para permitir VMs anidadas, proporciona una configuración `require` explícita para que la compensación sea visible en el lugar de la llamada.
## Problemas conocidos
- No es posible definir una clase que extienda una clase proxy. Esto incluye usar una clase proxy en `Object.create`.
- El eval directo no funciona.
- Al registrar arreglos del sandbox, la parte del arreglo se repetirá en las propiedades.
- Las transformaciones del código fuente pueden dar lugar a una cadena de código fuente diferente para una función.
- Hay formas de hacer fallar el proceso de node desde dentro del sandbox. Consulta [Recomendaciones de endurecimiento](#hardening-recommendations).
[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