
Sandbox microVM effimero per agenti AI con allowlisting di rete, iniezione di segreti tramite proxy MITM e isolamento a livello di VM. Si avvia in meno di un secondo, supporta SDK Go/Python/TypeScript.
Sperimentale: Questo progetto è ancora in fase di sviluppo attivo e soggetto a modifiche sostanziali.
Matchlock è uno strumento CLI per eseguire agenti AI in microVM effimere - con allowlisting di rete, iniezione di segreti tramite proxy MITM e isolamento a livello di VM. I tuoi segreti non entrano mai nella VM.
Gli agenti AI devono eseguire codice, ma dare loro accesso illimitato alla tua macchina è un rischio. Matchlock ti permette di fornire a un agente un ambiente Linux completo che si avvia in meno di un secondo - isolato e usa e getta.
Quando passi --allow-host o --secret, Matchlock sigilla la rete - solo il traffico verso host esplicitamente consentiti passa, tutto il resto viene bloccato. Quando il tuo agente chiama un'API, le credenziali reali vengono iniettate in volo dall'host. La sandbox vede solo un segnaposto. Anche se l'agente viene indotto a eseguire qualcosa di malevolo, le tue chiavi non vengono esposte e non c'è nessun posto dove i dati possano andare. All'interno l'agente riceve un ambiente Linux completo per fare tutto ciò di cui ha bisogno. Può installare pacchetti, scrivere file e fare confusione. All'esterno la tua macchina non sente nulla. I mount overlay del volume sono snapshot isolati che svaniscono quando hai finito. Stessa CLI e stesso comportamento, sia che tu sia su un server Linux che su un MacBook.
Vedi docs/install.md per i dettagli completi sull'installazione.
Installazione rapida
Lo script seguente rileva il sistema operativo e installa matchlock usando Homebrew su macOS, e rpm/deb su distribuzioni Linux Debian/RHEL.
curl -fsSL https://raw.githubusercontent.com/jingkaihe/matchlock/main/scripts/install.sh | bash
# Oppure installa una release specifica
curl -fsSL https://raw.githubusercontent.com/jingkaihe/matchlock/main/scripts/install.sh | bash -s -- --version 0.2.4
Homebrew
L'installazione basata su Homebrew è supportata sia su macOS che su Linux:
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
Se matchlock diagnose segnala una configurazione host mancante, esegui:
sudo matchlock setup linux
Per registrare esplicitamente un utente specifico, esegui:
sudo matchlock setup user <nome>
# Base
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'
# Allowlist di rete
matchlock run --image python:3.12-alpine \
--allow-host "api.openai.com" python agent.py
# Mantieni l'intercettazione abilitata anche con una allowlist vuota,
# per poter aggiungere/rimuovere host in fase di esecuzione.
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
# Iniezione di segreti (non entra mai nella VM)
export ANTHROPIC_API_KEY=sk-xxx
matchlock run --image python:3.12-alpine \
--secret [email protected] python call_api.py
# Sandbox a lunga durata
matchlock run --image alpine:latest --rm=false # stampa l'ID della VM
matchlock run --image nginx:latest -d # stesso di sopra, in detached
matchlock exec vm-abc12345 -it sh # si collega
matchlock port-forward vm-abc12345 8080:8080 # forward host:8080 -> guest:8080
# Pubblica porte all'avvio
matchlock run --image alpine:latest --rm=false -p 8080:8080
# Ciclo di vita
matchlock list | kill | rm | prune
# Build da Dockerfile (usa BuildKit-in-VM)
matchlock build -f Dockerfile -t myapp:latest .
# Pre-build rootfs da immagine registry (caching per avvii più veloci)
matchlock build alpine:latest
# Gestione immagini
matchlock image ls # Elenca tutte le immagini
matchlock image rm myapp:latest # Rimuove un'immagine locale
docker save myapp:latest | matchlock image import myapp:latest # Importa da tarball
Matchlock include SDK per Go, Python e TypeScript per incorporare sandbox direttamente nella tua applicazione. Puoi avviare VM, eseguire comandi, ottenere output in streaming e gestire file a livello di programmazione.
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)
}
// La VM vede solo un segnaposto - la chiave reale non entra mai nella sandbox
result, err := client.Exec(ctx, "echo $ANTHROPIC_API_KEY")
if err != nil {
panic(err)
}
fmt.Print(result.Stdout) // stampa "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)
}
}
Comportamento degli IP privati del Go SDK (10/8, 172.16/12, 192.168/16):
.WithBlockPrivateIPs(true) (o .BlockPrivateIPs())..AllowPrivateIPs() o .WithBlockPrivateIPs(false).sandbox := sdk.New("alpine:latest").
AllowHost("api.openai.com").
AddHost("api.internal", "10.0.0.10").
WithNetworkMTU(1200).
AllowPrivateIPs() // override esplicito: block_private_ips=false
// Intercettazione di rete SDK (mutazione richiesta/risposta, shaping del corpo, trasformazione delle linee dati SSE)
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"},
},
},
},
})
Se usi client.Create(...) direttamente (senza il builder), imposta:
BlockPrivateIPsSet: trueBlockPrivateIPs: false (o true)Per sandbox completamente offline (nessuna NIC guest / nessuna uscita), usa:
--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();
}
Altri esempi nella directory 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| Piattaforma |
|---|
MIT
| Descrizione | Esempio |
|---|
| Stream della risposta API Anthropic con iniezione di segreti (Go) | examples/go/basic/ |
| Terminale interattivo con PTY usando ExecInteractive (Go) | examples/go/exec_modes/ |
| Inietta chiave API tramite hook di intercettazione di rete (Go) | examples/go/network_interception/ |
| Hook di intercettazione VFS per mutazioni di operazioni su file (Go) | examples/go/vfs_hooks/ |
| Stream della risposta API Anthropic (Python) | examples/python/basic/ |
| Modalità di esecuzione stream, pipe e interattiva (Python) | examples/python/exec_modes/ |
| Inietta chiave API tramite hook di intercettazione di rete (Python) | examples/python/network_interception/ |
| Hook di intercettazione VFS per mutazioni di operazioni su file (Python) | examples/python/vfs_hooks/ |
| Stream della risposta API Anthropic (TypeScript) | examples/typescript/basic/ |
| Modalità di esecuzione stream, pipe e interattiva (TypeScript) | examples/typescript/exec_modes/ |
| Inietta chiave API tramite hook di intercettazione di rete (TypeScript) | examples/typescript/network_interception/ |
| Claude Code CLI in micro-VM con bootstrap GitHub | examples/claude-code/ |
| Claude Code con Docker all'interno della sandbox tramite SDK | examples/claude-code-with-docker/ |
| Claude Code con abbonamento Claude Pro/Max nella sandbox | examples/claude-danger/ |
| OpenAI Codex CLI in micro-VM con bootstrap GitHub | examples/codex/ |
| Demone Docker all'interno della sandbox con systemd | examples/docker-in-sandbox/ |
| Chatbot Streamlit che utilizza Agent Client Protocol | examples/agent-client-protocol/ |
| Automazione del browser con Kodelet e Playwright MCP | examples/playwright/ |
| Modalità |
|---|
| Meccanismo |
|---|
| Linux | Proxy trasparente | nftables DNAT sulle porte 80/443 |
| macOS | NAT (predefinita) | NAT integrato di Virtualization.framework |
| macOS | Intercettazione (con --allow-host/--secret) | gVisor userspace TCP/IP a livello L4 |