Torna agli aggiornamenti
New releaseJul 27, 2026

matchlock v0.2.17

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.

Condividi

Matchlock

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.

Perché Matchlock?

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.

Guida rapida

Requisiti di sistema

  • Linux con supporto KVM
  • macOS su Apple Silicon

Installazione

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>

Utilizzo

# 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

SDK

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):

  • Default (non impostato): gli IP privati vengono bloccati ogni volta che viene inviata una configurazione di rete.
  • Blocco esplicito: chiama .WithBlockPrivateIPs(true) (o .BlockPrivateIPs()).
  • Permesso esplicito: chiama .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: true
  • BlockPrivateIPs: false (o true)

Per sandbox completamente offline (nessuna NIC guest / nessuna uscita), usa:

  • CLI: --no-network
  • Builder Go SDK: .WithNoNetwork()
  • Builder Python SDK: .with_no_network()
  • Builder TypeScript SDK: .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/:

DescrizioneEsempio
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 GitHubexamples/claude-code/
Claude Code con Docker all'interno della sandbox tramite SDKexamples/claude-code-with-docker/
Claude Code con abbonamento Claude Pro/Max nella sandboxexamples/claude-danger/
OpenAI Codex CLI in micro-VM con bootstrap GitHubexamples/codex/
Demone Docker all'interno della sandbox con systemdexamples/docker-in-sandbox/
Chatbot Streamlit che utilizza Agent Client Protocolexamples/agent-client-protocol/
Automazione del browser con Kodelet e Playwright MCPexamples/playwright/

Architettura

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

Modalità di rete

PiattaformaModalitàMeccanismo
LinuxProxy trasparentenftables DNAT sulle porte 80/443
macOSNAT (predefinita)NAT integrato di Virtualization.framework
macOSIntercettazione (con --allow-host/--secret)gVisor userspace TCP/IP a livello L4

Documentazione

Licenza

MIT

Categorie