
Leichtgewichtige, sichere Linux-Sandboxes für nicht vertrauenswürdige Prozesse. Läuft im Browser und auf dem Server.
Vpod vpod?Ein vpod ist eine leichtgewichtige, portable Sandbox, die einem nicht vertrauenswürdigen Prozess eine sofortige Linux-Umgebung bietet. Sie verwendet eine RISC‑V-Architektur und läuft vollständig in WebAssembly.
Ein vpod führt ein vollständiges RISC‑V-System (RV64GC, einzelne vCPU) aus, das zu WebAssembly kompiliert wurde. Darin bootet ein echtes Linux-Kernel mit einem echten Userspace, sodass sich Shells, Tools und Daemons genau so verhalten wie auf echter Hardware.
Snapshots. Statt Linux von Grund auf zu booten, stellt ein vpod einen Snapshot wieder her: einen gespeicherten Maschinenzustand (CPU-Register, RAM, Dateisystem), der direkt nach dem Boot erfasst wurde. Die Wiederherstellung dauert deutlich unter einer Sekunde. Suspend funktioniert auf die gleiche Weise in umgekehrter Richtung – nur schmutzige Speicherseiten werden zurück auf die Festplatte geschrieben, sodass Sie eine Sandbox pausieren und später fortsetzen können, sogar aus einem anderen Prozess.
Ahead-of-time-Übersetzung. Reine Befehls-für-Befehls-Emulation ist langsam, und WebAssembly schließt einen Laufzeit-JIT aus. Daher werden beim Erstellen des Snapshots die heißesten Gast-Codepfade von RISC‑V in nativen Code übersetzt, der direkt in das WASM-Modul kompiliert wird. Zur Laufzeit leitet der Emulator in diese übersetzten Blöcke um, wenn der Gastcode übereinstimmt, und fällt auf den Interpreter zurück, wenn dies nicht der Fall ist. Das bringt etwa 5x bei CPU-gebundener Arbeit, ohne Auswirkungen auf die Isolation: Übersetzter Code durchläuft dieselben MMU- und Speicherprüfungen wie interpretierter Code.
Die WASI-Grenze. Die WASM-Komponente kommuniziert mit dem Host ausschließlich über WASI 0.2. Der Gast sieht niemals Host-Dateideskriptoren, Sockets oder Speicher: Dateisystemzugriff erfolgt über explizit gemountete Verzeichnisse, und Netzwerkzugriff läuft über einen User-Mode-Netzwerkstack innerhalb der Komponente, der den Host nur um einfache ausgehende Sockets bittet. Alles andere (Gast-Kernel, Prozesse, Speicher) lebt im linearen WASM-Speicher und stirbt mit ihm.
G (Allzweck-Erweiterungen)
C (Komprimierte Befehle) Reduziert die Codegröße um 30 %, verbessert die Befehlsabrufgeschwindigkeit und die Speichereffizienz. Das ist wichtig, wenn ein vollständiger Linux-Userspace in unserer speicherbeschränkten WASM-Umgebung läuft.
[!NOTE] Die V-Erweiterung (Vektor) ist nicht implementiert. RVV-Befehle würden als emuliertes RISC-V ausgeführt; es gibt kein SIMD-Passthrough zur Host-CPU. Das Hinzufügen von V würde den Emulationsaufwand erhöhen, ohne einen Leistungsvorteil für vektorisierte Workloads zu bringen.
npm install @capsule-run/vpod
import { Sandbox } from "@capsule-run/vpod";
const sandbox = await Sandbox.create();
// Der Zustand bleibt über Aufrufe hinweg erhalten
await sandbox.commands.run("export API_KEY=secret");
const key = await sandbox.commands.run("echo $API_KEY");
console.log(key.stdout); // secret
// Python-REPL — Variablen bleiben erhalten
await sandbox.code.run("data = [1, 2, 3]");
const total = await sandbox.code.run("print(sum(data))");
console.log(total.text); // 6
await sandbox.close();
Dasselbe Paket läuft in einem Browser-Tab, wo der Snapshot im origin-privaten Speicher statt auf der Festplatte zwischengespeichert wird.
[!IMPORTANT] Der erste Aufruf von
Sandbox.create()lädt den Standard-Snapshot (alpine) herunter und speichert ihn lokal zwischen, falls er nicht bereits vorhanden ist.
pip install vpod
from vpod import Sandbox
# Einen Befehl ausführen
sandbox = Sandbox.create()
result = sandbox.commands.run("whoami")
print(result.stdout) # root
sandbox.close()
# Persistente Sitzung — Zustand bleibt über Aufrufe hinweg erhalten
with Sandbox.create() as sandbox:
sandbox.commands.run("export API_KEY=secret")
result = sandbox.commands.run("echo $API_KEY")
print(result.stdout) # secret
# Python-REPL — Variablen bleiben erhalten
with Sandbox.create() as sandbox:
sandbox.code.run("import requests")
sandbox.code.run("data = [1, 2, 3]")
result = sandbox.code.run("print(sum(data))")
print(result.text) # 6
curl -fsSL https://install.vpod.sh | sh
Oder per PowerShell installieren (Windows)
irm https://install.vpod.sh | iex
# Einen Snapshot ziehen
vpod pull alpine:latest
# Eine interaktive Shell starten
vpod
Besuchen Sie die Vpod-Dokumentation.
Beiträge sind willkommen, von Fehlerberichten bis zur Unterstützung neuer Geräte. Öffnen Sie ein Issue, um alles Wesentliche zu besprechen, bevor Sie es bauen.
wasm32-wasip2-Target: rustup target add wasm32-wasip2# Einmalig: AOT-Stub generieren (ein frischer Klon hat keine übersetzten Blöcke)
./scripts/aot-stub.sh
# Die WASM-Komponente bauen (Bibliothek + CLI). Kopiert beide Stufen in sdks/python/vpod/
./scripts/build-wasm.sh
# Die Host-CLI installieren
cargo install --path crates/vpod
# Das Python-SDK im Entwicklermodus installieren
pip install -e "sdks/python[dev]"
# Das TypeScript-SDK bauen. Übernimmt die Komponente aus dem Python-SDK-Verzeichnis
cd sdks/typescript && npm install && npm run build
npm run build verwendet standardmäßig --tier aot; CI pinnt --tier base. Der Build
verweigert eine Komponente, die älter ist als die neueste Datei unter crates/, führen Sie also
./scripts/build-wasm.sh erneut aus, nachdem Sie den Emulator berührt haben. Eine Emulatoränderung zeigt sich nur
über den Gast, daher kompiliert eine veraltete Komponente und besteht fast alles.
CI führt diese bei jedem PR aus, führen Sie sie also vor dem Pushen aus:
cargo fmt --all -- --check # Formatierung
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all # Rust-Tests
# Python-SDK-Integrationstests (benötigt die WASM-Bibliothek an Ort und Stelle)
cp target/wasm32-wasip2/release/vpod_wasi_lib.wasm sdks/python/vpod/
pytest sdks/python/tests/ -v -m integration
# TypeScript-SDK (aus sdks/typescript)
npm run typecheck
npm test # Unit
npm run test:all # Unit + Integration, benötigt einen lokalen Snapshot
npm run test:perf # Gastzeit-Regressionen, exakte Konstanten
Die TypeScript-Tests importieren das gebaute dist/, nicht src/, bauen Sie also vor dem
Ausführen. Sie suchen nach einem Snapshot im gemeinsamen Cache-Verzeichnis;
VPOD_TEST_SNAPSHOT=/pfad/zu/x.snap zeigt auf einen anderen Ort.
Um den Browser Ende-zu-Ende zu testen, dient npm run dev die Seite mit aktiviertem COOP/COEP
aus und node dev/run-network.mjs --browser chrome steuert sie headless.
Die SDKs ziehen standardmäßig von registry.vpod.sh. Um einen selbst gebauten zu verwenden,
übergeben Sie ihn direkt statt eines Registry-Namens:
// TypeScript: eine Datei auf der Festplatte (Node) oder Bytes (überall)
await Sandbox.create({ snapshot: { path: "./dist/alpine-3.23.0-256mb.snap" } });
await Sandbox.create({ snapshot: { bytes, name: "alpine-3.23.0-256mb.snap" } });
# Python: VPOD_SNAPSHOT=/pfad/zu/x.snap
Behalten Sie die RAM-Größe in jedem Fall im Dateinamen, da der Emulator sie daraus liest.
Das Projekt verwendet vorgefertigte Alpine-Snapshots von registry.vpod.sh, daher benötigen Sie dies normalerweise nicht. Um einen lokal zu bauen:
./scripts/build-default-snapshot.sh # dist/alpine-3.23.0-256mb.snap
./scripts/build-data-snapshot.sh # 512-MB-Variante mit numpy/pandas/scipy
[!TIP] Um einen lokal gebauten Snapshot in der CLI zu verwenden, kommentieren Sie die Zeilen in
resolve_snapshot()incrates/vpod/src/main.rsaus.
Snapshot-Builds können auch den AOT-Durchlauf ausführen (scripts/aot-snapshot.sh <snapshot>), der einen repräsentativen Workload verfolgt, die heißen Blöcke übersetzt und den Emulator mit diesen eingebettet neu baut. Das dauert eine Weile; der Stub von aot-stub.sh ist für die tägliche Entwicklung ausreichend, alles funktioniert gleich, nur langsamer.
Benutzerdefinierte Snapshots können auch aus einem Dockerfile gebaut werden. Der Builder verwendet
Apples container-CLI auf macOS und
Docker Buildx auf Linux. Eine frische macOS-Installation muss ihre Laufzeit einmal konfigurieren,
sonst wartet der Build auf einen Builder, der nie startet:
container system kernel set --recommended
container builder start
Auf Linux installieren Sie Docker mit dem Buildx-Plugin und registrieren die riscv64-Emulation einmal, wenn der Host nicht bereits für plattformübergreifende Builds konfiguriert ist:
docker run --privileged --rm tonistiigi/binfmt --install riscv64
./scripts/build-custom-snapshot.sh -f Dockerfile -n my-image # dist/my-image-256mb.snap
# optional: --aot --trace-cmd '<der heiße Befehl des Images>' zum Einbetten von AOT-Blöcken
Das Dockerfile wird für linux/riscv64 gebaut (BuildKit führt RUN-Schritte
unter Emulation aus), sein abgeflachtes Rootfs ersetzt das Alpine-Minirootfs,
und der Rest der Pipeline ist identisch: vpod-Overlay, Boot,
--snapshot-save.
Nur das Dateisystem überlebt den Export. ENV, CMD und ENTRYPOINT aus der
Image-Konfiguration werden verworfen, persistieren Sie die Umgebung also über /etc/profile.d/ in
einem RUN-Schritt. Der Python-Warmstart wird automatisch für musl-basierte Images angewendet,
die python3 in /usr/bin oder /bin mitbringen.
fmt, clippy und die Testsuite müssen bestehen (CI erzwingt alle drei).Dieses Projekt ist unter der Apache License 2.0 lizenziert. Siehe die Datei LICENSE für Details.