fix(isolation): 补齐 credential-only 会话的发送通道 - #1321
Open
deepcoldy wants to merge 1 commit into
Open
Conversation
设备已注册但 Bot 未开完整文件沙盒时(Linux credential-only bwrap),
被约束的 `botmux send` 在解析 argv 之前就被拒,连 `botmux send --help`
也一起挂,agent 完全无法回复用户:
botmux send refused: read-isolated owning data-root locator is missing or ambiguous
根因不是漏写一个文件,而是这条形态下 `cmdSend` 的三道闸**结构性**都过不去
(均在 bwrap 0.8.0 上实测):
1. `--unshare-pid` 让子进程 pid=2/ppid=1、`/proc` 只剩 2 个 pid,
`findAncestorSessionContext` 的进程树 marker 走查**永远**解析不出来
(marker 目录本身可读,只是宿主 pid 不在该命名空间)。于是 cmdSend 走
isolation marker 那条臂,转而要求 data-root locator。
2. 该 locator 只在 `sandboxRequested && darwin` 分支写,这条路从没写过。
3. 补写也无效:它的文件名是 `.dashboard-secret.origin-root-<hash>.json`,
正好被本形态自己的 `.dashboard-secret.*` mask 枚举命中,被 ro-bind 到
/dev/null,读回来是 0 字节字符设备(EACCES);即便绕过,下一道闸要求
data-root probe 读出 EPERM,而 `--tmpfs` 盖住 `read-isolation/` 后是 ENOENT。
而「在 pane 内部放个文件当证明」整体不成立:带 `--unshare-user` 的子进程可以
`mount --bind` 把可写目录盖到宿主 ro-bind 之上(实测成功),所以「某路径只读」
或「某文件存在」在 pane 内都不是信任根。
因此改为给这条形态接上完整沙盒同款的**宿主侧 relay 通道**:provision per-session
outbox(读写 bind)、注入 `BOTMUX_SEND_RELAY`、启动宿主 outbox watcher 由 daemon
侧做权威 origin 判定。凭证依旧不进 pane,安全边界只增不减。
影响面:仅 Linux credential-only(设备已注册 + Bot 未开完整 sandbox)。完整沙盒
路径逐字未改(gate mode `covered`,不进本分支);remote backend 走 remote-bypass。
macOS credential-only 已核实不在范围内:其 deny 集合不含 `.dashboard-secret*` 与
`.botmux-cli-pids`,且 Seatbelt 不做 pid namespace,marker 走查仍成立。
隔离 pane marker 版本 13 → 14:本改动新增 spawn 期 mount + 启动 env,旧 pane
warm reattach 拿不到,必须冷启一次。
测试:新增 test/credential-only-send-authority.test.ts —— 全仓此前**没有任何**
测试真的 spawn 一个 credential-only 子进程再跑 `botmux send`(既有 read-isolation
/ managed-origin 单测只覆盖 helper 函数),这正是缺陷能全绿上线的原因。
Co-Authored-By: Claude Code <noreply@anthropic.com>
Owner
Author
CI 复核:13/13 全绿首轮
另附澄清: 最终状态:13 个 check 全 pass( |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题
设备已注册但 Bot 未开完整文件沙盒时(Linux credential-only bwrap),被约束的
botmux send在解析 argv 之前就被拒,连botmux send --help也一起挂 —— agent 完全无法回复用户:根因:不是漏写一个文件,而是三道闸结构性都过不去
全部在本机 bwrap 0.8.0 上实测(非阅读推断):
--unshare-pid让 marker 走查永远不可能成功。子进程pid=2/ppid=1、/proc只剩 2 个 pid ⟹findAncestorSessionContext解析不出宿主 session。实测确认 marker 目录本身可读、文件就在那儿,纯粹是宿主 pid 不在该命名空间:locator 从没写过 —— 只在
if (sandboxRequested) → if (darwin)(worker.ts)里写。补写也无效,只是把失败推到下一道闸(这条是关键,也是我实测推翻的「显而易见的修法」):
.dashboard-secret.origin-root-<hash>.json,正好被本形态自己的.dashboard-secret.*mask 枚举命中,被--ro-bind /dev/null掉。子进程里读到的是 0 字节字符设备:--tmpfs盖住read-isolation/后读出 ENOENT ⟹missing_or_unsafe:为什么修法是「宿主侧 relay」而不是「pane 内补文件证明」
pane 内的文件类证明整体不成立。子进程带
--unshare-user,实测可以mount --bind把一个可写目录盖到宿主 ro-bind 之上:所以「这个路径只读」/「这个文件存在」在 pane 内都不是信任根(我一度考虑用 EROFS 当判据,实测证明可伪造后放弃)。唯一可靠的是宿主侧通道 —— 也就是完整沙盒已经在用的 relay:outbox + daemon 侧 watcher 做权威判定。
本 PR 把这条通道接给 credential-only:provision per-session outbox(读写 bind)、注入
BOTMUX_SEND_RELAY、启动宿主 outbox watcher(session-id强制绑定)。凭证依旧不进 pane,安全边界只增不减。影响面评估
covered,不进本分支device-auth/platform.json/device.json/device-enroll-pending/.device-credential-isolation,不含.dashboard-secret*、不含.botmux-cli-pids;且 Seatbelt 不做 pid namespace ⟹ marker 走查仍成立。没有 mac 机器实测,故不擅自纳入修复范围remote-bypassadapters/cli/共用基类测试
新增
test/credential-only-send-authority.test.ts。为什么此前 102/102 全绿:既有 read-isolation / managed-origin 单测只覆盖 locator/capability 的 helper 函数,全仓没有任何测试真的 spawn 一个 credential-only 子进程再跑
botmux send(buildCredentialOnlySandboxArgs只在sandbox.test.ts出现,不接 send 路径)。这正是缺陷能全绿上线的原因,所以新测试真的起 bwrap 子进程跑真实构建产物。反向验证:11 枪变异全部转红
不只跑绿,逐条确认测试真的承重(每枪改完复原、无残留):
--bindBOTMUX_SEND_RELAYcleanup改成 no-opstartOutboxWatcher/ fail-closed 分支 /writableOutbox入参 / cleanup 归属 / 第二次publishSandboxRelayCapabilityworker.ts。补了 worker 侧 source-text 断言(沿用read-isolation.test.ts既有写法)后 6/6 转红。另外我也如实标注:生产形态下没有任何 mask 是 outbox 的祖先,所以那个显式--bind是防御性的、并非承重(承重的是 relay env + watcher),已在代码注释中写明真实作用。全量单测:与基线逐文件相同
全量跑有 25 文件 / 56 用例红。做了 load-matched 对照(保留新测试文件、只把
src/回退到本分支基线0ad77ffcc,同一子集同样并发):失败集合逐文件相同(
plugin-mcp-sandbox/plugin-registry-sandbox-read/mojo-launcher-env-quarantine/session-store-sqlite-*,均为本机环境相关:bwrap/root DAC/Bun-vs-Node sidecar),且都不是本 PR 触碰的文件 ⟹ 本改动新增 0 失败。全量跑里另外 20 个文件只在满载并发时红、单跑即绿。待确认
需要在真机飞书里手动验证一次(本机 daemon 未启用设备凭证隔离,
~/.botmux/device-auth不存在,无法在 live daemon 上自然触发这条形态)。要我switch:here && daemon:restart部署本 checkout 实测的话说一声 —— 那会让所有 bot 都跑本 build,所以先不擅自动。🤖 Generated with Claude Code