fix(sandbox): 对齐 Linux 会话路径并修正宿主转发判定 - #1315
Conversation
|
你好!评审群已自动创建:pr 1315 评审群。但作者暂未拉入(不在自动拉群名单里)。请把你的 GitHub 账号和飞书信息补进名单文档,补好后后续复审会自动拉你进群。这是自动流程,谢谢! |
感谢这个 PR — 前两部分(canonical ✅ 已实测确认有效的部分1. canonical
2. 版本 13→14 与 worker 挂载 canonical 化 — 反变异 6 枪全部转红(M1 掉 env → 5 red;M2 去 canonical → 4 red;M3 还原 worker canonical → 2 red;M4 版本回 13 → 2 red;M5 去 🔴 阻断项:
|
| 退出码 | stderr | |
|---|---|---|
| master | 2 | botmux send refused: read-isolated owning data-root locator is missing or ambiguous ✅ fail-closed |
| 本 PR | 1 | 发送失败: failed to get tenant_access_token, code: 10003, msg: invalid param |
也就是说本 PR 上这条路径已经走到真实飞书 API 调用,唯一拦住它的是我那份假凭证被飞书服务端拒了 —— 不是任何授权闸。换成真凭证即为越权直发。
为什么 ppid 绑定在这里不成立
isTrustedVcMeetingHostRelayParent 本身没问题:它原本的落点(:8272 的 trustedRelayCtx)用于判定宿主上的 watcher 子进程,那里 process.ppid 与 session.pid 在同一 PID namespace,比较有意义。而新落点要区分的恰恰是「宿主 re-exec」与「沙箱内进程」——后者在独立 PID namespace 里,pid 数值可自选,跨 namespace 的 pid 比较不构成身份证明。
建议方向(供参考,最终以维护者判断为准)
想解决的问题(合法宿主转发被提前判成读隔离)是真实的,但判据需要一个沙箱内不可伪造的信号,例如:
- (a) watcher 侧显式标记:
startOutboxWatcherre-exec 时注入一个宿主生成的一次性 token(写在只有宿主可写、沙箱内不可读的位置),cmdSend校验该 token 而非 ppid; - (b) PID namespace 判据:直接比较
/proc/self/ns/pid与宿主 worker 的ns/pidinode —— 同 namespace 才认;沙箱内看到的是不同 inode,且无法伪造; - (c) 收窄触发面:既然
kernelReadIsolationDetected在 root 部署下结构性失效,也可考虑让 Linux 沙箱同样设置BOTMUX_READ_ISOLATED=1(当前仅 darwin),使沙箱内isolatedSendRequired恒为真,宿主转发另走 (a)/(b) 的正向证明。
顺带一提,test/sandbox-session-data-dir.test.ts 那条真 bwrap schedule 用例在 node_modules 是软链接的 worktree 里会因 tsx 解析失败而红;把 realpath(node_modules) 也加进 policy 后 5/5 全绿。属于我这侧环境形态,不是 PR 缺陷,仅提示其他 reviewer 别误判。
以上为自动评审的初步意见,可能有我判断不准的地方,最终以维护者审阅为准。前两部分我认为可以直接合;只有第三部分建议先调整。
|
已在 beff964 修正,阻断意见成立。 没有继续加强 PID 豁免,而是完整撤销了初版 补充了真实 bwrap 回归:在 root 用户命名空间内 fork 到夹具 worker PID,再重新设置宿主 relay 标志并调用真实 CLI。初版能够越过隔离校验;修订后在 locator 校验处拒绝。断言包含实际 UID 和 PID,禁网与已结束轮次作为额外保护,不请求真实飞书 API。 也采纳了共享依赖测试的提示,将 自测结果:
PR 描述已同步更新。最新上游 CI 仍为 |
更正与补充(复审后)复审指出我上一条评论里有一处判据错误,我已独立实测确认我错、复审对,在此更正;同时确认了一个更严重的、本 PR 之前就存在的问题,它改变了第 3 部分的定性。 ❌ 更正:「root 才可利用」是错的 —— 与 uid 无关我上一条写「非 root 下 mode-000 掩码给 用真实
(附带说明: 🔴 更严重:最小绕过在本 PR 之前就已成立,不需要 ppid 伪造沙箱内 agent 只要 unset
对照组在两棵树上都正确拒绝,证明探针能区分、不是恒通的哑弹。 根因: (我上一条评论的探针没有让沙箱子进程真正继承 对本 PR 的结论(不变,但定性调整)
以上为自动评审意见,最终以维护者审阅为准。 |
复审已收敛,附一份「我们踩过的坑」给后续 reviewer两位评审对本 PR 的结论一致(见上两条评论)。这里单独记录验证方法上的三个坑——它们都曾让读数看起来"正好证明了结论",值得后来人避开。 坑 1:对照组是哑弹,两组其实是同一个输入我第一版探针只在内层 spawn 设 抓到它靠的是在沙箱内插桩打印闸的输入: 修正后对照组在两棵树上都正确 exit 2。判据:任何"X 是必要条件"的结论,先证对照组真的会被拒,否则那个绿是哑弹。 坑 2:基线对照用了会漂的 refreview 期间 修正为钉死 SHA: 坑 3:拿自己手搭的形态代替真实 compiler 输出我一开始判「只有 root 部署可利用」,依据是我手搭的 mode-000 掩码下非 root 会得到 改用不依赖 uid 的结构判据(真实 并用真实 policy argv 直接测两种 uid: errno 与 uid 无关,因为路径是"不存在"而不是"被掩码"。 (顺带更正我自己在讨论中说过的一句:我曾说"沙箱内不存在 uid 65534、 判据:判"某路径在沙箱内为何不可读",先看真实编译出的 argv 里它到底被 bind 了、被 mask 了、还是压根没出现 —— 这比造权限形态可靠,且不依赖 uid。 以上为自动评审的过程记录,最终以维护者审阅为准。 |
对新 head
|
| 声明 | 我的验证 | 结果 |
|---|---|---|
src/cli.ts 相对 PR 基线零改动 |
diff <(git show 0ad77ffcc:src/cli.ts) <(git show beff96435:src/cli.ts) |
逐字节相同 ✅ |
| 初版能越过隔离校验、修订后被拒 | 我原本的 ppid 伪造探针(channel 继承 + fork 到目标 pid,实测 FORGE_PPID_OK target=4321) |
初版 exit 1(到真实飞书 API)→ 新 head exit 2 refused: locator is missing or ambiguous ✅ |
| 对照组仍正确拒绝 | channel 保留、不做任何伪造 | exit 2 ✅(探针有区分度) |
已采纳 realpath(node_modules) |
软链接 node_modules 的 worktree 下重跑 |
4 文件 130/130 全绿 ✅(我首审时那条真 bwrap 用例正是在这里红的) |
✅ 新修法的机制我做了正反两向验证
反变异:删掉 buildRelayHostEnv 里那行 delete env.BOTMUX_ORIGIN_CHANNEL_ID → 2 个用例转红(materializes prepared card bytes…、rejects a trusted host re-exec when its authorized Codex App ledger was already settled)⟹ 这行是承重的,不是惰性编辑。
正向对照(验证它真修了 #1313 的后半个问题):模拟 watcher 真实行为(buildRelayHostEnv(paneEnv) + BOTMUX_HOST_RELAY_AUTHORIZED=1)跑真实 CLI —
基线 0ad77ffcc : exit 2 refused: read-isolated owning data-root locator is missing or ambiguous ← issue 报的误判
新 head : exit 1 (通过隔离分类,进入正常发送路径)✅
最小化检查:buildRelayHostEnv 只剥离 pane 专属的两项,会话身份与授权来源都保留 ——
BOTMUX_SEND_RELAY STRIPPED
BOTMUX_ORIGIN_CHANNEL_ID STRIPPED ← 新增
BOTMUX_SESSION_ID kept
BOTMUX_LARK_APP_ID kept
SESSION_DATA_DIR kept
durable origin 仍只来自 authorize(sandbox.ts:1387 的 BOTMUX_HOST_RELAY_AUTHORIZED 与 trustedOrigin 赋值未动),没有新增 env 豁免。
🗑 我此前评论中随之作废的部分
- 我在
#issuecomment-5580100491与#issuecomment-5581227894里给的三个修法方向(宿主一次性 token / 比ns/pidinode / Linux 也设BOTMUX_READ_ISOLATED)已不适用:作者没有加强 PID 豁免,而是整段撤销,把问题收敛到真正产生错误输入的边界(watcher 的环境副本)——这比我提的任何一条都更小、更不引入新信任面。 - 那两条评论里关于
parentBoundHostRelay/ ppid 判据的所有分析,只适用于旧 head2c6d04fd4,对当前 head 不再成立。 #issuecomment-5581290457(方法论三坑)里坑 1、坑 2 仍然有效(对照组哑弹、基线对照要钉 SHA);坑 3 关于"我误判 root 是分水岭"的更正也仍然成立。
⚠️ 仍然存在、但不属于本 PR 的一项
沙箱内 agent 自行 unset BOTMUX_ORIGIN_CHANNEL_ID(而非依赖 watcher 传递)仍可使隔离分类失效 —— 我在钉死的基线 0ad77ffcc 上复现过,本 PR 之前就成立,与本 PR 无关。新 head 同样保留该行为(exit 1),符合作者"不扩大范围"的取舍。建议单独开高优 issue:根因是 Linux 下内核探针在 bwrap 的 --tmpfs / 新根中返回 ENOENT 而非 sandbox_denied(实测 uid 0 与 uid 65534 皆然),叠加 BOTMUX_READ_ISOLATED 为 darwin-only(worker.ts:13346),三条 pre-empt 臂在 Linux 全断。
结论
我这边阻断意见解除,四项改动(canonical SESSION_DATA_DIR / Linux 挂载对齐 / 版本 13→14 冷启动 / watcher 边界清理 channel)均实测有效,反变异承重,无新增阻断项。
CI 仍是 action_required(fork 工作流待维护者批准),本地 bun run build rc=0、相关 4 文件 130/130 绿。以上为自动评审意见,最终以维护者审阅为准;未经维护者确认不合码。
补一项:与已漂移的主干做真实合并态验证(无冲突,可合)复核期间 合并树对账: 合并态实测:
⟹ #1266 对 (顺带更正我上一条评论里一处笔误:文中 以上为自动评审意见,最终以维护者审阅为准;未经维护者确认不合码。 |
主干第三次漂移后重验:仍无冲突,结论不变上一条评论发出后 #1308 与本 PR 零文件交集(它改 真实合并态(HEAD
⟹ 连续两次主干前推(#1266、#1308)都没有削弱本 PR 的防护,也没有破坏它修复的路径。 结论不变:四项改动(canonical CI 仍是 |
deepcoldy
left a comment
There was a problem hiding this comment.
CI 已全绿(build / test 及 3 个 shard / bun-test / bun-binary / bun-binary-darwin / bun-binary-musl 共 9 项 success)。双审复核:四项修复实测有效、反变异承重,对最新主干无冲突、合并态 build + 130/130 + 三探针全绿。
Closes #1313
问题
Linux bwrap 挂载使用 canonical 路径,但沙箱内继承的
SESSION_DATA_DIR仍可能包含宿主 HOME 的软链接别名。新根目录没有该别名,因此会话数据库虽已挂载,history/schedule add仍无法定位。另一处独立问题是 outbox 的宿主重执行:它继承了属于 pane 的 origin channel,导致宿主 CLI 被误分类为读隔离调用,提前报缺少 data-root locator。
修改
prepareDirectSandbox的返回环境和 bwrap--setenv中固定 canonicalSESSION_DATA_DIR。buildRelayHostEnv中清理 pane 专属的BOTMUX_ORIGIN_CHANNEL_ID,与清理BOTMUX_SEND_RELAY处于同一宿主重执行边界。只修改宿主子进程的环境副本,不修改沙箱 pane 的环境或授权文件。node_modules的实际路径挂载。根据 review,已完整撤销初版
parentBoundHostRelay隔离豁免:当前 PR 的src/cli.ts相对基线没有改动。PID 数值不再被新增用于跨 namespace 识别宿主。宿主 watcher 仍走原有 capability 授权,后续精确轮次、VC 输出策略和 ledger 检查保持不变。没有增加生产目录授权、配置字段或持久化 token 协议,也没有修改发布版本号。
影响面
生产改动涉及 Linux 完整文件沙箱的数据目录、managed-origin 挂载以及 outbox 的宿主重执行(send / dispatch)。新建与恢复会话共用相同 watcher 环境清理逻辑;其他平台的直接发送和隔离鉴权实现不变。测试覆盖 Codex、Claude 的共用隔离逻辑及持久 pane 版本检查。
验证
最新修订的实际结果:
node_modules软链接指向外部实际依赖目录,真实 bwrap 文件中的 6 项全部通过。bun run build:通过。bun run verify:binary:Linux x64 独立可执行文件的全部冒烟检查通过。关键回归用例:
测试均使用临时数据,不依赖真实飞书凭据,不发送真实消息;未部署或重启运行中的机器人。