Docker Sandboxes 的隔离例外:共享 Skills 与宿主机 MCP

Docker Sandboxes 的隔离例外:共享 Skills 与宿主机 MCP

Docker Sandboxes 实测系列(共 5 篇)

  1. Docker Sandboxes 上手:从安装到第一次跑 Agent
  2. Docker Sandboxes 工作区怎么接:Direct、Clone 和它们的边界
  3. Docker Sandboxes 的隔离例外:共享 Skills 与宿主机 MCP(本篇
  4. Docker Sandboxes 的凭证:host 代理转发,不进 microVM
  5. Docker Sandboxes 的网络策略:balanced 挡住了什么,怎么放行一条例外

TL;DR

官方给 sandbox 划了五层隔离:Hypervisor、网络、Docker Engine、workspace、凭证。前两篇讲的读写工作区、宿主机 Docker 碰不到,都在这五层里。

但还有两处不算隔离穿透,是设计上就主动开放的边界例外:共享 Skills store,Claude 默认以读写方式挂载,多个 sandbox 因此共用同一份可被互相改写的技能;宿主机侧的 MCP Gateway,本地 stdio 类型的 MCP Server 实际跑在你的 Mac 上,不在 microVM 里——Agent 通过它调用的能力,隔离边界覆盖不到。

官方隔离之外的两个口子

前两篇分别讲了 怎么装、怎么跑通,以及 工作区怎么挂。五层里,Hypervisor、网络、Docker Engine 这三层挡的是 sandbox 和宿主机之间默认不通的部分——各自独立的 kernel、网络出站、Docker Engine,前两篇都验证过;凭证隔离管的是 sbx secret 存的 Key 只在宿主机侧代理注入,sandbox 内的进程拿不到明文。

workspace 隔离是个例外:它在官方的五层名单里,但默认(Direct 模式)恰恰是没隔离的——上一篇整篇讲的就是这件事,Agent 在工作区里的权限和你本人一样,没有额外的约束。

这五层都是“默认关”(workspace 除外)。但 Docker 自己在文档里承认,还有两个接口是设计上默认就要打通的,不算漏洞:共享 Skills 和 MCP Gateway。

共享 Skills:五个 Agent 各挂各的路径,同一份文件

sbx skills import

sbx skills import --help 里写的支持源目录,按扫描顺序是 ~/.agents/skills~/.claude/skills~/.copilot/skills~/.cursor/skills~/.factory/skills——同时存在时前面的优先级更高。这条命令把里面的 Skill 拷进一份持久化存储,路径在 ~/Library/Application Support/com.docker.sandboxes/sandboxes/agent-skills。我这台机器上导进去 11 个,都是平时在用的 skill。

关键是导进去之后谁能挂、挂在哪。sbx skills import --help 里有一句“Supported by Claude, Codex, Copilot, Cursor, and Droid agents”,我把 sbx run 支持的 10 个 Agent 逐个起了一遍 sandbox,用 mount 核对:

Agent挂载路径结果
claude/home/agent/.claude/skills挂,rw
codex/home/agent/.agents/skills挂,rw
copilot/home/agent/.copilot/skills挂,rw
cursor/home/agent/.cursor/skills挂,rw
droid/home/agent/.factory/skills挂,rw
opencode不挂
shell不挂
gemini不挂
kiro不挂

五个受支持的 Agent 各自挂在自己原生的配置路径上,不是统一挂在 /home/agent/.claude/skillsopencodeshellgeminikiro 完整挂载列表里除了 workspace 本身,没有任何和 skills 相关的条目:

$ sbx exec sbx-skills-opencode bash -c "ls /home/agent/.claude/skills; mount | grep -i skill"
ls: cannot access '/home/agent/.claude/skills': No such file or directory

这四个“不挂”的 Agent 里,opencodegemini 其实自己有能力读这份共享目录——只是 Docker 没接。OpenCode 官方文档写了它会扫描六个位置,其中包括 ~/.claude/skills/~/.agents/skills/opencode.ai/docs/skills);Gemini CLI 官方文档更直接,~/.agents/skills/ 是它专门留的跨工具互通 alias,原话“provides an interoperable path for managing agent-specific expertise that remains compatible across different AI tools”,同一层级里优先级还比 ~/.gemini/skills/ 更高(geminicli.com/docs/cli/skills)。而 ~/.agents/skills 正是 Docker 已经在挂给 codex 用的那个路径。

我把 host 上 OpenCode 用的 skill 目录复制进 ~/.agents/skills(Docker 支持的五个源目录之一),跑 sbx skills import 确认它被当成合法源扫描并导入了持久化 store:

$ sbx skills import
Overwrite "pdf"? [y/N]:
Warning: skipping skill "pdf" from /Users/addo/.claude/skills: already imported from an earlier source

.claude/skills 里同名的 pdf 被跳过,说明 .agents/skills(字母序更靠前)先被扫描、先导入——store 里确实有这份内容了。但重新起一个干净的 opencode sandbox,/home/agent/.agents 这个目录压根不存在,不是空目录:

$ sbx exec sbx-opencode-test bash -c "find /home/agent -maxdepth 3 -iname '.agents'"
(无输出)

官方 FAQ 把默认行为写得很直接(docs.docker.com/ai/sandboxes/faq):sandbox 默认完全不导入宿主机的用户级 agent 配置——~/.claude 之类目录下的 hooks、settings 都留在宿主机,只有 workspace 里的项目级配置会带进去。“Shared agent skills are the exception”:共享 Skills 是官方明确开的一个例外口子。但这句话本身没有说“只对这五个开”——docs.docker.com/ai/sandboxes/workflows 页面里那张挂载表(链接)只列了 Claude、Codex、Copilot、Cursor、Droid 五行,没有反过来写一句“OpenCode/Gemini/Kiro 不支持”——是枚举没提,不是显式排除。真正把“OpenCode 不在这个例外范围里”坐实的,是前面那组实测:~/.agents/skills 确认能被 sbx skills import 当合法源导入,但干净的 opencode sandbox 里连 /home/agent/.agents 这个目录都不存在。

文档能提供的只是枚举证据,sbx 本体的代码不公开——docker/sbx-releases 仓库里只有 LICENSE(Proprietary)、README.mdSECURITY.md,没有源码。能拿到的最接近源码的东西,是本机装的 v0.38.0 二进制——Go 编译、没有 strip,符号表和字符串常量原样留在里面,strings 就能读:

$ strings /opt/homebrew/Caskroom/sbx/0.38.0/bin/sbx | grep -oE '\.[a-z]+/skills' | sort -u
.agents/skills
.claude/skills
.copilot/skills
.cursor/skills
.factory/skills

负责这块的符号是 github.com/docker/sandboxes/sandboxlib/agent.SharedSkillsMountSpecs,全二进制里跟“skills 挂载路径”相关的字符串常量只有这五条——.gemini/skills.kiro/skills.config/opencode/skills 一次都搜不到,不是文档没写全,是这条代码分支里就没有过对应的字符串常量。

同一个二进制里还有一个容易和它混淆的证据:sandboxlib/agentkits 这个包里,Load("opencode")Load("gemini")Load("kiro") 都作为受支持的 agent kit 名字存在(和 claude/codex/copilot/cursor/droid/shell 并列)。这说明 OpenCode/Gemini/Kiro 本身是 sbx 认得的 agent——能 sbx run opencode 正常起 sandbox——只是“共享 Skills”这一条例外机制的挂载表单独把它们排除在外,两件事互不影响。至于为什么共享 Skills 只做这五个、不顺手把 agent kit 支持的另外三个也接上,官方没有解释,这没有更多依据,只能确认代码现状。

挂载路径不同,但背后是同一份宿主机目录。起一个 Claude sandbox 和一个 Codex sandbox,在 Claude 这边写一个文件,Codex 那边立刻能读到:

$ sbx exec sbx-skills-claude bash -c "echo hello-from-claude > /home/agent/.claude/skills/writing/marker.txt"

$ sbx exec sbx-skills-codex bash -c "cat /home/agent/.agents/skills/writing/marker.txt"
hello-from-claude

这意味着:

  • 这不是“多个 Claude sandbox 共用一份 Skill”这么窄的事,是 Claude、Codex、Copilot、Cursor、Droid 这五种 Agent、任意组合的 sandbox,只要都不关共享 Skills,就共用同一份可读写的文件
  • Sandbox A 里不管跑的是哪一种受支持 Agent,改了共享 Skill 的内容,Sandbox B 以后加载同一个 Skill 名字,读到的就是被 A 改过的版本
  • 这些 sandbox 因而进了同一个信任边界——不是 microVM 之间的隔离失效,是这条挂载本来就在隔离之外

sbx create --help 没有列出这个开关,但二进制里确实支持 --no-share-skills,五种受支持 Agent 都能用:

sbx run --no-share-skills claude

如果测的是安全边界,用这五种 Agent 之一就该带上这个参数;用 OpenCode、shell、Gemini CLI、Kiro CLI,这条边界默认就不成立,不用为了“关掉共享 Skills”专门起一个 sandbox。

Host-side MCP Gateway:本地 stdio Server 跑在你的 Mac 上

Docker 的 MCP Gateway 在宿主机侧,不在 microVM 里。注册一个 MCP Server 时,它可能是远程 HTTP endpoint,也可能是宿主机上的本地进程:

sbx mcp add probe --command python3 --args /tmp/sbx-mcp-probe.py

我拿一个只在启动时把自己的 pidcwdhostname 写进日志文件的假 MCP Server 测了一遍:

$ sbx mcp load probe --sandbox my-sandbox
ERROR: add "probe" to sandbox "my-sandbox": add MCP gateway server: request failed:
  502 Bad Gateway: add local server to gateway: connect to probe: calling "initialize": EOF

$ cat /tmp/sbx-mcp-probe.log
pid=58717 cwd=/private/tmp hostname=my-macbook.local

load 报 502,是因为我的假脚本不会回应 MCP 的 initialize 握手,这个失败在预期之内。但日志已经落在宿主机上,hostname 是我这台 Mac 的名字,不是 microVM 里的机器名——进程已经在宿主机上真实起来过sbx mcp load 只是随后没能跟它建立起 MCP 会话,不代表进程没跑。

结论跟 Skills 那条一样:本地 stdio MCP Server 是宿主机侧的受信任集成,不是 sandbox 内部工具。它跑在你的开发机上,能碰到的东西和你本人权限一致;如果这个 MCP Server 本身能操作文件、Shell、Git 或云资源,Agent 通过它调用的能力就已经绕过 microVM 边界,跟共享 Skills 一样,这不算隔离被攻破,是这条通路本来就在隔离范围之外。

小结

Docker Sandboxes 想解决的问题很具体:Agent 要装依赖、跑测试、起容器,得有权限;给它这些权限的地方,最好是一个能承受它犯错、可以随手删掉的环境。microVM 做到了这一点——五层隔离挡住了 workspace 之外几乎所有东西。

但这道边界不是无缝的。工作区本身、Claude 默认挂的共享 Skills、宿主机上跑的本地 MCP,都是设计上主动开放的例外:共享 Skills 默认开着,本地 MCP 是你自己接的。用之前想清楚要不要关共享 Skills、要不要接本地 MCP——留几条例外,隔离就只覆盖到剩下的部分。

还有一个我没测完的口子:SSH_AUTH_SOCK 会转发进 sandbox,Agent 能拿宿主机的 key 签名,但读不到私钥文件本身。这条留到下一篇细说。

乱世浮生微信公众号二维码

(转载本站文章请注明作者和出处乱世浮生,请勿用于任何商业用途)