
Ephemere microVM-Sandbox für KI-Agenten mit Network Allowlisting, Secret Injection via MITM-Proxy und VM-Isolation. Startet in unter einer Sekunde, unterstützt Go/Python/TypeScript-SDKs.
Experimentell: Dieses Projekt befindet sich noch in aktiver Entwicklung und kann breaking changes unterliegen.
Matchlock ist ein CLI-Tool zum Ausführen von KI-Agenten in flüchtigen Mikro-VMs – mit Netzwerk-Whitelisting, Secret-Injection via MITM-Proxy und VM-Isolation. Deine Secrets gelangen niemals in die VM.
KI-Agenten müssen Code ausführen, aber ihnen uneingeschränkten Zugriff auf deinen Rechner zu geben, ist riskant. Matchlock ermöglicht es dir, einem Agenten eine vollständige Linux-Umgebung zu geben, die in unter einer Sekunde startet – isoliert und wegwerfbar.
Wenn du --allow-host oder --secret übergibst, versiegelt Matchlock das Netzwerk – nur Traffic zu explizit erlaubten Hosts kommt durch, alles andere wird blockiert. Wenn dein Agent eine API aufruft, werden die echten Anmeldedaten während der Übertragung vom Host eingefügt. Die Sandbox sieht nur einen Platzhalter. Selbst wenn der Agent dazu gebracht wird, etwas Bösartiges auszuführen, werden deine Schlüssel nicht preisgegeben und es gibt keinen Zielort für Daten. Im Inneren erhält der Agent eine vollständige Linux-Umgebung, um das zu tun, was er tun muss. Er kann Pakete installieren, Dateien schreiben und Chaos anrichten. Außen spürt dein Rechner nichts. Volume-Overlay-Mounts sind isolierte Snapshots, die verschwinden, wenn du fertig bist. Gleiches CLI und gleiches Verhalten, egal ob du auf einem Linux-Server oder einem MacBook arbeitest.
Siehe docs/install.md für vollständige Installationsdetails.
Schnellinstallation
Das folgende Skript erkennt das Betriebssystem und installiert Matchlock mit Homebrew auf macOS und mit rpm/deb auf Debian/RHEL-basierten Linux-Distributionen.
curl -fsSL https://raw.githubusercontent.com/jingkaihe/matchlock/main/scripts/install.sh | bash
# Oder eine bestimmte Version installieren
curl -fsSL https://raw.githubusercontent.com/jingkaihe/matchlock/main/scripts/install.sh | bash -s -- --version 0.2.4
Homebrew
Die Installation über Homebrew wird sowohl auf macOS als auch auf Linux unterstützt:
brew tap jingkaihe/essentials
brew install matchlock
Debian / Ubuntu (.deb)
sudo dpkg -i ./matchlock_<version>_linux_amd64.deb
sudo apt-get install -f
matchlock diagnose
Fedora / RHEL / CentOS Stream (.rpm)
sudo dnf install ./matchlock_<version>_linux_amd64.rpm
matchlock diagnose
Wenn matchlock diagnose eine fehlende Host-Einrichtung meldet, führe Folgendes aus:
sudo matchlock setup linux
Um einen bestimmten Benutzer explizit zu registrieren, führe Folgendes aus:
sudo matchlock setup user <name>
# Basis
matchlock run --image alpine:latest cat /etc/os-release
matchlock run --image alpine:latest -it sh
matchlock run --image alpine:latest --no-network -- sh -lc 'echo offline'
# Netzwerk-Whitelist
matchlock run --image python:3.12-alpine \
--allow-host "api.openai.com" python agent.py
# Halte die Abfangfunktion auch bei leerer Whitelist aktiviert,
# damit Hosts zur Laufzeit hinzugefügt/entfernt werden können.
matchlock run --image alpine:latest --rm=false --network-intercept
matchlock allow-list add <vm-id> api.openai.com,api.anthropic.com
matchlock allow-list delete <vm-id> api.openai.com
# Secret-Injection (gelangt niemals in die VM)
export ANTHROPIC_API_KEY=sk-xxx
matchlock run --image python:3.12-alpine \
--secret [email protected] python call_api.py
# Langlebige Sandboxen
matchlock run --image alpine:latest --rm=false # gibt VM-ID aus
matchlock run --image nginx:latest -d # dasselbe wie oben, detached
matchlock exec vm-abc12345 -it sh # daran anhängen
matchlock port-forward vm-abc12345 8080:8080 # forward host:8080 -> guest:8080
# Ports beim Start veröffentlichen
matchlock run --image alpine:latest --rm=false -p 8080:8080
# Lebenszyklus
matchlock list | kill | rm | prune
# Aus Dockerfile bauen (verwendet BuildKit-in-VM)
matchlock build -f Dockerfile -t myapp:latest .
# Rootfs aus Registry-Image vorab erstellen (cached für schnelleren Start)
matchlock build alpine:latest
# Image-Verwaltung
matchlock image ls # Alle Images auflisten
matchlock image rm myapp:latest # Ein lokales Image entfernen
docker save myapp:latest | matchlock image import myapp:latest # Aus Tarball importieren
Matchlock liefert Go-, Python- und TypeScript-SDKs zum Einbetten von Sandboxen direkt in deine Anwendung. Du kannst VMs starten, Befehle ausführen, Ausgaben streamen und Dateien programmatisch verwalten.
Go
package main
import (
"context"
"fmt"
"os"
"github.com/jingkaihe/matchlock/pkg/sdk"
)
func main() {
ctx := context.Background()
client, err := sdk.NewClient(sdk.DefaultConfig())
if err != nil {
panic(err)
}
defer client.Close(0)
defer client.Remove()
sandbox := sdk.New("alpine:latest").
AllowHost("dl-cdn.alpinelinux.org", "api.anthropic.com").
AddSecret("ANTHROPIC_API_KEY", os.Getenv("ANTHROPIC_API_KEY"), "api.anthropic.com")
if _, err := client.Launch(sandbox); err != nil {
panic(err)
}
if _, err := client.Exec(ctx, "apk add --no-cache curl"); err != nil {
panic(err)
}
// The VM only ever sees a placeholder - the real key never enters the sandbox
result, err := client.Exec(ctx, "echo $ANTHROPIC_API_KEY")
if err != nil {
panic(err)
}
fmt.Print(result.Stdout) // prints "SANDBOX_SECRET_a1b2c3d4..."
curlCmd := `curl -s --no-buffer https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"claude-haiku-4-5-20251001","max_tokens":1024,"stream":true,
"messages":[{"role":"user","content":"Explain TCP to me"}]}'`
if _, err := client.ExecStream(ctx, curlCmd, os.Stdout, os.Stderr); err != nil {
panic(err)
}
}
Go SDK Verhalten bei privaten IPs (10/8, 172.16/12, 192.168/16):
.WithBlockPrivateIPs(true) (oder .BlockPrivateIPs()) aufrufen..AllowPrivateIPs() oder .WithBlockPrivateIPs(false) aufrufen.sandbox := sdk.New("alpine:latest").
AllowHost("api.openai.com").
AddHost("api.internal", "10.0.0.10").
WithNetworkMTU(1200).
AllowPrivateIPs() // explizite Überschreibung: block_private_ips=false
// SDK Netzwerkabfangung (Request/Response-Mutation, Body-Shaping, SSE-Datenzeilen-Transformation)
sandbox = sandbox.WithNetworkInterception(&sdk.NetworkInterceptionConfig{
Rules: []sdk.NetworkHookRule{
{
Phase: sdk.NetworkHookPhaseBefore,
Action: sdk.NetworkHookActionMutate,
Hosts: []string{"api.openai.com"},
SetHeaders: map[string]string{"X-Trace-Id": "trace-123"},
},
{
Phase: sdk.NetworkHookPhaseAfter,
Action: sdk.NetworkHookActionMutate,
Hosts: []string{"api.openai.com"},
BodyReplacements: []sdk.NetworkBodyTransform{
{Find: "internal-id", Replace: "redacted"},
},
},
},
})
Wenn du client.Create(...) direkt verwendest (ohne den Builder), setze:
BlockPrivateIPsSet: trueBlockPrivateIPs: false (oder true)Für vollständig offline Sandboxen (kein Gast-NIC / kein ausgehender Verkehr) verwende:
--no-network.WithNoNetwork().with_no_network().withNoNetwork()Python (PyPI)
pip install matchlock
# or
uv add matchlock
import os
import sys
from matchlock import Client, Sandbox
sandbox = (
Sandbox("python:3.12-alpine")
.allow_host(
"dl-cdn.alpinelinux.org",
"files.pythonhosted.org", "pypi.org",
"astral.sh", "github.com", "objects.githubusercontent.com",
"api.anthropic.com",
)
.add_secret(
"ANTHROPIC_API_KEY", os.environ["ANTHROPIC_API_KEY"], "api.anthropic.com"
)
)
SCRIPT = """\
# /// script
# requires-python = ">=3.12"
# dependencies = ["anthropic"]
# ///
import anthropic, os
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
with client.messages.stream(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": "Explain TCP/IP."}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
print()
"""
with Client() as client:
client.launch(sandbox)
client.exec("pip install --quiet uv")
client.write_file("/workspace/ask.py", SCRIPT)
client.exec_stream("uv run /workspace/ask.py", stdout=sys.stdout, stderr=sys.stderr)
client.remove()
TypeScript
npm install matchlock-sdk
import { Client, Sandbox } from "matchlock-sdk";
const SCRIPT = `import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
const stream = anthropic.messages
.stream({
model: "claude-haiku-4-5-20251001",
max_tokens: 1024,
messages: [{ role: "user", content: "Explain TCP/IP." }],
})
.on("text", (text) => {
process.stdout.write(text);
});
await stream.finalMessage();
process.stdout.write("\\n");
`;
const client = new Client();
try {
const sandbox = new Sandbox("node:22-alpine")
.allowHost("registry.npmjs.org", "*.npmjs.org", "api.anthropic.com")
.addSecret("ANTHROPIC_API_KEY", process.env.ANTHROPIC_API_KEY ?? "", "api.anthropic.com");
await client.launch(sandbox);
await client.exec(
"npm init -y >/dev/null 2>&1 && npm install --quiet --no-bin-links @anthropic-ai/sdk",
{ workingDir: "/workspace" },
);
await client.writeFile("/workspace/ask.mjs", SCRIPT);
await client.execStream("node ask.mjs", {
workingDir: "/workspace",
stdout: process.stdout,
stderr: process.stderr,
});
} finally {
await client.close();
await client.remove();
}
Weitere Beispiele im Verzeichnis examples/:
graph LR
subgraph Host
CLI["Matchlock CLI"]
Policy["Policy Engine"]
Proxy["Transparent Proxy + TLS MITM"]
VFS["VFS Server"]
CLI --> Policy
CLI --> Proxy
Policy --> Proxy
end
subgraph VM["Micro-VM (Firecracker / Virtualization.framework)"]
Agent["Guest Agent"]
FUSE["/workspace (FUSE)"]
Image["Any OCI Image (Alpine, Ubuntu, etc.)"]
Agent --- Image
FUSE --- Image
end
Proxy -- "vsock :5000" --> Agent
VFS -- "vsock :5001" --> FUSE
| Plattform |
|---|
MIT
| Beschreibung | Beispiel |
|---|
| Streamt Anthropic-API-Antwort mit Secret-Injection (Go) | examples/go/basic/ |
| Interaktives Terminal mit PTY mittels ExecInteractive (Go) | examples/go/exec_modes/ |
| Injiziert API-Key über Network-Interception-Hook (Go) | examples/go/network_interception/ |
| VFS-Interception-Hooks für Dateioperations-Mutationen (Go) | examples/go/vfs_hooks/ |
| Streamt Anthropic-API-Antwort (Python) | examples/python/basic/ |
| Stream-, Pipe- und interaktive Ausführungsmodi (Python) | examples/python/exec_modes/ |
| Injiziert API-Key über Network-Interception-Hook (Python) | examples/python/network_interception/ |
| VFS-Interception-Hooks für Dateioperations-Mutationen (Python) | examples/python/vfs_hooks/ |
| Streamt Anthropic-API-Antwort (TypeScript) | examples/typescript/basic/ |
| Stream-, Pipe- und interaktive Ausführungsmodi (TypeScript) | examples/typescript/exec_modes/ |
| Injiziert API-Key über Network-Interception-Hook (TypeScript) | examples/typescript/network_interception/ |
| Claude Code CLI in Mikro-VM mit GitHub-Bootstrap | examples/claude-code/ |
| Claude Code mit Docker in der Sandbox via SDK | examples/claude-code-with-docker/ |
| Claude Code mit Claude Pro/Max-Abonnement in der Sandbox | examples/claude-danger/ |
| OpenAI Codex CLI in Mikro-VM mit GitHub-Bootstrap | examples/codex/ |
| Docker-Daemon in der Sandbox mit systemd | examples/docker-in-sandbox/ |
| Streamlit-Chatbot mit Agent-Client-Protocol | examples/agent-client-protocol/ |
| Browser-Automatisierung mit Kodelet und Playwright MCP | examples/playwright/ |
| Modus |
|---|
| Mechanismus |
|---|
| Linux | Transparenter Proxy | nftables DNAT auf Ports 80/443 |
| macOS | NAT (Standard) | Virtualization.framework integrierter NAT |
| macOS | Abfangung (mit --allow-host/--secret) | gVisor Userspace TCP/IP auf Layer 4 |