
Эфемерная песочница microVM для AI-агентов с сетевым белым списком, инъекцией секретов через MITM-прокси и изоляцией на уровне ВМ. Загружается менее чем за секунду, поддерживает SDK для Go/Python/TypeScript.
Экспериментально: Этот проект всё ещё находится в активной разработке и может претерпевать критические изменения.
Matchlock — это CLI-инструмент для запуска ИИ-агентов в эфемерных микро-ВМ — с белым списком сети, внедрением секретов через MITM-прокси и изоляцией на уровне ВМ. Ваши секреты никогда не попадают внутрь ВМ.
ИИ-агентам нужно запускать код, но давать им неограниченный доступ к вашей машине — рискованно. Matchlock позволяет предоставить агенту полноценную среду Linux, которая загружается менее чем за секунду — изолированную и одноразовую.
Когда вы передаёте --allow-host или --secret, Matchlock блокирует сеть — проходит только трафик к явно разрешённым хостам, всё остальное блокируется. Когда ваш агент вызывает API, реальные учётные данные подставляются на лету хостом. Песочница видит только заглушку. Даже если агента обманом заставят выполнить что-то вредоносное, ваши ключи не утекут, и данные некуда отправить. Внутри агент получает полноценную среду Linux, где может делать всё необходимое: устанавливать пакеты, записывать файлы, устроить беспорядок. Снаружи ваша машина ничего не замечает. Тома поверхностного монтирования — это изолированные снимки, которые исчезают после завершения работы. Один и тот же CLI и одинаковое поведение, работает ли вы на Linux-сервере или на MacBook.
Полные инструкции по установке см. в docs/install.md.
Быстрая установка
Скрипт ниже определяет ОС и устанавливает matchlock с помощью Homebrew на macOS и rpm/deb на дистрибутивах Linux на основе Debian/RHEL.
curl -fsSL https://raw.githubusercontent.com/jingkaihe/matchlock/main/scripts/install.sh | bash
# Или установить конкретную версию
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>
# Базовое
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'
# Белый список сети
matchlock run --image python:3.12-alpine \
--allow-host "api.openai.com" python agent.py
# Оставить перехват включённым даже с пустым белым списком,
# чтобы хосты можно было добавлять/удалять во время выполнения.
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
# Внедрение секретов (никогда не попадает в ВМ)
export ANTHROPIC_API_KEY=sk-xxx
matchlock run --image python:3.12-alpine \
--secret [email protected] python call_api.py
# Долгоживущие песочницы
matchlock run --image alpine:latest --rm=false # выводит ID ВМ
matchlock run --image nginx:latest -d # то же, что выше, в фоновом режиме
matchlock exec vm-abc12345 -it sh # подключиться к ней
matchlock port-forward vm-abc12345 8080:8080 # перенаправить host:8080 -> guest:8080
# Публикация портов при запуске
matchlock run --image alpine:latest --rm=false -p 8080:8080
# Жизненный цикл
matchlock list | kill | rm | prune
# Сборка из Dockerfile (использует BuildKit в ВМ)
matchlock build -f Dockerfile -t myapp:latest .
# Предварительная сборка rootfs из образа реестра (кэшируется для более быстрого запуска)
matchlock build alpine:latest
# Управление образами
matchlock image ls # Список всех образов
matchlock image rm myapp:latest # Удалить локальный образ
docker save myapp:latest | matchlock image import myapp:latest # Импорт из tarball
Matchlock поставляет SDK для Go, Python и TypeScript для встраивания песочниц непосредственно в ваше приложение. Вы можете запускать ВМ, выполнять команды, передавать потоковый вывод и управлять файлами программно.
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)Для полностью автономных песочниц (без гостевого сетевого интерфейса / без исходящего трафика) используйте:
--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();
}
Больше примеров в каталоге 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
| Платформа |
|---|
MIT
| Описание | Пример |
|---|
| Потоковая передача ответа Anthropic API с внедрением секрета (Go) | examples/go/basic/ |
| Интерактивный терминал с PTY с использованием ExecInteractive (Go) | examples/go/exec_modes/ |
| Внедрение API-ключа через хук сетевого перехвата (Go) | examples/go/network_interception/ |
| Хуки перехвата VFS для мутаций файловых операций (Go) | examples/go/vfs_hooks/ |
| Потоковая передача ответа Anthropic API (Python) | examples/python/basic/ |
| Режимы выполнения: потоковый, конвейерный, интерактивный (Python) | examples/python/exec_modes/ |
| Внедрение API-ключа через хук сетевого перехвата (Python) | examples/python/network_interception/ |
| Хуки перехвата VFS для мутаций файловых операций (Python) | examples/python/vfs_hooks/ |
| Потоковая передача ответа Anthropic API (TypeScript) | examples/typescript/basic/ |
| Режимы выполнения: потоковый, конвейерный, интерактивный (TypeScript) | examples/typescript/exec_modes/ |
| Внедрение API-ключа через хук сетевого перехвата (TypeScript) | examples/typescript/network_interception/ |
| Claude Code CLI в микро-ВМ с загрузкой из GitHub | examples/claude-code/ |
| Claude Code с Docker внутри песочницы через SDK | examples/claude-code-with-docker/ |
| Claude Code с подпиской Claude Pro/Max в песочнице | examples/claude-danger/ |
| OpenAI Codex CLI в микро-ВМ с загрузкой из GitHub | examples/codex/ |
| Docker-демон внутри песочницы с systemd | examples/docker-in-sandbox/ |
| Чат-бот на Streamlit с использованием Agent Client Protocol | examples/agent-client-protocol/ |
| Автоматизация браузера с Kodelet и Playwright MCP | examples/playwright/ |
| Режим |
|---|
| Механизм |
|---|
| Linux | Прозрачный прокси | nftables DNAT на портах 80/443 |
| macOS | NAT (по умолчанию) | Встроенный NAT Virtualization.framework |
| macOS | Перехват (с --allow-host/--secret) | gVisor userspace TCP/IP на уровне L4 |