
Sandbox microVM éphémère pour agents d'IA avec liste blanche réseau, injection de secrets via proxy MITM, et isolation au niveau de la VM. Démarre en moins d'une seconde, prend en charge les SDK Go, Python et TypeScript.
# Matchlock > **Expérimental :** Ce projet est encore en développement actif et peut subir des changements majeurs. Matchlock est un outil en ligne de commande (CLI) pour exécuter des agents IA dans des micro-VM éphémères – avec une liste blanche réseau, une injection de secrets via un proxy MITM et un isolement au niveau de la VM. Vos secrets n’entrent jamais dans la VM. ## Pourquoi Matchlock ? Les agents IA ont besoin d’exécuter du code, mais leur donner un accès sans restriction à votre machine est risqué. Matchlock vous permet de fournir à un agent un environnement Linux complet qui démarre en moins d’une seconde – isolé et jetable. Lorsque vous passez `--allow-host` ou `--secret`, Matchlock verrouille le réseau – seul le trafic vers les hôtes explicitement autorisés passe, tout le reste est bloqué. Quand votre agent appelle une API, les vraies identifiants sont injectés en vol par l’hôte. Le sandbox ne voit jamais qu’un espace réservé. Même si l’agent est trompé en exécutant quelque chose de malveillant, vos clés ne fuient pas et il n’y a nulle part où envoyer les données. À l’intérieur, l’agent dispose d’un environnement Linux complet pour faire ce dont il a besoin. Il peut installer des paquets, écrire des fichiers et faire du désordre. À l’extérieur, votre machine ne ressent rien. Les montages par overlay de volume sont des instantanés isolés qui disparaissent une fois terminés. Même CLI et même comportement, que vous soyez sur un serveur Linux ou un MacBook. ## Démarrage rapide ### Configuration requise - **Linux** avec support KVM - **macOS** sur Apple Silicon ### Installation Voir [`docs/install.md`](https://github.com/jingkaihe/matchlock/blob/main/docs/install.md) pour les détails complets d’installation. **Installation rapide** Le script ci-dessous détecte le système d’exploitation et installe Matchlock avec Homebrew sur macOS et avec rpm/deb sur les distributions Linux Debian/RHEL. ```bash curl -fsSL https://raw.githubusercontent.com/jingkaihe/matchlock/main/scripts/install.sh | bash # Ou installer une version spécifique curl -fsSL https://raw.githubusercontent.com/jingkaihe/matchlock/main/scripts/install.sh | bash -s -- --version 0.2.4 ``` **Homebrew** L’installation via Homebrew est prise en charge aussi bien sur macOS que sur Linux : ```bash brew tap jingkaihe/essentials brew install matchlock ``` **Debian / Ubuntu (.deb)** ```bash sudo dpkg -i ./matchlock_<version>_linux_amd64.deb sudo apt-get install -f matchlock diagnose ``` **Fedora / RHEL / CentOS Stream (.rpm)** ```bash sudo dnf install ./matchlock_<version>_linux_amd64.rpm matchlock diagnose ``` Si `matchlock diagnose` signale une configuration hôte manquante, exécutez : ```bash sudo matchlock setup linux ``` Pour enrôler explicitement un utilisateur spécifique, exécutez : ```bash sudo matchlock setup user <name> ``` ### Utilisation ```bash # Basique 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' # Liste blanche réseau matchlock run --image python:3.12-alpine \ --allow-host "api.openai.com" python agent.py # Garder l’interception activée même avec une liste blanche vide, # afin que les hôtes puissent être ajoutés/supprimés en cours d’exécution. 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 # Injection de secrets (n’entre jamais dans la VM) export ANTHROPIC_API_KEY=sk-xxx matchlock run --image python:3.12-alpine \ --secret [email protected] python call_api.py # Sandbox longue durée matchlock run --image alpine:latest --rm=false # affiche l’ID de la VM matchlock run --image nginx:latest -d # identique à ci-dessus, détaché matchlock exec vm-abc12345 -it sh # s’y attacher matchlock port-forward vm-abc12345 8080:8080 # forward hôte:8080 -> invité:8080 # Publier des ports au démarrage matchlock run --image alpine:latest --rm=false -p 8080:8080 # Cycle de vie matchlock list | kill | rm | prune # Construire à partir d’un Dockerfile (utilise BuildKit-in-VM) matchlock build -f Dockerfile -t myapp:latest . # Pré-construire le rootfs à partir d’une image registre (met en cache pour un démarrage plus rapide) matchlock build alpine:latest # Gestion des images matchlock image ls # Lister toutes les images matchlock image rm myapp:latest # Supprimer une image locale docker save myapp:latest | matchlock image import myapp:latest # Importer depuis une archive tar ``` ## SDK Matchlock propose des SDK en Go, Python et TypeScript pour intégrer des sandbox directement dans votre application. Vous pouvez lancer des VM, exécuter des commandes, diffuser la sortie et gérer des fichiers par programme. **Go** ```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 ne voit jamais qu'un espace réservé - la vraie clé n'entre jamais dans le sandbox result, err := client.Exec(ctx, "echo $ANTHROPIC_API_KEY") if err != nil { panic(err) } fmt.Print(result.Stdout) // affiche "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) } } ``` Comportement des IP privées dans le SDK Go (`10/8`, `172.16/12`, `192.168/16`) : - Par défaut (non défini) : les IP privées sont bloquées lorsqu’une configuration réseau est envoyée. - Blocage explicite : appeler `.WithBlockPrivateIPs(true)` (ou `.BlockPrivateIPs()`). - Autorisation explicite : appeler `.AllowPrivateIPs()` ou `.WithBlockPrivateIPs(false)`. ```go sandbox := sdk.New("alpine:latest"). AllowHost("api.openai.com"). AddHost("api.internal", "10.0.0.10"). WithNetworkMTU(1200). AllowPrivateIPs() // surcharge explicite : block_private_ips=false // Interception réseau SDK (mutation requête/réponse, mise en forme du corps, transformation des lignes de données 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"}, }, }, }, }) ``` Si vous utilisez `client.Create(...)` directement (sans le constructeur), définissez : - `BlockPrivateIPsSet: true` - `BlockPrivateIPs: false` (ou `true`) Pour des sandbox totalement hors ligne (pas de carte réseau invitée / pas de trafic sortant), utilisez : - CLI : `--no-network` - Constructeur Go SDK : `.WithNoNetwork()` - Constructeur Python SDK : `.with_no_network()` - Constructeur TypeScript SDK : `.withNoNetwork()` **Python** ([PyPI](https://pypi.org/project/matchlock/)) ```bash pip install matchlock # ou uv add matchlock ``` ```python 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** ```bash npm install matchlock-sdk ``` ```ts 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(); } ``` Plus d’exemples dans le répertoire [`examples/`](https://github.com/jingkaihe/matchlock/blob/main/examples) : | Description | Exemple | |---|---| | Diffuse la réponse de l’API Anthropic avec injection de secret (Go) | [`examples/go/basic/`](https://github.com/jingkaihe/matchlock/blob/main/examples/go/basic) | | Terminal interactif avec PTY utilisant ExecInteractive (Go) | [`examples/go/exec_modes/`](https://github.com/jingkaihe/matchlock/blob/main/examples/go/exec_modes) | | Injecte la clé API via un hook d’interception réseau (Go) | [`examples/go/network_interception/`](https://github.com/jingkaihe/matchlock/blob/main/examples/go/network_interception) | | Hooks d’interception VFS pour les mutations d’opérations sur fichiers (Go) | [`examples/go/vfs_hooks/`](https://github.com/jingkaihe/matchlock/blob/main/examples/go/vfs_hooks) | | Diffuse la réponse de l’API Anthropic (Python) | [`examples/python/basic/`](https://github.com/jingkaihe/matchlock/blob/main/examples/python/basic) | | Modes d’exécution : flux, pipe et interactif (Python) | [`examples/python/exec_modes/`](https://github.com/jingkaihe/matchlock/blob/main/examples/python/exec_modes) | | Injecte la clé API via un hook d’interception réseau (Python) | [`examples/python/network_interception/`](https://github.com/jingkaihe/matchlock/blob/main/examples/python/network_interception) | | Hooks d’interception VFS pour les mutations d’opérations sur fichiers (Python) | [`examples/python/vfs_hooks/`](https://github.com/jingkaihe/matchlock/blob/main/examples/python/vfs_hooks) | | Diffuse la réponse de l’API Anthropic (TypeScript) | [`examples/typescript/basic/`](https://github.com/jingkaihe/matchlock/blob/main/examples/typescript/basic) | | Modes d’exécution : flux, pipe et interactif (TypeScript) | [`examples/typescript/exec_modes/`](https://github.com/jingkaihe/matchlock/blob/main/examples/typescript/exec_modes) | | Injecte la clé API via un hook d’interception réseau (TypeScript) | [`examples/typescript/network_interception/`](https://github.com/jingkaihe/matchlock/blob/main/examples/typescript/network_interception) | | CLI Claude Code dans une micro-VM avec bootstrap GitHub | [`examples/claude-code/`](https://github.com/jingkaihe/matchlock/blob/main/examples/claude-code) | | Claude Code avec Docker dans le sandbox via SDK | [`examples/claude-code-with-docker/`](https://github.com/jingkaihe/matchlock/blob/main/examples/claude-code-with-docker) | | Claude Code avec abonnement Claude Pro/Max dans le sandbox | [`examples/claude-danger/`](https://github.com/jingkaihe/matchlock/blob/main/examples/claude-danger) | | CLI OpenAI Codex dans une micro-VM avec bootstrap GitHub | [`examples/codex/`](https://github.com/jingkaihe/matchlock/blob/main/examples/codex) | | Démon Docker dans un sandbox avec systemd | [`examples/docker-in-sandbox/`](https://github.com/jingkaihe/matchlock/blob/main/examples/docker-in-sandbox) | | Chatbot Streamlit utilisant le protocole Agent Client | [`examples/agent-client-protocol/`](https://github.com/jingkaihe/matchlock/blob/main/examples/agent-client-protocol) | | Automatisation de navigateur avec Kodelet et Playwright MCP | [`examples/playwright/`](https://github.com/jingkaihe/matchlock/blob/main/examples/playwright) | ## Architecture ```mermaid 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 ``` ### Modes réseau | Plateforme | Mode | Mécanisme | |------------|------|-----------| | Linux | Proxy transparent | DNAT nftables sur les ports 80/443 | | macOS | NAT (par défaut) | NAT intégré de Virtualization.framework | | macOS | Interception (avec `--allow-host`/`--secret`) | TCP/IP espace utilisateur gVisor au niveau L4 | ## Documentation - [Manuel de cycle de vie et de nettoyage](https://github.com/jingkaihe/matchlock/blob/main/docs/lifecycle.md) - [Interception réseau](https://github.com/jingkaihe/matchlock/blob/main/docs/network-interception.md) - [Interception VFS](https://github.com/jingkaihe/matchlock/blob/main/docs/vfs-interception.md) - [Référence développeur](https://github.com/jingkaihe/matchlock/blob/main/AGENTS.md) ## Licence MIT