Skip to content

fix(sandbox): 对齐 Linux 会话路径并修正宿主转发判定 - #1315

Merged
deepcoldy merged 2 commits into
deepcoldy:masterfrom
Rememorio:fix/linux-sandbox-session-routing
Sep 8, 2026
Merged

fix(sandbox): 对齐 Linux 会话路径并修正宿主转发判定#1315
deepcoldy merged 2 commits into
deepcoldy:masterfrom
Rememorio:fix/linux-sandbox-session-routing

Conversation

@Rememorio

@Rememorio Rememorio commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Closes #1313

问题

Linux bwrap 挂载使用 canonical 路径,但沙箱内继承的 SESSION_DATA_DIR 仍可能包含宿主 HOME 的软链接别名。新根目录没有该别名,因此会话数据库虽已挂载,history / schedule add 仍无法定位。

另一处独立问题是 outbox 的宿主重执行:它继承了属于 pane 的 origin channel,导致宿主 CLI 被误分类为读隔离调用,提前报缺少 data-root locator。

修改

  • prepareDirectSandbox 的返回环境和 bwrap --setenv 中固定 canonical SESSION_DATA_DIR
  • 对齐 Linux managed-origin capability / attestation 的只读挂载路径。
  • 隔离启动约定版本从 13 升到 14,避免重连继续使用旧环境和挂载。
  • buildRelayHostEnv 中清理 pane 专属的 BOTMUX_ORIGIN_CHANNEL_ID,与清理 BOTMUX_SEND_RELAY 处于同一宿主重执行边界。只修改宿主子进程的环境副本,不修改沙箱 pane 的环境或授权文件。
  • 为真实 bwrap 测试补充共享 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 / Vitest:17 个相关测试文件,267 项通过;6 项 macOS Seatbelt 真实运行测试在 Linux 按平台跳过。
  • Bun 1.4.2:4 个相关文件逐进程运行,130 项通过;每个进程在启动前隔离 HOME 和临时目录。
  • 共享依赖的临时 worktree:node_modules 软链接指向外部实际依赖目录,真实 bwrap 文件中的 6 项全部通过。
  • bun run build:通过。
  • bun run verify:binary:Linux x64 独立可执行文件的全部冒烟检查通过。
  • 真实 outbox watcher → 构建后的 CLI,以及独立可执行文件的宿主重执行:均到达预期的精确已结束轮次拒绝门,未误报缺少 locator。

关键回归用例:

  • 软链接 HOME、数据路径别名、普通路径的环境与 argv 一致性。
  • 真实 bwrap 内当前会话及自身授权目录可读,兄弟 bot 的会话和授权目录不可读。
  • 真实 CLI 在沙箱内不指定 chat-id 创建定时任务,校验目标聊天、bot、所有者、工作目录及落盘位置。
  • root 用户命名空间中,通过 fork 命中夹具 worker PID 后伪造宿主 relay 标志:初版越过隔离校验,修订后在 locator 校验处拒绝。测试同时断言实际 UID/PID,且使用禁网沙箱和已结束轮次避免外部副作用。
  • 合法宿主环境清理后仍执行精确 ledger 校验;匹配数值 PID 的 pane、错误父进程和显式隔离调用均不能借宿主标志取得新增豁免。
  • 旧 pane 版本不能直接重连,worker 的授权目录生成点使用 canonical 路径。

测试均使用临时数据,不依赖真实飞书凭据,不发送真实消息;未部署或重启运行中的机器人。

@deepcoldy

Copy link
Copy Markdown
Owner

你好!评审群已自动创建:pr 1315 评审群。但作者暂未拉入(不在自动拉群名单里)。请把你的 GitHub 账号和飞书信息补进名单文档,补好后后续复审会自动拉你进群。这是自动流程,谢谢!

@deepcoldy

deepcoldy commented Sep 8, 2026

Copy link
Copy Markdown
Owner

⚠️ 本条已过时(针对旧 head 2c6d04fd4)。 作者已在 beff96435 整段撤销 parentBoundHostRelay,本条的阻断意见已解除,文中给出的三个修法方向也不再适用。最新复核见 #issuecomment-5581390762

感谢这个 PR — 前两部分(canonical SESSION_DATA_DIR 与 Linux 挂载对齐)我实测确认是正确且必要的修复。但第三部分(cmdSend 的宿主转发放宽)我实测出一个阻断级问题,建议合入前先处理。

✅ 已实测确认有效的部分

1. canonical SESSION_DATA_DIRsandbox.ts — 用软链接 HOME 布局做 A/B:

master 本 PR
子进程内 SESSION_DATA_DIR <alias>/.botmux/data(未设置,继承宿主字面值) <home>/.botmux/data
真 bwrap 内读会话库 READ_FAILED OWN-DB

2. 版本 13→14 与 worker 挂载 canonical 化 — 反变异 6 枪全部转红(M1 掉 env → 5 red;M2 去 canonical → 4 red;M3 还原 worker canonical → 2 red;M4 版本回 13 → 2 red;M5 去 !parentBoundHostRelay → 2 red;M6 去 ppid 绑定 → 2 red)。新增用例都是承重的,没有惰性断言。

🔴 阻断项:parentBoundHostRelay 在 Linux 上可被沙箱内的 agent 伪造

放宽后的判定链:

parentBoundHostRelay = !relayDir && !kernelReadIsolationDetected
  && BOTMUX_HOST_RELAY_AUTHORIZED==='1' && inheritedSessionId
  && session.pid === process.ppid

这四项在 Linux 沙箱内全部由子进程可控,我逐项实测:

  1. !relayDir — agent 自己 unset BOTMUX_SEND_RELAY 即可。

  2. BOTMUX_HOST_RELAY_AUTHORIZED==='1' — bwrap --unsetenv 只清启动时的值;子进程能给自己的孙进程重新设回:实测 as_spawned=[<unset>]child_reset=[1]

  3. !kernelReadIsolationDetected — 这是关键。两条探针只在 EACCES/EPERM 时判 sandbox_denied以 root 运行时,mode-000 掩码给出的是 ENOENT(目录掩码)或直接打开成功(文件掩码),都不是 sandbox_denied

    root  + dir-mask  -> ENOENT  => missing_or_unsafe(非 sandbox_denied)
    root  + file-mask -> OPENED  => host_accessible
    nobody + 任一掩码 -> EACCES  => sandbox_denied(此时闸被 kernel 臂抢先,问题不成立)
    

    而 Linux 上 BOTMUX_READ_ISOLATED 也不会被设置(willReadIsolateprocess.platform === 'darwin' && sandboxRequested,见 worker.ts:13346),所以第三条 pre-empt 臂在 Linux 同样不生效。

  4. session.pid === process.ppid — bwrap 用 --unshare-pid全新 PID namespace,命名空间内 pid 从 1 开始递增,沙箱内进程可以空转 fork 直到自己恰好持有任意目标 pid,其子进程的 ppid 就等于该值。实测:

    目标 4321   → forks=4318   ppid_seen_by_child=4321
    目标 8151   → 3.7s
    目标 100000 → 45s
    

    本机 live worker pid 集中在 8000 附近,即几秒即可命中

决定性 A/B(真 bwrap + 真 cli.ts send + 真 bots.json

沙箱内 agent 完成上述三步后执行真实 botmux send

退出码 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 本身没问题:它原本的落点(:8272trustedRelayCtx)用于判定宿主上的 watcher 子进程,那里 process.ppidsession.pid同一 PID namespace,比较有意义。而新落点要区分的恰恰是「宿主 re-exec」与「沙箱内进程」——后者在独立 PID namespace 里,pid 数值可自选,跨 namespace 的 pid 比较不构成身份证明。

建议方向(供参考,最终以维护者判断为准)

想解决的问题(合法宿主转发被提前判成读隔离)是真实的,但判据需要一个沙箱内不可伪造的信号,例如:

  • (a) watcher 侧显式标记startOutboxWatcher re-exec 时注入一个宿主生成的一次性 token(写在只有宿主可写、沙箱内不可读的位置),cmdSend 校验该 token 而非 ppid;
  • (b) PID namespace 判据:直接比较 /proc/self/ns/pid 与宿主 worker 的 ns/pid inode —— 同 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 别误判。

以上为自动评审的初步意见,可能有我判断不准的地方,最终以维护者审阅为准。前两部分我认为可以直接合;只有第三部分建议先调整。

@Rememorio

Copy link
Copy Markdown
Contributor Author

已在 beff964 修正,阻断意见成立。

没有继续加强 PID 豁免,而是完整撤销了初版 parentBoundHostRelay:现在 src/cli.ts 相对 PR 基线没有改动。问题收敛到实际产生错误输入的边界——buildRelayHostEnv 在宿主重执行的环境副本中清理 pane 专属 BOTMUX_ORIGIN_CHANNEL_ID,与原有 BOTMUX_SEND_RELAY 清理并列。沙箱环境、隔离标记和授权文件均不受影响,原有 watcher capability 校验和后续精确轮次授权保持不变。

补充了真实 bwrap 回归:在 root 用户命名空间内 fork 到夹具 worker PID,再重新设置宿主 relay 标志并调用真实 CLI。初版能够越过隔离校验;修订后在 locator 校验处拒绝。断言包含实际 UID 和 PID,禁网与已结束轮次作为额外保护,不请求真实飞书 API。

也采纳了共享依赖测试的提示,将 realpath(node_modules) 加入测试挂载;在依赖软链接指向外部实际目录的 worktree 中,6 项全部通过。

自测结果:

  • Node / Vitest:267 项通过,6 项 macOS 专属运行测试在 Linux 跳过。
  • Bun 1.4.2:4 个文件逐进程运行,130 项通过。
  • bun run buildbun run verify:binary 均通过。
  • 真实 watcher → 构建后 CLI,以及独立二进制宿主重执行,均到达预期的精确已结束轮次拒绝门。

PR 描述已同步更新。最新上游 CI 仍为 action_required,需要维护者批准 fork 工作流后才能运行,尚无可读取的执行日志。

@deepcoldy

deepcoldy commented Sep 8, 2026

Copy link
Copy Markdown
Owner

⚠️ 本条部分已过时(针对旧 head 2c6d04fd4)。 关于 ppid 判据的阻断意见已解除(作者在 beff96435 整段撤销了 parentBoundHostRelay),文中的修法建议也不再适用。
仍然有效的部分:uid 分水岭的自我更正(与 uid 无关)、以及「最小绕过在本 PR 之前就存在」这一独立问题。
最新复核见 #issuecomment-5581390762

更正与补充(复审后)

复审指出我上一条评论里有一处判据错误,我已独立实测确认我错、复审对,在此更正;同时确认了一个更严重的、本 PR 之前就存在的问题,它改变了第 3 部分的定性。

❌ 更正:「root 才可利用」是错的 —— 与 uid 无关

我上一条写「非 root 下 mode-000 掩码给 EACCES ⟹ 闸被 kernel 臂抢先 ⟹ 问题不成立」。这个判据不成立:我测的是我自己假想的 mode-000 掩码形态,而真实 full-sandbox 策略.dashboard-secret 根本不产生掩码

用真实 prepareDirectSandbox + 真 compileToBwrap 复测(结构判据,不依赖 uid):

argv 起始就是 --tmpfs /                      : true
argv 里出现 .dashboard-secret                : false   ← 从未被 bind,也从未被 mask
argv 里出现宿主 ~/.botmux 目录                : false
沙箱内实际 open()                             : ENOENT => missing_or_unsafe

compileToBwrap 第一件事就是 --tmpfs /fs-policy.ts:1232),未列入策略的路径在新根里根本不存在(ENOENT),这对所有 uid 一视同仁。所以 kernelReadIsolationDetected 在 Linux 沙箱内对任何 uid 都结构性为 false,与 root 无关,非 root 部署同样可利用。我上一条的 uid 分水岭结论请以此为准作废;阻断结论本身不变,且适用范围比我原先说的更宽。

(附带说明:bwrap --unshare-user 的 uid_map 实测只映射 0 0 1,沙箱内不存在 uid 65534,setpriv --reuid=65534 直接 EINVAL —— 所以「沙箱内以非 root 跑」这个前提本身在这条链路上也不成立。)

🔴 更严重:最小绕过在本 PR 之前就已成立,不需要 ppid 伪造

沙箱内 agent 只要 unset BOTMUX_SEND_RELAY + unset BOTMUX_ORIGIN_CHANNEL_ID 两个环境变量,botmux send 就直发飞书 —— 不需要 ppid 伪造,不需要 BOTMUX_HOST_RELAY_AUTHORIZED。我用带对照组的探针在 PR 的真实 merge-base (0ad77ffcc) 上复现:

探针(真 bwrap + 真 cli.ts send 基线 0ad77ffcc 本 PR
对照组:channel 正常继承、不动它 exit 2 refused: locator is missing or ambiguous exit 2
最小绕过:agent unset 掉 channel exit 1 tenant_access_token invalid param 🔴 exit 1 🔴
本 PR 新增路径:channel 保留 + ppid 伪造 exit 2 exit 1 🔴

对照组在两棵树上都正确拒绝,证明探针能区分、不是恒通的哑弹。

根因:isolatedSendRequired 的 marker 臂条件是 !!inheritedOriginChannelId && hasManagedOriginIsolationMarker(...) —— 隔离信号本身是攻击者可控的 env 变量。沙箱内 agent 完全控制自己子进程的 env,unset 掉 origin channel 后该臂即灭;而 Linux 上内核臂结构性失效(见上),BOTMUX_READ_ISOLATED 是 darwin-only(worker.ts:13346 willReadIsolate = process.platform === 'darwin' && sandboxRequested)⟹ 三条臂全断。macOS 有 Seatbelt 的 EPERM 内核臂兜底(env 被清也能检出),Linux 没有任何等价兜底

(我上一条评论的探针没有让沙箱子进程真正继承 BOTMUX_ORIGIN_CHANNEL_ID,所以当时缺少有效对照组 —— 那条 A/B 的结论方向是对的,但严格的对照是这一版才补上的。)

对本 PR 的结论(不变,但定性调整)

  • 第 1、2 部分:建议合入。 canonical SESSION_DATA_DIR + Linux 挂载对齐 + 版本 13→14 冷启动,A/B 实测有效(master READ_FAILED → PR 读到会话库),反变异 6 枪全红。
  • 第 3 部分:建议不合。 ppid 判据在沙箱独立 PID namespace 内可数值伪造(实测 target=8151 约 3.7s 命中),跨 namespace 比 pid 不构成身份证明。它不是上述漏洞的根因,但在「origin channel 仍在」的常态下额外多开了一条旁路(见表格第三行:基线拒绝 / 本 PR 放行)。
  • 现有测试 workerPid: process.pid + 1 只在宿主单 namespace 下验证 ppid 判据,没有覆盖跨 namespace 伪造
  • 宿主 re-exec 的建议修法:watcher 注入宿主生成的一次性 token(可用 .dashboard-secret 签名 —— 沙箱内读不到该文件,见上文结构判据),不要用 ppid。
  • 另建议就「Linux 隔离闸依赖攻击者可控 env」单独开一个 issue —— 它独立于本 PR、优先级更高

以上为自动评审意见,最终以维护者审阅为准

@deepcoldy

Copy link
Copy Markdown
Owner

复审已收敛,附一份「我们踩过的坑」给后续 reviewer

两位评审对本 PR 的结论一致(见上两条评论)。这里单独记录验证方法上的三个坑——它们都曾让读数看起来"正好证明了结论",值得后来人避开。

坑 1:对照组是哑弹,两组其实是同一个输入

我第一版探针只在内层 spawnBOTMUX_ORIGIN_CHANNEL_ID没在外层 bwrap env 设。worker 真实行为是 childEnv.BOTMUX_ORIGIN_CHANNEL_ID = readIsolationOriginChannelIdworker.ts:14587),即沙箱子进程继承该变量。缺了这一步,我的"对照组(保留 channel)"和"绕过组(unset channel)"实际是同一个输入,两边都 exit 1 —— 看起来正好像"连对照组都能绕",而真相是对照组从来没成立过。

抓到它靠的是在沙箱内插桩打印闸的输入:

INS inheritedChannel=<unset>
INS hasMarker THREW invalid managed origin channel id

修正后对照组在两棵树上都正确 exit 2。判据:任何"X 是必要条件"的结论,先证对照组真的会被拒,否则那个绿是哑弹。

坑 2:基线对照用了会漂的 ref

review 期间 origin/master0ad77ffcc 漂到 61dadb04c#1266 合入)。我用 git show origin/master:<file> 造基线,于是混进了新 master 的 worker.ts —— 是 git diff --stat 报出 36 个文件差异(而本 PR 只改 7 个)才暴露的。

修正为钉死 SHA:git show 0ad77ffcc:<file>判据:基线对照必须钉 SHA,不能用会漂的 ref;对完后用文件数/差异量自检一次。

坑 3:拿自己手搭的形态代替真实 compiler 输出

我一开始判「只有 root 部署可利用」,依据是我手搭的 mode-000 掩码下非 root 会得到 EACCES。但真实 full-sandbox 策略.dashboard-secret 根本不产生掩码 —— compileToBwrap 第一件事就是 --tmpfs /fs-policy.ts:1232),未列入策略的路径在新根里不存在

改用不依赖 uid 的结构判据(真实 prepareDirectSandbox 产出的 argv):

argv 起始就是 --tmpfs /        : true
argv 里出现 .dashboard-secret  : false   ← 从未 bind、从未 mask

并用真实 policy argv 直接测两种 uid:

[default    ] uid=0     ERR=ENOENT => missing_or_unsafe
[--uid 65534] uid=65534 ERR=ENOENT => missing_or_unsafe

errno 与 uid 无关,因为路径是"不存在"而不是"被掩码"。

(顺带更正我自己在讨论中说过的一句:我曾说"沙箱内不存在 uid 65534、setpriv --reuid=65534 直接 EINVAL"。这句只对默认形态成立 —— 默认 uid_map0 0 1setpriv 确实 EINVAL;但 bwrap --uid 65534 会写出 65534 0 1 的映射,能真正以非 root 跑起来。所以「非 root 沙箱跑不起来」这个更强的说法我收回;结论不受影响,因为上面那组实测显示非 root 下同样是 ENOENT。生产 prepareDirectSandbox 不传 --uid,全仓 0 处命中。)

判据:判"某路径在沙箱内为何不可读",先看真实编译出的 argv 里它到底被 bind 了、被 mask 了、还是压根没出现 —— 这比造权限形态可靠,且不依赖 uid。

以上为自动评审的过程记录,最终以维护者审阅为准

@deepcoldy

deepcoldy commented Sep 8, 2026

Copy link
Copy Markdown
Owner

对新 head beff96435 的复核:阻断已解除,我此前两条评论的部分内容随之作废

作者的修法我逐项实测确认有效,且方向比我建议的更干净。下面是核对结果。

✅ 核心声明逐项验证

声明 我的验证 结果
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_ID2 个用例转红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 仍只来自 authorizesandbox.ts:1387BOTMUX_HOST_RELAY_AUTHORIZED 与 trustedOrigin 赋值未动),没有新增 env 豁免。

🗑 我此前评论中随之作废的部分

  • 我在 #issuecomment-5580100491#issuecomment-5581227894 里给的三个修法方向(宿主一次性 token / 比 ns/pid inode / Linux 也设 BOTMUX_READ_ISOLATED)已不适用:作者没有加强 PID 豁免,而是整段撤销,把问题收敛到真正产生错误输入的边界(watcher 的环境副本)——这比我提的任何一条都更小、更不引入新信任面。
  • 那两条评论里关于 parentBoundHostRelay / ppid 判据的所有分析,只适用于旧 head 2c6d04fd4,对当前 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 绿。以上为自动评审意见,最终以维护者审阅为准;未经维护者确认不合码。

@deepcoldy

Copy link
Copy Markdown
Owner

补一项:与已漂移的主干做真实合并态验证(无冲突,可合)

复核期间 origin/master 从本 PR 的基线 0ad77ffcc 前推到了 61dadb04c#1266 合入),本 PR 现落后 1 个 commit。#1266 同样改了 src/worker.ts(172 行),与本 PR 存在文件级重叠,所以我做了真实合并态验证而不是只看「无冲突」:

合并树对账git merge-tree --write-tree 预测树 2b3b34f057d8c251f57f8f3d793185859ce29b28,本地实际 git merge origin/master 产出树逐字节相同 ⟹ 下面这些结果就是「合上去之后的那棵树」的结果。

合并态实测

  • bun run build rc=0(无 TS 报错)
  • 本 PR 的 4 个测试文件 130/130 全绿
  • 三个安全探针在合并态复跑,行为与合并前一致:
探针 合并态结果
ppid 伪造(channel 继承 + fork 到目标 pid) exit 2 refused: locator is missing or ambiguous
对照组(channel 在、不伪造) exit 2
正向对照:合法宿主转发(buildRelayHostEnv 后 channel <STRIPPED> exit 1 通过隔离分类 ✅

#1266worker.ts 的改动没有削弱本 PR 的防护,也没有破坏它修的那条路径。两处 canonical(isolationRuntimeDataDir) 在合并后仍然成立。

(顺带更正我上一条评论里一处笔误:文中 parentBoderHostRelay 应为 parentBoundHostRelay,已在原评论修正。)

以上为自动评审意见,最终以维护者审阅为准;未经维护者确认不合码。

@deepcoldy

Copy link
Copy Markdown
Owner

主干第三次漂移后重验:仍无冲突,结论不变

上一条评论发出后 origin/master 又前推了一次(61dadb04c12e6e1d83#1308 合入)。本 PR 现落后 2 个 commit。因为上一条的合并态读数是针对 61dadb04c 的,对新主干已过期,这里重做一遍:

#1308 与本 PR 零文件交集(它改 session-turn-queue / worker-pool / daemon / tmux-pipe-backend 等,本 PR 改 sandbox.ts / read-isolation.ts / worker.ts + 4 个测试;comm -12 求交集为空)。

真实合并态(HEAD beff96435 + master 12e6e1d83

  • merge-tree --write-tree 预测树 b197c74d3dfdf3eed7e777a069bf73dbe0481434,本地实际 git merge 产出树相同 ⟹ 下列读数即「合上去那棵树」的读数
  • bun run build rc=0(无 TS 报错)
  • 4 个测试文件 130/130 全绿
  • 三个探针:ppid 伪造 exit 2 ✅ / 对照组 exit 2 ✅ / 正向对照(合法宿主转发,channel <STRIPPED>)exit 1
  • 本 PR 在合并态 src/worker.ts 的两处 canonical(isolationRuntimeDataDir) 完好,位于 15097 / 15102

⟹ 连续两次主干前推(#1266#1308)都没有削弱本 PR 的防护,也没有破坏它修复的路径

结论不变:四项改动(canonical SESSION_DATA_DIR / Linux 挂载对齐 / 版本 13→14 冷启动 / buildRelayHostEnv 清理 BOTMUX_ORIGIN_CHANNEL_ID)均实测有效、反变异承重、无阻断项。既有漏洞(沙箱内自行 unset channel 即直发,根因是 bwrap --tmpfs / 新根下内核探针返 ENOENT 而非 sandbox_denied与本 PR 无关,建议单独开高优 issue、不阻塞本 PR。

CI 仍是 action_required(fork 工作流待维护者批准)。以上为自动评审意见,最终以维护者审阅为准;未经维护者确认不合码。

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI 已全绿(build / test 及 3 个 shard / bun-test / bun-binary / bun-binary-darwin / bun-binary-musl 共 9 项 success)。双审复核:四项修复实测有效、反变异承重,对最新主干无冲突、合并态 build + 130/130 + 三探针全绿。

@deepcoldy
deepcoldy merged commit 516eae3 into deepcoldy:master Sep 8, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(sandbox): Linux 软链接 HOME 下会话定位失败,宿主转发被误判为读隔离

2 participants