关于 Headmesh Agent Sandbox
Headmesh Agent Sandbox 为需要操作真实文件、依赖、Git 和私有服务的 Agent 提供持久化 Docker 沙箱。Go 守护进程和主机 CLI 提供 MCP 访问、持久会话及常规与 YOLO 模式。
主要功能
- 01持久化 Docker 沙箱
- 02命令执行与文件编辑
- 03Git 和 tmux 会话管理
- 04作用域令牌与审计
- 05常规与 YOLO 模式
适用场景
- 01让编码 Agent 操作真实项目文件
- 02断线重连后保留 Agent 会话
- 03向 MCP 客户端开放沙箱工具
MCP 相关
`headmesh-sandboxd` 守护进程通过 MCP 暴露沙箱工具,包括命令执行、会话操作和文件系统操作。支持 Unix 套接字与 HTTP 访问,HTTP 使用 bearer 令牌认证。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://{sandbox_host}/mcpclaude mcp add --transport http headmesh-dev-headmesh-sandbox https://{sandbox_host}/mcp{"mcpServers":{"headmesh-dev-headmesh-sandbox":{"type":"http","url":"https://{sandbox_host}/mcp"}}}在 Cursor 中打开{"servers":{"headmesh-dev-headmesh-sandbox":{"type":"http","url":"https://{sandbox_host}/mcp"}}}在 VS Code 中打开{"mcpServers":{"headmesh-dev-headmesh-sandbox":{"type":"http","url":"https://{sandbox_host}/mcp"}}}{"mcpServers":{"headmesh-dev-headmesh-sandbox":{"serverUrl":"https://{sandbox_host}/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
https://{sandbox_host}/mcp需要授权工具
详细介绍
Headmesh Agent 沙箱
ASUS 上的持久化 Docker 容器,作为独立的已标记节点加入 Aaryn 的 Headscale tailnet, 让云端 Agent 能使用真实文件、真实依赖项和私有服务。Agent 可通过多个入口访问它:
- MCP 服务
headmesh-sandboxd; - Claude 可自托管的 runner 和 Claude Remote Control;
- 通过 Tailscale SSH 使用 Codex;
- OpenAI Secure MCP Tunnel;
codex exec-server,由主机通过 Agents API 驱动。
它实现了计划 §6 和 §7(docs/plan.md)。
| 路径 | 内容 |
|---|---|
image/ | 基于 ubuntu:26.04(按摘要固定)的多阶段 Dockerfile、s6-overlay 服务、git hooks、指南模板、docker-bake.hcl、container-structure-test、hadolint 配置 |
compose/ | 包含 regular 和 yolo 配置的 compose.yaml(嵌入 CLI 中) |
config/ | sandbox.example.toml 及其 JSON Schema(sandbox.schema.json) |
sandboxd/ | MCP 守护进程:工具、会话(tmux)、身份验证、审计、状态 |
cmd/headmesh-sandboxd/ | 守护进程二进制文件,以及容器内的辅助工具(hm) |
cmd/headmesh-sandbox/ | 主机 CLI |
host/ | 出站防火墙和 restic 备份单元。Aaryn 使用 sudo 安装;详见 host/README.md |
tests/acceptance/ | 五项检查的测试框架、重建加分项和 prompt.md |
tests/e2e/ | 针对运行中容器的实时测试 |
docs/ | container-contract.md(镜像、Compose、CLI 和守护进程之间的接口)以及 ADR |
这是独立的 Go 模块 github.com/headmesh-dev/headmesh/sandbox;根目录下的
go.work 已包含它。在此运行 go test -race ./...。
已验证内容(2026-10-27)
以下所有测试均在本地运行,未加入 tailnet:
go test -race ./...通过。其中包括针对真实 tmux 和真实 git 的集成测试,以及针对进程内 sandboxd 的测试框架。使用仓库配置运行golangci-lint run,报告 0 个问题。docker buildx bake -f sandbox/image/docker-bake.hcl sandbox-local成功构建headmesh-sandbox:dev(2.39 GB)。hadolint 检查通过,container-structure-test 43/43 项通过。headmesh-sandbox up通过 Compose 启动容器,并启用 §6.3 加固配置。tailscaled 尚未登录时,容器报告健康状态;未配置的各入口处于空闲状态,并说明原因。status和doctor均可用。- 针对运行中的容器,通过 unix socket 和 HTTP bearer token,
tools/list(31 个工具)、exec、session_*和fs_*均通过。两种协议版本均可运行:2026-07-28 无状态版本和 2025-11-25 握手版本 (tests/e2e)。 - 验收测试,路由
mcp和路由ssh(在容器内运行第 1 阶段,再从新进程运行第 2 阶段):- 检查 1–3 通过:uid 1000、Ubuntu 26.04、文件哈希与主机上的副本匹配,且依赖项可以运行。
- 检查 5 的机制通过:重连后会话仍在运行,标记文件也存在。
- 检查 4 和 5 仍未通过,因为节点尚未加入网络。
acceptance recreate以 5/5 通过:home 和~/.bash_history均会保留,启动标识会变化,旧会话会报告lost (container restarted at …)。 tailnet 身份部分已跳过(尚未加入网络)。- YOLO:
- 没有新的备份记录时会拒绝启动。
- 有备份记录时,会通过按子树只读挂载的
/host启动。即使以 root 身份运行,写入也会失败;传播模式为private;读取/etc/shadow得到 0 字节;~/.ssh已屏蔽;主机 tailscaled 状态和 Docker 数据根目录均未挂载。 - 到达 TTL 时会自行停止,并写入审计记录;下一次
up将使用 Regular 模式。
Spike 的结果见 docs/adr/spike-t0{1,2,3,7,8}-*.md 和
docs/adr/spike-t16-*.md。其他实现决策见
docs/adr/0001-sandbox-implementation-decisions.md。
需要处理的安全发现
- 注册前安装出站防火墙(下文步骤 1 中的
host/firewall)。否则,Docker 桥接容器可以访问主机能够访问的任何内容:其局域网、自身服务,以及主机本身也是 tailnet 节点时的 tailnet。主机特定的发现记录在内部运维说明中。 - YOLO 以只读方式显示主机。这包括数据目录和证书,只要文件权限允许即可访问。Agent 以 uid
1000运行;仅 root 可读的文件对它们仍不可读,但tailscaled和 runner 在容器中以 root 身份运行。将任何敏感内容添加到[mounts] extra_denylist:被列入拒绝名单的子树不会挂载到/host。 - 在沙盒中,
UV_PROJECT_ENVIRONMENT=.venv-sandbox,因此uv不会使用容器路径重建项目的主机.venv。确保每个项目的.gitignore都包含.venv-sandbox(或.venv*)。
快速开始(主机)
cd /mnt/storage/projects/headmesh/sandbox
go build -o ~/.local/bin/headmesh-sandbox ./cmd/headmesh-sandbox
docker buildx bake -f image/docker-bake.hcl sandbox-local # headmesh-sandbox:dev
headmesh-sandbox init # /mnt/storage/headmesh/sandbox
$EDITOR /mnt/storage/headmesh/sandbox/config/sandbox.toml # image = "headmesh-sandbox:dev" until GHCR has a release
headmesh-sandbox scan-secrets # review, then --apply
headmesh-sandbox token create claude-code-mac > ~/.config/headmesh/sandbox-token
headmesh-sandbox render --diff
headmesh-sandbox up
headmesh-sandbox status
headmesh-sandbox doctor
其他命令:down [--yolo|--all]、recreate、logs [service|audit] [-f]、shell [--root]、attach(在 tmux 中观察 Agent)、backup、token list|revoke、acceptance run|verify|report|recreate,以及 agent run --provider openai。
密钥存放在 /mnt/storage/headmesh/sandbox/secrets/<name> 中,权限模式为 0600;空文件表示对应入口已关闭。密钥绝不会被挂载。CLI 通过环境变量将其传递给 docker compose,Compose 会将每个密钥复制到容器中,所有者为 root:root,权限为 0400(spike T-1)。
需要 Aaryn 执行的步骤
每个步骤都需要 sudo、生产环境变更或账户。目前这些步骤均未完成。
1. 主机(sudo)
- 安装出站防火墙和家庭备份单元,然后执行首次备份。具体命令见
host/README.md。运行headmesh-sandbox doctor检查结果;应显示egress firewall ✓和home backup ✓。 - 安装 restic(计划 §5.3)。休眠目标已屏蔽(
doctor会检查)。
2. Headscale 策略(生产 ACL 变更;维护窗口)
此步骤基于计划 §5.2 和 §6.6。将标签和授权规则添加到 Xcelsior 仓库中的 api/infra/headscale/policy.hujson。先运行 headscale policy check;策略中的 tests 必须通过。
"tagOwners": { "tag:sandbox": ["group:admin"], "tag:sandbox-yolo": ["group:admin"] },
"grants": [
// Regular: only the private services you list (keep sandbox.toml [mode.regular] reach in sync).
{ "src": ["tag:sandbox"], "dst": ["tower-server"], "ip": ["tcp:11434"] },
{ "src": ["tag:sandbox-yolo"], "dst": ["*"], "ip": ["*"] },
],
"ssh": [
{ "action": "accept", "src": ["xcelsior@"], "dst": ["tag:sandbox", "tag:sandbox-yolo"], "users": ["agent"] },
],
xcelsior@ 必须仅表示 Aaryn 自己的设备。提供商主机打上标签后(§5.2)即可满足这一点。
3. 注册节点
# On prod-hetzner: a single-use pre-auth key that carries the tag (Headscale 0.29.6).
sudo headscale preauthkeys create --user <owner> --tags tag:sandbox --expiration 1h
# On the ASUS: store it and recreate.
install -m 600 /dev/stdin /mnt/storage/headmesh/sandbox/secrets/ts_authkey # paste, then Ctrl-D
headmesh-sandbox recreate
headmesh-sandbox status # tailnet Running, node asus-sandbox, dns-check ok
- 密钥本身会设置标签。绝不传递
--advertise-tags,因为 Headscale 会拒绝与带标签的密钥一起使用该参数(spike T-8)。tailscale-up会改为检查已注册的标签是否与TS_TAGS一致。 - 仅在状态尚不存在时,
tailscale up才会使用密钥,因此之后重启时不会重新进行身份验证。带标签的节点不会过期。注册后清除密钥文件。 - YOLO 是一个独立节点(
asus-sandbox-yolo、tag:sandbox-yolo),使用自己的状态目录。首次执行up --yolo时,使用tag:sandbox-yolo密钥按相同方式注册。
4. 入口
通过 tailnet 使用 MCP(Claude Code、任何 MCP 客户端):
headmesh-sandbox token create claude-code-mac # prints hms_… once
claude mcp add --transport http sandbox http://asus-sandbox.mesh.xcelsior.ca:8780/mcp \
--header "Authorization: Bearer hms_…"
令牌具有作用域(sandbox:read|exec|write|admin),并且可以撤销:
headmesh-sandbox token revoke claude-code-mac 会在几秒内生效。
OAuth(RFC 9728 元数据已通过
/.well-known/oauth-protected-resource/mcp 提供)将在 M3 随 Headmesh
授权服务器一同推出。
Claude 可自托管 Runner(计划 §7.1):
- 在 claude.ai 管理设置中开启“Allow self-hosted environments”。
- 将环境密钥放入
secrets/claude_env_secret,并在secrets/github_token中填入细粒度 PAT。 - 设置
[entrypoints.claude_runner] enabled = true,然后运行headmesh-sandbox up。
Runner 以 root 身份运行。每个会话都会经过 claude-session,该程序会使用
setpriv 降权为 agent,因此会话无法读取密钥(验证 T-1)。
checkout 钩子会将 [repos] 中映射的仓库链接到共享工作副本的
main 分支。仍有一项实际检查待完成:确认 Runner 会在会话环境中设置什么。
Claude Remote Control(计划 §7.2):
- 设置
[entrypoints.claude_rc] enabled = true,然后运行headmesh-sandbox up。 - 运行
headmesh-sandbox shell,然后运行claude auth login(完整登录 claude.ai), 信任/home/agent,并运行一次claude remote-control,回答 “Enable Remote Control? y”。 - 运行
headmesh-sandbox recreate。
切勿设置 DISABLE_TELEMETRY、DO_NOT_TRACK、
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC 或 DISABLE_GROWTHBOOK:Remote
Control 在设置其中任何一项时都会拒绝启动(验证 T-2)。登录后还需检查:在没有 TTY 的情况下,它是否会自行重启。
通过 Tailscale SSH 使用 Codex(计划 §7.3):
- 这需要第 2 步中的
ssh规则。 - 在 Codex 桌面应用中,添加远程主机
[email protected]。 - 冒烟测试:
ssh [email protected] codex --version。 - Tailscale SSH 可在容器内运行(验证 T-3)。Codex 桌面应用本身尚未测试。
OpenAI Secure MCP Tunnel(计划 §7.4):
- 在 Platform 中创建一个 Tunnel,并创建一个仅具有 Read+Use 权限的运行时密钥。将密钥放入
secrets/openai_tunnel_key,并将 ID 填入[entrypoints.openai_tunnel] tunnel_id。设置enabled = true,然后运行up。 - 为 Tunnel 路由创建令牌:
headmesh-sandbox token create chatgpt-tunnel --route tunnel(exec ≤ 110 s, 响应 ≤ 8 MiB)。 - 在 ChatGPT 中:Plugins → Add custom MCP server → Connection: Tunnel,并使用 Bearer 身份验证和该令牌。
通过 Agents API 使用 Codex exec-server(计划 §7.6):
-
将应用密钥存放在沙箱外。CLI 以你的身份运行,因此请使用
secrets/openai_app_key(0600;绝不会挂载,并且已列入 YOLO 的/host拒绝列表)。如果你有读取权限,也可以使用/etc/headmesh/openai-app.key。 -
将环境密钥(Agents 选项卡,其他所有权限均设为 None)放入
secrets/openai_env_key。 -
运行:
headmesh-sandbox agent run --provider openai --repo xcelsior \ --prompt-file sandbox/tests/acceptance/prompt.md
执行器以 agent 身份在仓库中运行,使用 --linux-sandbox-pid-namespace inherit,并在容器内拥有完全访问权限。Codex 的 bubblewrap 沙箱无法在容器的
seccomp 和 AppArmor 配置文件下运行,因此容器就是安全边界(验证 T-16)。环境密钥通过
stdin 传给执行器,转录记录写入 logs/agent-runs/<session>.jsonl。
5. 验收(计划 §6.15)
headmesh-sandbox acceptance run --route mcp \
--url http://asus-sandbox.mesh.xcelsior.ca:8780/mcp --token-file ~/.config/headmesh/sandbox-token
ssh [email protected] headmesh-sandbox acceptance run --route ssh --phase 1
ssh [email protected] headmesh-sandbox acceptance run --route ssh --phase 2
# runner, exec-server, tunnel: the agent follows tests/acceptance/prompt.md
headmesh-sandbox acceptance recreate
headmesh-sandbox acceptance verify
headmesh-sandbox acceptance report --out ../docs/acceptance/$(date +%F).md
在 sandbox.toml [acceptance] 中设置目标:
- 必须将
allowed_target授予tag:sandbox。 - 必须阻止
denied_target。请仅在安装防火墙后运行此探测。
检查 4 只接受 MagicDNS 名称对应的 tailnet 地址;同名的公共 DNS 记录不计入。
6. YOLO
-
创建专用部署密钥,将其作为“headmesh-sandbox-yolo (asus)”添加到 GitHub, 并固定
known_hosts:ssh-keygen -t ed25519 -f /mnt/storage/headmesh/sandbox/secrets/github_ssh_key \ -C "headmesh-sandbox-yolo (asus)"此密钥只会传递给 YOLO 容器。
-
运行
headmesh-sandbox backup,然后运行headmesh-sandbox up --yolo --ttl 4h。
如果没有创建时间不超过 backup_max_age 的备份,CLI 会拒绝执行,并要求你确认正在监看。
7. 发布镜像
推送一个 sandbox-v0.1.0 标签。.github/workflows/sandbox-image.yml 会构建镜像,
运行 hadolint、container-structure-test 和 trivy,将镜像推送到
ghcr.io/headmesh-dev/sandbox,并使用 cosign 为其签名。然后设置
image = "ghcr.io/headmesh-dev/sandbox:0.1.0@sha256:…"。
故障排查
- 首先运行
headmesh-sandbox doctor;每个故障都会附带修复方法。 headmesh-sandbox logs sandboxd|tailscaled|tailscale-up|claude-runner|claude-rc|openai-tunnel|audit。- 要实时查看 Agent:
headmesh-sandbox attach。 - “IP 可用,名称无法解析”:
dns-check会说明修复方法(问题 3)。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。