Skip to content

fix(read-isolation): botmux quoted 在隔离 bot 下可用 + 下载被引用消息的文件#402

Open
xu4wang wants to merge 4 commits into
deepcoldy:masterfrom
xu4wang:fix/quoted-read-isolation
Open

fix(read-isolation): botmux quoted 在隔离 bot 下可用 + 下载被引用消息的文件#402
xu4wang wants to merge 4 commits into
deepcoldy:masterfrom
xu4wang:fix/quoted-read-isolation

Conversation

@xu4wang

@xu4wang xu4wang commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

⚠️ 依赖:stack 在 #387 之上

本 PR 依赖 #387(read-isolation 附件分桶)引入的 registerSelfFromCredFile / downloadResources / appId 分桶,基于其分支 pr/read-isolation-attachments 开出。请在 #387 之后 review/合并。在 #387 合入 master 前,本 PR 的 diff 会包含 #387 的改动;#387 合并后 GitHub 会自动收敛成仅剩本 PR 的增量(src/cli.ts cmdQuoted)。

问题

隔离 bot 的 sandbox 里 ~/.botmux/bots.json(含所有 bot 的 app_id/app_secret)被 read-isolation deny(必须 deny,否则跨 bot 凭证泄露)。这导致 botmux quoted 对隔离 bot 两处不可用:

  1. Bot not registeredcmdQuoted → resolveSessionAppId → loadBotConfigs()bots.json → 被 deny → 注册静默失败 → getMessageDetail 找不到已注册 bot → 抛 Bot not registered: <appId>cmdHistory / cmdSend 早已用 send-cred 兜过这层,cmdQuoted 漏了。
  2. 文件字节拿不到:即便修好 ①,被引用消息的文件资源也只返回 key+name;隔离 bot 无凭证自己调 Lark resource API 下载,botmux quoted 又没把字节落盘。

改动(仅 src/cli.tscmdQuoted

  1. 开头补 await registerSelfFromCredFile(); —— 用注入的 BOTMUX_LARK_APP_ID + 该 bot 自己 BOT_HOME 里的 send-cred.json 注册,不读 bots.json,与 cmdHistory / cmdSend 一致。
  2. 渲染后对 rendered.resources 调 daemon 同款 downloadResources(appId, messageId, resources) —— 用上一步注册的 bot client(tenant token)把文件字节下到该 bot 自己的 appId 桶 attachments/<appId>/<messageId>/(read-isolation carve-out 允许读自己桶;sandbox 只 deny 文件、不 deny 写),并把 attachments:[{type,path,name}] 塞进 quoted 的 JSON 输出,agent 直接读 path 即可打开文件。

影响面

  • 只动 cmdQuoted,对非隔离 bot 无行为变化:registerSelfFromCredFile() 在无 send-cred 时 no-op,downloadResources 与直发消息附件走完全相同的既有路径。
  • 复用已验证的 downloadResources(daemon 直发附件同款),文件类型/失败处理/needLogin 语义一致。

测试验证

  • codex CLI 代码审查 两轮(先 register、后 download)均零 findings
  • 用户 live 实测:隔离 bot(cli_aab4eaea…)在群里引用回复一条文件消息 → botmux quoted <msgId> 先返回 resources{type:file, key, name}),补下载后输出含 attachments[].path,隔离 bot 直接读该本地路径成功拿到文件内容(此前 Bot not registered + 文件不落盘)。
  • tsc 通过。
  • cmdQuoted 是 CLI I/O,暂无单测(需 mock getMessageDetail/downloadResources);可按需补。

🤖 Generated with Claude Code

xu4wang and others added 4 commits July 6, 2026 23:04
问题:飞书上传的附件存 data/attachments/<messageId>/,路径无 spawn 时可知
的 key,Seatbelt profile 又是 spawn 时静态生成 → 只能整树 deny,隔离 bot
连自己收到的文件都 EPERM。

修复:附件改按 appId 分桶 data/attachments/<appId>/<messageId>/
(getAttachmentsDir),attachments/ 整目录保持 wholesale deny(盖住兄弟桶
+历史扁平存量),carve-out 只放行自己的桶 + traverse shim——与 bots/、
.lark-cli-bots/ 同一范式,不枚举兄弟 appId,新增 bot 无需冷重启。

同轮数据盘点补 deny(均核实 CLI 侧无读取):
- data/queues/(所有 bot 的完整入站消息内容)
- data/schedules.json、data/read-isolation/
- feishu-session.json(网页登录态,可开新 bot)、.dashboard-token
- regex 整类:data/identities-<appId>.json(对话者姓名 PII,own 也不留)、
  遗留 data/.send-cred-<appId>(旧版发送凭证,存量已删)

影响面:getAttachmentsDir 是共用路径(core/session-manager),所有 CLI、
两平台生效;附件路径经 prompt 绝对路径传递,CLI 侧无耦合;非隔离 bot 仅
落盘位置变化;历史存量附件留在旧布局(旧 prompt 引用的路径本来就 deny)。

验证:pnpm build 绿;vitest 相关文件 37/37 绿,全量 7221 过(唯一失败
sandbox.test.ts 在 clean master 同样挂,与本次无关);真机 sandbox-exec
12/12(own 桶可读/兄弟桶 EPERM/枚举仍挡/own sessions 不误伤);已部署
live daemon 并在飞书实测隔离 bot 读取新上传附件成功。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ppId 软失败

codex review(重点非隔离 bot 影响面)出的两条,承接上一 commit:

1) [blocker] deny ~/.botmux/.dashboard-secret —— 之前只挡了 .dashboard-token,
   漏了签名密钥 .dashboard-secret。它签 loopback-HMAC,门禁 /__cli/rotate 和
   daemon-IPC 的 write-link 路由;dashboard-ipc-server.ts 的注释本就假设「沙箱
   挡住 secret」以阻止 sandboxed worker 给不属于自己的会话铸造可写终端 token
   ——这是半可信对话者(oncall 群成员/团队群真人/allowedChatGroup/开放态,均非
   owner,见 canTalk)驱动隔离 agent 即可触达的跨 bot 提权。deny 之后隔离 agent
   仍可正常 send/list/status(走 relay/send-cred,不签 HMAC);只有 owner 管理命令
   dashboard/term-link 会失效,本就不该由半可信 agent 执行。.dashboard-port 是
   纯端口号无凭证价值,留可读。

2) [minor] downloadResources 对 path-unsafe appId 软失败:getAttachmentsDir 内
   assertSafeAppId 现在会抛(仅手编 bots.json 的畸形 appId 触发,真实 cli_xxx 恒
   通过)。之前无校验、下载不会因 appId 抛;若直接穿到 event-dispatcher 会连整条
   消息的文本一起丢。改为 catch→记 warn→返回空附件(与下载失败同 shape),文本照常
   处理。

未修 codex 第 2 条(存活旧 pane 用旧 profile):部署 runbook 的
`suspend --isolated` 已是有效缓解(本次部署即用),隔离 bot 暂无外部用户,结构性
兜底(marker 存 profile hash)留待规则迭代变频/上外部用户时再做。

影响面:deny 仅作用于隔离 bot 的 Seatbelt profile,非隔离 bot 与 Linux daemon
无变化;downloadResources 软失败对合法 appId 是 no-op。

验证:pnpm build 绿;vitest 相关文件 32/32 绿(+软失败用例 +secret/port 断言),
全量 7222 过(唯一失败 sandbox.test.ts 在 clean master 同样挂,与本次无关);真机
sandbox-exec 14/14(secret DENY / port ALLOW / own 附件桶可读 / 兄弟桶+queues+
凭证 EPERM / 枚举仍挡);已部署 live daemon + suspend --isolated 冷启动应用新规则。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
read-isolation.ts 把共享 schedules.json 加进读 deny,但 schedule-store 是
read-modify-write:sandbox 里读被 deny → load() 得空 map → save() 用只含新任务的
map 覆盖回写,静默抹掉其它所有 bot 的定时任务(read-modify-write 退化成 write-only)。
可达路径是正常业务(owner 让隔离 bot「明天提醒我」→ agent 跑 botmux schedule add)。

本次先放开该 deny(接受"隔离 bot 能读到别人定时提示词"的轻微泄露)解掉数据丢失 blocker。
更稳的长期解是让 schedule-store.load() 区分 ENOENT/EPERM、读失败 fail-closed 拒绝 save
——那时 deny 可保留又不丢数据,留待后续重构。加注释防回归;测试断言相应翻转。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
隔离 bot 的 sandbox 里 bots.json 被 read-isolation deny(含所有 bot 密钥,必须 deny),
导致两个问题:
① `botmux quoted` 走 resolveSessionAppId→loadBotConfigs() 读 bots.json→被 deny→注册静默
   失败→getMessageDetail 抛 "Bot not registered"。cmdHistory/cmdSend 早有 send-cred 兜过,
   quoted 漏了。→ 开头补 `registerSelfFromCredFile()`(用 BOTMUX_LARK_APP_ID + 自己 BOT_HOME
   的 send-cred.json 注册,不读 bots.json),与 history/send 一致。
② 即便拿到被引用消息,其文件资源只是 key+name;隔离 bot 无凭证自己调 Lark resource API 下载。
   → 渲染后对 rendered.resources 调 daemon 同款 downloadResources,用已注册 client(tenant token)
   把字节下到自己 appId 桶 attachments/<appId>/<messageId>/(carve-out 可读;sandbox 只 deny 读、
   不 deny 写),并把 attachments[].path 塞进输出,agent 直接读路径即可。

仅动 src/cli.ts 的 cmdQuoted。依赖本分支(deepcoldy#387)引入的 registerSelfFromCredFile / downloadResources /
appId 分桶,故 stack 在 deepcoldy#387 之上。验证:codex review 两轮零 findings + 用户 live 实测隔离 bot
引用回复文件、quoted 输出含本地 path 并成功读取。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@xu4wang xu4wang requested a review from deepcoldy as a code owner July 8, 2026 03:56
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.

1 participant