
Isolierte JavaScript-Sandbox für Node.js, die nicht vertrauenswürdigen Code mit eingeschränktem Zugriff auf eingebaute Module und Host-Ressourcen über Proxy-basierte Abfangmechanismen ausführt.
vm2 ist eine Sandbox, die nicht vertrauenswürdigen Code mit auf die Whitelist gesetzten eingebauten Node-Modulen ausführen kann.
npm install vm2
## Schnellbeispiele```js
import { VM } from 'vm2';
const vm = new VM();
vm.run(`process.exit()`); // TypeError: process.exit is not a function
## Installation
To install `nuclei`, you need [Go](https://golang.org/dl/) installed on your system. Then you can install it with the following command:
```bash
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
Alternatively, you can download the pre-built binaries from the Releases page.
You can also run nuclei using Docker:
docker pull projectdiscovery/nuclei:latest
docker run -it projectdiscovery/nuclei:latest
If you are on macOS or Linux, you can install nuclei via Homebrew:
brew install nuclei
nuclei -h
This will display help for the tool. Here are all the switches it supports.
Usage:
nuclei [flags]
Flags:
INPUT:
-u, -target string[] target URLs/hosts to scan
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
-resume string resume scan using resume.cfg (clustering will be disabled)
-sa, -scan-all-ips scan all the IP's associated with DNS record
-health-check run diagnostic check up
``````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',
);
Bevor Sie vm2 verwenden, sollten Sie verstehen, wie es funktioniert und welche Einschränkungen es hat.
vm2 versucht, nicht vertrauenswürdigen JavaScript-Code im selben Node.js-Prozess wie Ihre Anwendung zu sandboxen. Dies geschieht über ein komplexes Netzwerk von Proxies, die jede Interaktion zwischen der Sandbox und der Host-Umgebung abfangen und vermitteln.
JavaScript ist eine außergewöhnlich dynamische Sprache. Objekte können über Prototyp-Ketten erreicht werden, Konstruktoren können über Fehlerobjekte erreicht werden, Symbole bieten Protokoll-Hooks, und asynchrone Ausführung erzeugt Timing-Fenster. Die schiere Anzahl an Möglichkeiten, in JavaScript von einem Objekt zu einem anderen zu gelangen, macht es äußerst schwierig, eine luftdichte In-Process-Sandbox zu bauen.
Wir sind ehrlich bezüglich dieser Realität: Trotz unserer besten Bemühungen entdecken Forscher und Sicherheitsexperten kontinuierlich neue Wege, die vm2-Sandbox zu umgehen. Wir patchen diese Schwachstellen aktiv, sobald sie gemeldet werden, aber die Natur des Katz-und-Maus-Spiels bei In-Process-Sandboxing bedeutet:
Wenn Sie stärkere Isolationsgarantien benötigen, ziehen Sie diese Alternativen in Betracht, die echte Prozess- oder Hardware-Level-Isolation bieten:
| Lösung | Ansatz | Leistung | Kompromisse |
|---|---|---|---|
| isolated-vm | Separate V8-Isolate (verschiedene V8-Heaps) | Schnell | Im Wartungsmodus; erfordert manuelle V8-Updates |
| Separater Prozess / Worker | child_process oder Worker-Threads mit eingeschränkten Berechtigungen | Mittel | Höherer IPC-Overhead; Daten müssen serialisiert werden |
| Container / VMs | Docker, gVisor, Firecracker | Langsam | Start-Overhead; ressourcenintensiv |
| Verwaltete Dienste | Cloud-basierte Code-Ausführung (z. B. AWS Lambda, Cloudflare Workers) | Variabel | Netzwerklatenz; externe Abhängigkeit |
vm2 kann geeignet sein, wenn:
Wenn Sie Code aus vollständig nicht vertrauenswürdigen Quellen ausführen (z. B. beliebige Benutzereinsendungen), empfehlen wir dringend eine Lösung mit stärkeren Isolationsgarantien.
| Laufzeitumgebung | Status |
|---|---|
| Node.js | Unterstützt. Die Sandbox ist eine Sicherheitsgrenze. |
| Bun | Experimentell. Teilweise funktionale Kompatibilität — keine Sicherheitsgrenze. |
Für Bun gelten zwei getrennte Einschränkungen, und keine impliziert die andere.
Es ist keine Sicherheitsgrenze. Das Bedrohungsmodell von vm2, der Angriffskatalog in
docs/ATTACKS.md und jeder Regressionstest in test/ghsa/
leiten sich aus V8-Interna ab. JavaScriptCore, das Bun verwendet, hat seine eigenen
Äquivalente, und keines wurde gegen die vm2-Brücke geprüft. Das Bestehen der Testsuite
unter Bun zeigt Kompatibilität, nicht dass die Sandbox dort hält. Verwenden Sie
vm2 unter Bun nicht, um nicht vertrauenswürdigen Code zu isolieren.
Die Kompatibilität ist teilweise, nicht gleichwertig. Ein grüner Bun-Lauf deckt nur die Tests ab,
die dort tatsächlich ausgeführt werden. test/bun-skips.js listet auf, was ausgeschlossen ist und warum,
und die bekannten Verhaltenslücken umfassen:
Buffer.from(arrayLike) gibt einen Puffer mit der Länge null zurückVMScript-Metadaten für filename / lineOffset / columnOffset sind nicht
beobachtbar, da JSC-CallSite-Objekte keine Methoden tragenObject.freeze auf einem eingefrorenen Host-Objekt mit einem nicht konfigurierbaren Accessor
wirft einen Proxy-Invariant-TypeError, wo V8 dies nicht tutBuffer-Operationen über die Sandbox-Grenze hinweg sind drastisch langsamer —
eine 64-MB-allocUnsafe dauert über 400 Sekunden gegenüber 1,7 unter Node, langsam genug,
um wie ein Hänger zu wirkenBehandeln Sie die Bun-Unterstützung als Best-Effort-Kompatibilität für vertrauenswürdigen Code und prüfen Sie die Skip-Liste, bevor Sie sich auf ein bestimmtes Verhalten verlassen.
Für einen detaillierten Einblick in die Interna von vm2, siehe docs/ATTACKS.md.
Probieren Sie es selbst aus:```js import { runInNewContext } from "node:vm";
runInNewContext('this.constructor.constructor("return process")().exit()'); console.log('Never gets executed.');
git clone https://github.com/example/tool.git
cd tool
pip install -r requirements.txt
python tool.py --target 192.168.1.1 --port 8080
Die Konfigurationsdatei befindet sich unter config.yaml. Hier können Sie Ports, Timeouts und andere Parameter anpassen.
server:
host: 0.0.0.0
port: 8080
timeout: 30
Weitere Informationen finden Sie in der offiziellen Dokumentation.
Beiträge sind willkommen! Bitte lesen Sie die CONTRIBUTING.md für Richtlinien.
Dieses Projekt ist unter der MIT-Lizenz lizenziert – siehe LICENSE für Details.
import { VM } from 'vm2';
new VM().run('this.constructor.constructor("return process")().exit()');
// Throws ReferenceError: process is not defined
```
## Dokumentation
- [VM](#vm)
- [NodeVM](#nodevm)
- [VMScript](#vmscript)
- [Fehlerbehandlung](#fehlerbehandlung)
- [Debugging von Sandbox-Code](#debugging-eines-sandbox-codes)
- [Schreibgeschützte Objekte](#schreibgeschuetzte-objekte-experimentell)
- [Geschützte Objekte](#geschuetzte-objekte-experimentell)
- [Beziehungen zwischen Sandboxes](#beziehungen-zwischen-sandboxes)
- [CLI](#cli)
- [Änderungen von 2.x zu 3.x](https://github.com/patriksimek/vm2/wiki/2.x-to-3.x-changes)
- [Dokumentation für 1.x und 2.x](https://github.com/patriksimek/vm2/wiki/1.x-and-2.x-docs)
- [Mitwirken](https://github.com/patriksimek/vm2/wiki/Contributing)
## VM
VM ist eine einfache Sandbox, um nicht vertrauenswürdigen Code synchron ohne die `require`-Funktion auszuführen. Nur eingebaute JavaScript-Objekte und Nodes `Buffer` sind verfügbar. Planungsfunktionen (`setInterval`, `setTimeout` und `setImmediate`) sind standardmäßig nicht verfügbar.
**Optionen:**
- `timeout` - Skript-Timeout in Millisekunden. **WARNUNG**: Sie sollten diese Option möglicherweise zusammen mit `allowAsync=false` verwenden. Darüber hinaus kann die Arbeit mit zurückgegebenen Objekten aus der Sandbox beliebigen Code ausführen und den Timeout umgehen. Man sollte testen, ob das zurückgegebene Objekt ein primitiver Wert ist, indem man `typeof` verwendet, und es im anderen Fall vollständig verwerfen (auch das Protokollieren oder das Erstellen von Fehlermeldungen mit einem solchen Objekt könnte erneut beliebigen Code ausführen).
- `sandbox` - Das globale Objekt der VM.
- `compiler` - `javascript` (Standard), `typescript`, `coffeescript` oder eine benutzerdefinierte Compiler-Funktion. Die Bibliothek erwartet, dass Sie den Compiler vorinstalliert haben, wenn der Wert auf `typescript` oder `coffeescript` gesetzt ist. **`typescript` erfordert `typescript@6` oder früher** — siehe [Compiler](#compiler).
- `eval` - Wenn auf `false` gesetzt, werfen alle Aufrufe von `eval` oder Funktionskonstruktoren (`Function`, `GeneratorFunction`, usw.) einen `EvalError` (Standard: `true`).
- `wasm` - Wenn auf `false` gesetzt, wirft jeder Versuch, ein WebAssembly-Modul zu kompilieren, einen `WebAssembly.CompileError` (Standard: `true`). Hinweis: `WebAssembly.JSTag` wird aus Sicherheitsgründen innerhalb der Sandbox entfernt, sodass Wasm-Code keine JavaScript-Ausnahmen abfangen kann.
- `allowAsync` - Wenn auf `false` gesetzt, wirft jeder Versuch, Code mit `async` auszuführen, einen `VMError` (Standard: `true`).
- `bufferAllocLimit` - Maximale Größe in Bytes für eine einzelne `Buffer.alloc` / `Buffer.allocUnsafe` / `Buffer.allocUnsafeSlow` / `Buffer(N)` / `new Buffer(N)`-Anfrage von innerhalb der Sandbox. Anfragen, die dieses Limit überschreiten, werfen synchron einen `RangeError`, ohne die Host-Zuweisung durchzuführen. Standard: `Infinity` (kein Limit, vollständig abwärtskompatibel). Einbettende Systeme, die nicht vertrauenswürdigen Code in speicherbegrenzten Umgebungen (Docker / Kubernetes / Lambda / serverless) ausführen, sollten sich für ein endliches Limit (z. B. `32 * 1024 * 1024`) als Teil einer mehrschichtigen DoS-Abwehr entscheiden, genauso wie sie sich für `timeout` entscheiden. Siehe [Härtungsempfehlungen](#haertungsempfehlungen) unten.
**WICHTIG**: Der Timeout wirkt nur auf synchronen Code, den Sie über `run` ausführen. Der Timeout funktioniert **NICHT** bei Methoden, die von der VM zurückgegeben werden. Es gibt einige Situationen, in denen der Timeout nicht funktioniert - siehe [#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
```
Du kannst auch Werte aus der VM abrufen.```js
let number = vm.run('1337'); // returns 1337
```
**TIPP**: Siehe Tests für weitere Verwendungsbeispiele.
## NodeVM
Im Gegensatz zu `VM` ermöglicht `NodeVM` das Einbinden von Modulen auf die gleiche Weise wie im regulären Node-Kontext.
**Optionen:**
- `console` – `inherit` zum Aktivieren der Konsole, `redirect` zum Umleiten an Events, `off` zum Deaktivieren der Konsole (Standard: `inherit`).
- `sandbox` – Das globale Objekt der VM.
- `compiler` – `javascript` (Standard), `typescript`, `coffeescript` oder eine benutzerdefinierte Compiler-Funktion (die den Code und dessen Dateipfad erhält). Die Bibliothek erwartet, dass der Compiler vorinstalliert ist, wenn der Wert auf `typescript` oder `coffeescript` gesetzt ist. **`typescript` erfordert `typescript@6` oder früher** – siehe [Compiler](#compilers).
- `eval` – Wenn auf `false` gesetzt, werfen alle Aufrufe von `eval` oder Funktionskonstruktoren (`Function`, `GeneratorFunction`, usw.) einen `EvalError` (Standard: `true`).
- `wasm` – Wenn auf `false` gesetzt, wirft jeder Versuch, ein WebAssembly-Modul zu kompilieren, einen `WebAssembly.CompileError` (Standard: `true`). Hinweis: `WebAssembly.JSTag` wird aus Sicherheitsgründen innerhalb der Sandbox entfernt, sodass Wasm-Code keine JavaScript-Ausnahmen abfangen kann.
- `bufferAllocLimit` – Gleiche Semantik wie bei `VM` – maximale Größe in Bytes für eine einzelne `Buffer.alloc`-Familienanfrage von innerhalb der Sandbox. Standard: `Infinity`. Siehe [Härtungsempfehlungen](#hardening-recommendations).
- `sourceExtensions` – Array von Dateierweiterungen, die als Quellcode behandelt werden (Standard: `['js']`).
- `require` – `true`, ein Objekt oder ein Resolver, um die `require`-Methode zu aktivieren (Standard: `false`).
- `require.external` – Werte können `true`, ein Array erlaubter externer Module oder ein Objekt sein (Standard: `false`). Alle Pfade, die mit `/node_modules/${beliebiges_erlaubtes_externes_modul}/(?!/node_modules/)` übereinstimmen, dürfen eingebunden werden.
- `require.external.modules` – Array erlaubter externer Module. Unterstützt auch Wildcards, sodass die Angabe von `['@scope/*-ver-??]` beispielsweise die Verwendung aller Module mit einem Namen der Form `@scope/something-ver-aa`, `@scope/other-ver-11` usw. erlaubt. Der `*`-Wildcard entspricht nicht Pfadtrennzeichen.
- `require.external.transitive` – Boolescher Wert, der angibt, ob transitive Abhängigkeiten externer Module erlaubt sind (Standard: `false`). **WARNUNG**: Wenn ein Modul transitiv eingebunden wird, kann jedes Modul es anschließend normal einbinden, selbst wenn dies vor dem Laden nicht möglich war.
- `require.builtin` – Array erlaubter eingebauter Module, akzeptiert ["\*"] für alle (Standard: keine). **WARNUNG**: "\*" kann gefährlich sein, da neue eingebaute Module hinzugefügt werden können.
- `require.root` – Eingeschränkte Pfade, aus denen lokale Module eingebunden werden können (Standard: jeder Pfad).
- `require.mock` – Sammlung von Mock-Modulen (sowohl extern als auch eingebaut).
- `require.context` – `host` (Standard), um Module im Host einzubinden und sie in die Sandbox zu proxien. `sandbox`, um Module in der Sandbox zu laden, zu kompilieren und einzubinden. `callback(moduleFilename, ext)`, um pro Modul dynamisch einen Kontext zu wählen. Der Standard ist `sandbox`, wenn nichts angegeben ist. Mit Ausnahme von `events` werden eingebaute Module immer im Host eingebunden und in die Sandbox geproxiert.
- `require.import` – Ein Array von Modulen, die beim Start in die NodeVM geladen werden.
- `require.resolve` – Eine zusätzliche Lookup-Funktion für den Fall, dass ein Modul in keinem der traditionellen Node-Lookup-Pfade gefunden wurde.
- `require.customRequire` – Wird anstelle der `require`-Funktion verwendet, um Module aus dem Host zu laden.
- `require.strict` – `false`, um den strikten Modus für Module, die per `require` geladen werden, nicht zu erzwingen (Standard: `true`).
- `require.fs` – Benutzerdefinierte Dateisystemimplementierung.
- `nesting` – **WARNUNG**: Dies zu erlauben ist ein Sicherheitsrisiko, da Skripte eine NodeVM erstellen können, die beliebige Host-Module einbinden kann. `true`, um VM-Verschachtelung zu aktivieren (Standard: `false`).
- `wrapper` – `commonjs` (Standard), um das Skript in einen CommonJS-Wrapper zu packen, `none`, um den vom Skript zurückgegebenen Wert abzurufen.
- `argv` – Array, das an `process.argv` übergeben wird.
- `env` – Objekt, das an `process.env` übergeben wird.
- `strict` – `true`, um geladene Module im strikten Modus auszuführen (Standard: `false`).
**WICHTIG**: Timeout ist für NodeVM nicht wirksam, daher ist es nicht immun gegen `while (true) {}` oder ähnliches Übel.
**DENK DARAN**: Je mehr Module du erlaubst, desto fragiler wird deine 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);
});
```
Wenn `wrapper` auf `none` gesetzt ist, verhält sich `NodeVM` für synchronen Code eher wie `VM`.```js
assert.ok(vm.run('return true') === true);
```
**TIPP**: Weitere Verwendungsbeispiele finden Sie in den Tests.
### Module über relative Pfade laden
Um Module über relative Pfade zu laden, müssen Sie den vollständigen Pfad des Skripts, das Sie ausführen, als zweites Argument an die `run`-Methode von vm übergeben, wenn das Skript ein String ist. Der Dateiname wird dann in allen Stack-Traces angezeigt, die vom Skript erzeugt werden.```js
vm.run('require("foobar")', '/data/myvmscript.js');
```
Wenn das Skript, das Sie ausführen, ein VMScript ist, wird der Pfad im VMScript-Konstruktor angegeben.```js
const script = new VMScript('require("foobar")', { filename: '/data/myvmscript.js' });
vm.run(script);
```
### Resolver
Ein Resolver kann über `makeResolverFromLegacyOptions` erstellt und für mehrere `NodeVM`-Instanzen verwendet werden, wodurch kompilierter Modulcode gemeinsam genutzt werden kann und möglicherweise die Ladezeiten verkürzt werden. Das erste Beispiel von `NodeVM` kann mit `makeResolverFromLegacyOptions` wie folgt neu geschrieben werden.```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
Sie können die Leistung steigern, indem Sie vorkompilierte Skripte verwenden. Das vorkompilierte VMScript kann mehrfach ausgeführt werden. Es ist wichtig zu beachten, dass der Code nicht an eine VM (Kontext) gebunden ist; vielmehr wird er vor jedem Lauf gebunden, nur für diesen Lauf.```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));
```
Es funktioniert sowohl für `VM` als auch für `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 wird automatisch kompiliert, sobald es zum ersten Mal ausgeführt wird. Man kann den Code jederzeit mit `script.compile()` kompilieren. Sobald der Code kompiliert ist, hat die Methode keine Wirkung mehr.
## Compiler
`compiler` akzeptiert `javascript` (Standard), `typescript`, `coffeescript` oder eine eigene Funktion. Die `typescript`- und `coffeescript`-Compiler sind optional – installiere das Paket selbst; vm2 hängt von keinem der beiden ab.
### TypeScript
**Der eingebaute `typescript`-Compiler erfordert `typescript@6` oder früher.**
vm2 transpiliert über die `transpileModule()`-API von TypeScript. TypeScript 7 hat sie aus dem Paket-Einstiegspunkt entfernt – `require('typescript')` löst dort nur noch zu `{ version, versionMajorMinor }` auf, und die Ersatz-API liegt hinter den ausdrücklich als instabil markierten `typescript/unstable/*`-Unterpfaden, von denen keiner ein Einzeldatei-Transpile-Äquivalent bietet. Es gibt daher nichts, auf das vm2 bei 7.x zurückfallen könnte.
Die Auswahl von `compiler: 'typescript'` mit installiertem TypeScript 7 wirft bei `new VMScript(...)` / `new VM(...)` einen Fehler:```
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 }.
```
Entweder pinne `typescript@6` oder stelle deinen eigenen Transpiler bereit – jede Funktion, die JavaScript zurückgibt, funktioniert, also sind TypeScript 7s `tsc`, esbuild, swc oder ein Type-Stripper alle gültig:```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);
```
Ein benutzerdefinierter Compiler empfängt `(code, filename)` und muss JavaScript-Quellcode zurückgeben. Er läuft **in der Host-Umgebung, vor der Sandboxing** — behandeln Sie ihn als vertrauenswürdigen Code und bauen Sie niemals einen aus nicht vertrauenswürdiger Eingabe.
### CoffeeScript
Erfordert, dass `coffee-script` installiert ist. Kompiliert mit `{ header: false, bare: true }`; alle `compilerOptions`, die Sie übergeben, werden darüber zusammengeführt.
## Fehlerbehandlung
Fehler bei der Codekompilierung und der synchronen Codeausführung können mit `try-catch` behandelt werden. Fehler bei der asynchronen Codeausführung können behandelt werden, indem ein `uncaughtException`-Event-Handler an den `process` von Node angehängt wird.```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);
});
```
## Debugging von Sandbox-Code
Sie können Code, der in der Sandbox läuft, debuggen oder inspizieren, als ob er in einem normalen Prozess laufen würde.
- Sie können Haltepunkte verwenden (dazu müssen Sie einen Skriptdateinamen angeben).
- Sie können das Schlüsselwort `debugger` verwenden.
- Sie können Schritt-für-Schritt (Step-in) verwenden, um in den in der Sandbox laufenden Code einzusteigen.
### Beispiel
/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;
```
## Schreibgeschützte Objekte (experimentell)
Um zu verhindern, dass Sandbox-Skripte Eigenschaften der proxied Objekte hinzufügen, ändern oder löschen, können Sie `freeze`-Methoden verwenden, um das Objekt schreibgeschützt zu machen. Dies ist nur innerhalb der VM wirksam. Eingefrorene Objekte werden tiefgreifend beeinflusst. Primitive Typen können nicht eingefroren werden.
**Beispiel ohne Verwendung von `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
```
**Beispiel mit Verwendung von `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
```
**WICHTIG:** Es ist nicht möglich, Objekte einzufrieren, die bereits an die VM weitergeleitet (proxied) wurden.
## Geschützte Objekte (experimentell)
Im Gegensatz zu `freeze` erlaubt diese Methode Sandbox-Skripten, Eigenschaften von Objekten hinzuzufügen, zu ändern oder zu löschen – mit einer Ausnahme: Es ist nicht möglich, Funktionen anzuhängen. Sandbox-Skripte können daher keine Methoden wie `toJSON`, `toString` oder `inspect` modifizieren.
**WICHTIG:** Es ist nicht möglich, Objekte zu schützen, die bereits an die VM weitergeleitet (proxied) wurden.
## Beziehungen zwischen 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
Bevor du vm2 in der Befehlszeile verwenden kannst, installiere es global mit `npm install vm2 -g`.```sh
vm2 ./script.js
```
## Härtungsempfehlungen
vm2 verhindert Sandbox-Escapes (nicht vertrauenswürdiger Code, der Host-Realm-Zugriff erlangt). Es verhindert jedoch **nicht** von sich aus jede Form von Ressourcenerschöpfung oder Denial-of-Service. Einbettende Anwendungen, die nicht vertrauenswürdigen Code ausführen, sollten die folgenden mehrschichtigen Abwehrmaßnahmen um die Sandbox herum hinzufügen.
### 1. Speicherzuweisung mit `bufferAllocLimit` begrenzen
Ein einzelner `Buffer.alloc(N)`-Aufruf mit einem vom Angreifer kontrollierten `N` läuft als eine synchrone Host-C++-Zuweisung, die das `timeout` von V8 nicht unterbrechen kann. In speicherbegrenzten Umgebungen kann eine ~100-Byte-Sandbox-Payload einen RSS-Sprung des Hosts von über 100 MB verursachen und den Host-Prozess durch OOM zum Absturz bringen. Setzen Sie `bufferAllocLimit` (z. B. `32 * 1024 * 1024`), um einzelne Zuweisungen zu begrenzen:```js
const vm = new VM({
timeout: 1000,
bufferAllocLimit: 32 * 1024 * 1024,
allowAsync: false,
});
```
Die Obergrenze gilt auch für die veralteten Pfade `Buffer(N)` und `new Buffer(N)`. Beachten Sie, dass eine aggregierte Erschöpfung (viele kleine Allokationen, `Buffer.concat`, `Uint8Array`, `String.repeat`, `Array(n).fill()` usw.) **nicht** von dieser Obergrenze abgedeckt ist – kombinieren Sie sie für eine vollständige Abdeckung mit einem hostseitigen Speicherlimit (`--max-old-space-size`, Container-Limit, cgroup).
### 2. Installieren Sie einen hostseitigen `unhandledRejection`-Handler
Es existiert eine Klasse von Host-Prozess-Abbruch-DoS, bei der Sandbox-Code eine `async function`, `async function*` oder `await using` erstellt, deren Rumpf einen Wert wirft, der während der Stack-Formatierung einen Fehler in der Host-Realm auslöst (z. B. `e.name = Symbol(); e.stack`). V8 erstellt das Rejection-Promise über das intrinsische Promise der Realm, was den `Promise`-Subklassen-Wrap von vm2 umgeht, sodass die Rejection als `unhandledRejection` zum Host entkommt. Unter Node 15+ ist das Standardverhalten, den Prozess zu beenden.
Das Schließen dieser Lücke erfordert eine Änderung des beobachtbaren Host-Verhaltens, daher liefert vm2 standardmäßig keinen Fix. Embedder sollten einen Prozess-Level-Handler installieren, der aus der Sandbox stammende Rejections verschluckt (oder protokolliert):```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);
});
```
Wenn Ihre Anwendung keine andere Quelle für unbehandelte Ablehnungen hat, ist ein pauschales Schlucken + Protokollieren akzeptabel:```js
process.on('unhandledRejection', reason => {
yourLogger.warn('swallowed sandbox rejection', reason);
});
```
Eine gezielte Korrektur könnte in einem zukünftigen Minor-Release hinter einem Opt-in-Flag `swallowSandboxUnhandledRejections` ausgeliefert werden; bis dahin ist der Host-seitige Handler die empfohlene Gegenmaßnahme.
### 3. Ausführung mit einem Prozessweiten Speicherlimit
Selbst wenn `bufferAllocLimit` gesetzt ist, sollte der Host-Prozess mit `--max-old-space-size` (oder einem entsprechenden Container-Speicherlimit) ausgeführt werden, das auf die Arbeitslast abgestimmt ist. Das Limit schützt vor der Primitive für einzelne Allokationen; das OS-Level-Limit schützt vor aggregierter Erschöpfung und vor jeder zukünftigen Allokations-Primitive, die vm2 noch nicht begrenzt hat.
### 4. `require.builtin: ['*']` als Nicht-Sandbox-Konfiguration behandeln
Der Wildcard `'*'` erweitert sich auf die meisten Node-Built-ins, einschließlich `child_process`, `fs`, `dgram`, `net`, `http` und `dns`. Dies sind vollwertige Host-Fähigkeits-Primitiven — `require('child_process').execSync('id')` ist unter `'*'` aus der Sandbox erreichbar. Die `'*'`-Semantik von vm2 ist beabsichtigt (einige Embedder führen vertrauenswürdigen, aber isolierten Code aus), sollte jedoch nicht als Standard für nicht vertrauenswürdigen Code verwendet werden. Bevorzugen Sie eine explizite Allowlist mit der kleinsten Menge an Modulen, die Ihre Sandbox tatsächlich benötigt.
### 5. `nesting: true` ist eine Escape-Hatch
`nesting: true` erlaubt Sandbox-Code, `require('vm2')` aufzurufen und verschachtelte NodeVMs zu konstruieren. **Die `require`-Konfiguration der verschachtelten VM wird vom Sandbox-Code gewählt, der sie konstruiert, und nicht durch die äußere VM eingeschränkt.** Konkret:```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
`);
```
Wenn Sie `nesting: true` setzen, haben Sie der Sandbox effektiv die gleiche Vertrauensstufe eingeräumt wie sich selbst. **Aktivieren Sie `nesting: true` nicht für nicht vertrauenswürdigen Code.** Verwenden Sie es nur, wenn Sie dem sandboxierten Code selbst vertrauen, aber aus nicht-sicherheitsrelevanten Gründen VM-artige Ausführungssemantik (frisches globales Objekt, kontrollierte Timeouts) wünschen.
`nesting: true` **erfordert ein explizites `require`-Konfigurationsobjekt** (z. B. `require: { builtin: [] }` oder `require: {}`). Jede andere Form — `require: false`, `require: undefined`, `require: null` oder das vollständige Weglassen von `require` — wirft beim Erstellen einen `VMError` (GHSA-m4wx-m65x-ghrr, ersetzt GHSA-8hg8-63c5-gwmx). Alle diese Formen erzeugen einen reinen NESTING_OVERRIDE-Resolver: Die Sandbox kann `require('vm2')` ausführen, aber sonst nichts, was eine reine Escape-Primitive ohne legitime Verwendung darstellt. Um alle Requires zu verweigern, entfernen Sie `nesting: true`. Um verschachtelte VMs zu erlauben, geben Sie eine explizite `require`-Konfiguration an, damit der Kompromiss an der Aufrufstelle sichtbar ist.
## Bekannte Probleme
- Es ist nicht möglich, eine Klasse zu definieren, die eine proxierte Klasse erweitert. Dies beinhaltet die Verwendung einer proxierten Klasse in `Object.create`.
- Direktes `eval` funktioniert nicht.
- Beim Protokollieren von Sandbox-Arrays wird der Array-Teil in den Eigenschaften wiederholt.
- Quellcode-Transformationen können zu einem anderen Quellstring für eine Funktion führen.
- Es gibt Möglichkeiten, den Node-Prozess von innerhalb der Sandbox zum Absturz zu bringen. Siehe [Härtungsempfehlungen](#hardening-recommendations).
- Der eingebaute `typescript`-Compiler funktioniert nicht mit TypeScript 7 oder neuer, da dort die `transpileModule()`-API, die vm2 verwendet, entfernt wurde. Verwenden Sie `typescript@6` oder übergeben Sie Ihren eigenen Transpiler — siehe [Compiler](#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