实验性: 该项目仍在积极开发中,可能会发生破坏性变更。
Matchlock 是一个 CLI 工具,用于在临时微VM中运行 AI 代理——具有网络允许列表、通过中间人代理的秘密注入以及 VM 级隔离。你的秘密永远不会进入 VM。
AI 代理需要运行代码,但给予它们对你机器的不受限制的访问存在风险。Matchlock 让你为代理提供一个完整的 Linux 环境,该环境在一秒内启动——隔离且一次性。
当你传递 --allow-host 或 --secret 时,Matchlock 会封锁网络——只有明确允许的主机的流量可以通过,其他一切都被阻止。当你的代理调用 API 时,真实凭据由主机在飞行中注入。沙盒只看到一个占位符。即使代理被诱骗运行恶意软件,你的密钥也不会泄露,数据也无处可去。内部,代理拥有完整的 Linux 环境,可以做任何需要的事情。它可以安装软件包、写入文件、制造混乱。外部,你的机器毫无感觉。卷叠加挂载是隔离的快照,完成后消失。无论在 Linux 服务器上还是在 MacBook 上,CLI 和行为都相同。
完整安装详情请参见 docs/install.md。
快速安装
下面的脚本检测操作系统,并在 macOS 上使用 Homebrew 安装 matchlock,在基于 Debian/RHEL 的 Linux 发行版上使用 rpm/deb 安装。
curl -fsSL https://raw.githubusercontent.com/jingkaihe/matchlock/main/scripts/install.sh | bash
# Or install a specific release
curl -fsSL https://raw.githubusercontent.com/jingkaihe/matchlock/main/scripts/install.sh | bash -s -- --version 0.2.4
Homebrew
基于 Homebrew 的安装支持 macOS 和 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
如果 matchlock diagnose 报告缺少主机设置,请运行:
sudo matchlock setup linux
要显式注册特定用户,请运行:
sudo matchlock setup user <name>
# Basic
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'
# Network allowlist
matchlock run --image python:3.12-alpine \
--allow-host "api.openai.com" python agent.py
# Keep interception enabled even with an empty allowlist,
# so hosts can be added/removed at runtime.
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 (never enters the VM)
export ANTHROPIC_API_KEY=sk-xxx
matchlock run --image python:3.12-alpine \
--secret [email protected] python call_api.py
# Long-lived sandboxes
matchlock run --image alpine:latest --rm=false # prints VM ID
matchlock run --image nginx:latest -d # same as above, detached
matchlock exec vm-abc12345 -it sh # attach to it
matchlock port-forward vm-abc12345 8080:8080 # forward host:8080 -> guest:8080
# Publish ports at startup
matchlock run --image alpine:latest --rm=false -p 8080:8080
# Lifecycle
matchlock list | kill | rm | prune
# Build from Dockerfile (uses BuildKit-in-VM)
matchlock build -f Dockerfile -t myapp:latest .
# Pre-build rootfs from registry image (caches for faster startup)
matchlock build alpine:latest
# Image management
matchlock image ls # List all images
matchlock image rm myapp:latest # Remove a local image
docker save myapp:latest | matchlock image import myapp:latest # Import from tarball
Matchlock 提供了 Go、Python 和 TypeScript SDK,用于将沙盒直接嵌入到你的应用程序中。你可以以编程方式启动 VM、执行命令、流式输出和管理文件。
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 私有 IP 行为(10/8、172.16/12、192.168/16):
.WithBlockPrivateIPs(true)(或 .BlockPrivateIPs())。.AllowPrivateIPs() 或 .WithBlockPrivateIPs(false)。sandbox := sdk.New("alpine:latest").
AllowHost("api.openai.com").
AddHost("api.internal", "10.0.0.10").
WithNetworkMTU(1200).
AllowPrivateIPs() // explicit override: block_private_ips=false
// SDK network interception (request/response mutation, body shaping, SSE data-line transform)
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"},
},
},
},
})
如果你直接使用 client.Create(...)(不带构建器),设置:
BlockPrivateIPsSet: trueBlockPrivateIPs: false(或 true)对于完全离线的沙盒(无 guest NIC / 无出站),使用:
--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");
`;