Skip to content

fix(report): 为主编排回注补充 IPC 鉴权 - #779

Open
wzono wants to merge 5 commits into
deepcoldy:masterfrom
wzono:fix/report-daemon-ipc-auth
Open

fix(report): 为主编排回注补充 IPC 鉴权#779
wzono wants to merge 5 commits into
deepcoldy:masterfrom
wzono:fix/report-daemon-ipc-auth

Conversation

@wzono

@wzono wzono commented Aug 7, 2026

Copy link
Copy Markdown

改动

  • 合并当前 origin/master31a71268)并解决全部冲突。
  • 同机 dispatch 的 botmux report 在可读取宿主密钥时继续使用 fetchDaemonIpc,为最终 /api/trigger 请求携带 HMAC 鉴权。
  • 为隔离 CLI 建立 managed-origin channel:默认 tmux、tmux-pipe、zellij、zmx 等持久后端会重新注入 BOTMUX_ORIGIN_CHANNEL_ID;worker/daemon 在独立目录内原子轮换当前 session/turn capability。
  • Linux credential-only bwrap 先用 tmpfs 隐藏 capability 父目录,再只读绑定当前会话子目录;兄弟会话目录不可见,长驻 pane 仍可观察原子 rename 后的新 capability。
  • macOS read isolation 通过 Seatbelt 只开放当前 capability 目录。macOS credential-only Seatbelt 也发布私有 capability 目录,但该模式目前不屏蔽 .dashboard-secret,所以 report 仍优先走宿主 HMAC,而不是 capability relay。
  • dispatch 注册改由 source daemon 完成:daemon 从当前鉴权会话派生主编排身份,向飞书回查并证明精确 seed 确由当前 bot 发到目标群,再为 dispatch root、目标 app/session 和来源名称签发宿主 HMAC binding。
  • report 不再由 CLI 读取本地 registry,也不信任 registry 中可改写的 orchAppId/orchSessionId/title;source daemon 只采用验签后的 binding,因此合法会话篡改 registry 不能把回报劫持到其他会话。
  • registry 被全量文件沙箱或 macOS read isolation 隐藏时,report 仍可通过 source daemon lookup 到达主编排;显式 --dispatch-root 不再依赖 CLI 本地 registry。
  • thread 回报只绑定稳定 session root,不再被 type-ahead 覆盖的 quoteTargetId 误判 stale;chat 回报使用 replyTargets[liveTurnId] 精确槽位,仅在单槽明确属于同一 live turn 时兼容回退。旧轮 capability 仍会被拒绝。
  • relay 注册请求体上限为 64 KiB,report 请求体上限为 256 KiB;receiver、过期 capability、跨轮次、跨 root、未签名或被篡改 binding 均失败关闭。
  • 隔离 pane marker 升至 v10,使缺少新 env/mount/profile 的旧持久 pane 冷启动。
  • 清理 report-session-relay.ts 中实际已使用参数的下划线前缀,统一为 input / decision / meta

原因

原实现直接向 loopback /api/trigger 发请求,缺少 X-Botmux-Cli-* 鉴权头,会被 daemon 以 401 missing_headers 拒绝。仅改用宿主 HMAC 仍覆盖不了屏蔽 .dashboard-secret 的隔离环境;仅把 lookup 移进 daemon 又会让可写 registry 成为 confused-deputy 的目标来源。

当前方案同时解决传输、可达性与完整性:隔离 CLI 只向自己的 source daemon 提交当前轮 capability;dispatch 目标由 daemon 签名并在 relay 时验签;最终跨 daemon 的 /api/trigger 仍由宿主使用 HMAC 发出。

安全边界

  • capability 与 daemon 内存中的 live session/turn/attempt 对齐;调用方不能自选来源会话。
  • dispatch root 必须与 thread 稳定 root 或 chat 的精确 live-turn reply target 一致。
  • registry 只保存可变元数据和宿主签名 binding;目标身份只从验签 payload 派生。
  • daemon 注册 binding 前会验证飞书 seed 的 message id、chat id、sender type 和当前 bot 身份。
  • 转发到主编排的正文继续标记为 trusted: false,固定 instruction 明确把 report body 当作不可信数据。

兼容性与影响面

  • 仅影响 dispatch 注册及命中 dispatch root 的同机 report 回注;issue in-review 与普通飞书消息 report 路径保持不变。
  • 升级前没有签名 binding 的旧 registry 条目无法作为可信目标,会失败关闭;重新 dispatch 后生成新 binding。
  • marker v10 会让旧的 credential-only/read-isolation 持久 pane 冷启动一次,避免 warm reattach 保留旧权限视图。

验证

  • 相关 11 个测试文件、255/255 用例通过,覆盖签名 binding 篡改与 confused-deputy 负例、registry 被隐藏时的 CLI wiring、显式 dispatch root、type-ahead、旧 capability 拒绝、默认 tmux env、Seatbelt/bwrap 私有目录、reattach marker 及 session-delete 兼容。
  • pnpm build 通过:domain audit、TypeScript、runtime build id、dashboard bundle、dist audit 均成功。
  • 本机 Node v18.20.8 低于当前仓库要求的 Node >=22,因此不把本机全量套件中的环境/集成失败报告为通过;最终全矩阵以 CI 为准。

@wzono
wzono requested a review from deepcoldy as a code owner August 7, 2026 06:51

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f7e7320290

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cli.ts Outdated
let response: Response;
try {
response = await fetch(`http://127.0.0.1:${daemon.ipcPort}/api/trigger`, {
response = await fetchDaemonIpc(daemon.ipcPort, '/api/trigger', {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Route credential-isolated reports through a trusted relay

On Linux hosts with device credential isolation enabled but the per-bot file sandbox disabled, the credential-only bwrap wrapper deliberately masks .dashboard-secret while leaving the dispatch registry and daemon descriptors readable. This branch is therefore reachable, but fetchDaemonIpc calls loadDaemonIpcSecret() before sending anything and throws, so every registry-backed botmux report still fails instead of reaching the orchestrator. Use a daemon-side relay or a session-scoped capability path rather than requiring the agent process to read the host HMAC secret.

AGENTS.md reference: AGENTS.md:L54-L58

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

已修复。credential-isolated CLI 不再读取 host HMAC:它改为携带当前轮换的 session capability 请求 source daemon 的 /api/report-relay。该 handler 会绑定 live session / turn / dispatch root,从宿主 registry 派生目标身份,并由 daemon 使用 HMAC 转发固定的 untrusted report envelope;调用方提交的 source/target 字段不会被信任。补充的授权与 wiring 回归覆盖了缺失、错误、过期 capability,跨 root/turn、身份夹带、receiver 拒绝和 256 KiB body limit;相关 323/323 测试及 pnpm build 均通过。修复提交:37b64f8a。

@wzono

wzono commented Aug 7, 2026

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 37b64f8a1f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cli.ts Outdated
Comment on lines +8983 to +8987
const originClaim = readManagedOriginCapability(
resolveDataDir(),
sid,
relayDir,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Publish a capability for credential-only isolation

When device credential isolation is enabled but the full file sandbox is disabled, this fallback still cannot authenticate: BOTMUX_SEND_RELAY is unset, while the worker creates neither a Linux relay-outbox capability nor a read-isolation/origin-*.json file for credential-only bwrap, so originClaim is null and /api/report-relay rejects the request with origin_unproven. Fresh evidence after the prior thread is that the new client now reads this capability, but the credential-only worker path was not updated to publish it; therefore the scenario this commit intends to fix still breaks every registry-backed report. Publish a child-readable rotating capability for the credential-only path or provide another session-scoped transport.

AGENTS.md reference: AGENTS.md:L54-L58

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

已补齐 capability 生产端。credential-only worker 现在为每个 session 创建私有轮换目录,bwrap 先用 tmpfs 屏蔽整个 read-isolation 父目录,再只读绑定当前 session 子目录;因此 sibling capability 不可见,同时目录 bind 能观察 atomic rename 后的新 token。worker 在 fresh spawn 与 safe reattach 都会发布当前 session/turn capability,并通过 BOTMUX_ORIGIN_CAPABILITY_DIR 指向该目录;pane marker 升至 v10,使缺少新 mount/env 的旧 pane 强制冷启动。真实 bwrap 验证覆盖了 token v1→v2 轮换可见与 sibling 隐藏,相关 389/389 测试及 pnpm build 通过。修复提交:0f656a86。

@wzono

wzono commented Aug 7, 2026

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f656a86ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/worker.ts Outdated
}
if (!willReattachPersistent && credentialOnlyBwrap) {
let credentialOnlyCapabilityDir: string | undefined;
if (credentialOnlyBwrap) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Provision the relay capability for credential-only Seatbelt

On macOS when device credential isolation is active but the full file sandbox is disabled, the credential-only Seatbelt profile masks the host HMAC secret, but this new capability publication is guarded exclusively by credentialOnlyBwrap. The existing macOS capability setup is inside if (sandboxRequested), so this combination has neither the secret nor its own published claim; botmux report consequently submits no originCapability and /api/report-relay rejects every registry-backed report with origin_unproven. Add an equivalent current-session capability transport and carve-out to the credential-only Seatbelt path.

AGENTS.md reference: AGENTS.md:L54-L56

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

已补齐 macOS credential-only Seatbelt。worker 现在对 Seatbelt 与 bwrap 共用同一套 per-session capability 发布与 BOTMUX_ORIGIN_CAPABILITY_DIR 注入;Seatbelt profile 先 deny 整个 read-isolation 父目录,只授予父目录 metadata traversal 和当前 session 子目录 read,并在后序规则继续 deny 父目录 write,因此 sibling capability 不可读、当前轮换文件可读且不可写。pane marker 升至 v11,缺少新 profile/env 的 v10 Seatbelt pane 会冷启动。TDD 用例先复现缺失路径,相关 390/390 测试及 pnpm build 通过。修复提交:88169d58。

@wzono

wzono commented Aug 7, 2026

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 88169d58f8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/core/report-session-relay.ts Outdated
Comment on lines +91 to +98
const rawEntry = input.registry[dispatchRoot];
const entry = rawEntry && typeof rawEntry === 'object' && !Array.isArray(rawEntry)
? rawEntry as Record<string, unknown>
: undefined;
const targetLarkAppId = typeof entry?.orchAppId === 'string' ? entry.orchAppId.trim() : '';
const targetSessionId = typeof entry?.orchSessionId === 'string'
? entry.orchSessionId.trim()
: '';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Protect relay targets from a writable dispatch registry

When device credential isolation is enabled without the full file sandbox, both credential-only wrappers still permit writes to orchestrate-dispatch.json (bwrap binds / read-write, while the Seatbelt profile only protects credential/profile paths). An isolated CLI can therefore copy any other live orchAppId/orchSessionId tuple from the readable registry into its own capability-bound dispatch-root entry, then call this relay; these lines treat that caller-modified entry as trusted and send an HMAC-authenticated trigger into the victim session. Bind the target to host-owned immutable provenance, authenticate registry entries, or make this registry host-write-only before using it as an authorization source.

AGENTS.md reference: AGENTS.md:L54-L58

Useful? React with 👍 / 👎.

@deepcoldy

Copy link
Copy Markdown
Owner

Review: 有 blocking 问题,暂不可合

在 head 88169d58f 上复核了这版实现(pnpm build 通过、聚焦相关测试通过),确认 2 个 P1 + 1 个 P2,均可稳定复现。当前不建议合并。


P1|新 capability 目录穿不过默认 tmux 后端 → Linux 隔离 report 必 403

worker.ts 只把 BOTMUX_ORIGIN_CAPABILITY_DIR 写进过滤前的 childEnv,但 tmux / tmux-pipe / zellij / zmx 这些持久化后端最终只透传 BOTMUX_INJECTED_ENV_KEYSsrc/utils/child-env.ts)里的 key,而这个新 key 没被加进该白名单。

  • 默认后端就是 tmux(config.ts 无条件默认 tmux,PTY 需 BACKEND_TYPE=pty 显式 opt-in),所以命中的是默认配置,不是边角。
  • key 被丢后不是优雅降级,而是硬失败:worker 发布到目录式路径 …/read-isolation/origin-<digest>/.botmux-origin-capability.json,而 CLI 拿不到该 env 时回退去读旧的单文件 …/read-isolation/origin-<digest>.json——两条路径不同 → 读到 null → /api/report-relay 缺 capability → 403 origin_unproven
  • worker.ts 的 ready 预检 hasMatchingManagedOriginCapability(...) 读的是过滤前childEnv(worker 自己进程内),会误判 ready transport 可用,从而掩盖运行期断裂;SessionStart / v3 relay 等共用路径同样受影响。
  • 对照 BOTMUX_TURN_ID / BOTMUX_DISPATCH_ATTEMPT:它们用同样的 childEnv.XXX = ... 赋值方式,但白名单里,所以能到达 pane。

建议:把该 key 纳入 BOTMUX_INJECTED_ENV_KEYS(连带 tmuxEnv strip / server-global scrub 自动跟随,防陈旧值泄漏到非隔离 pane,语义同 BOTMUX_READ_ISOLATION),并补 buildBotmuxEnvAssignments 回归。至少在默认 tmux 后端 + Linux credential-only 会话上真实验证 capability 能透传进 pane——不能只测 worker 内视图(那正是本次假绿的来源)。


P1|目标派生不是真正"服务端可信派生",registry 可被隔离 CLI 改写(confused deputy)

credential-only bwrap 的底座是 --bind / /,除凭据文件外宿主文件仍可写;orchestrate-dispatch.json 不在掩码集里,隔离 CLI 可写它。daemon 侧(daemon.ts)读取这份 CLI 可写文件,report-session-relay.ts 随即把其中的 orchAppId / orchSessionId 当作可信 target。

授权函数只把 dispatchRoot 这个 key 绑定到调用方自己的会话(live turn / quoteTargetId / rootMessageId 校验的是"这个 root 是不是我的"),但没有校验该 key 下 entry 值的完整性,而该值恰恰来自可写文件。

复现:隔离 CLI 合法拥有自己的 root om_self,把 registry["om_self"] 改成任意 victim 的 {orchAppId, orchSessionId} → 带自己的 live capability POST /api/report-relay → capability ✓ + turn ✓ + rootMessageId === om_self ✓ → 授权返回 ok: true,target = victim → daemon 用 HMAC 签名的 /api/trigger 打到 victim 会话。

需要修正"忽略 body 自带 target 即安全"的假设:忽略 body 只挡住了一个入口,真正的 target 来源(registry entry 值)仍可被调用方篡改。trusted: false 只标记正文,消不掉跨会话触发、成本消耗与 prompt injection。

建议:target 绑定必须来自 daemon-owned 的不可变 provenance,或宿主签名的 registry entry,或等价的不可由 CLI 改写的授权状态;仅"忽略 body 自带 target"不够。


P1(可达性)|隔离分支在 relay 前仍依赖 CLI 本地 registry lookup

两条 relay 分支(直连签名 + 隔离 relay)都嵌在同一个 if (!hasExplicitPlacement && entry?.orchAppId && entry?.orchSessionId) 里,而 entry 来自 CLI 自己orchestrate-dispatch.json 的读取。

  • 全量 fs-policy 沙盒 / macOS 完整读隔离下,registry 不在 allow 列表 → CLI 读到 {}entry 为空 → relay 块整个进不去 → report 静默 fallback 到普通 recipient 投递,永远到不了主编排。而这两种模式恰好也是屏蔽了 .dashboard-secret、最需要 relay 的场景。
  • 显式 --dispatch-root + registry 被 mask → CLI 直接 exit(1)

结果:当前 relay 分支实际只在 credential-only bwrap(registry 可见)那一档可达,而那一档正好被上面两个 P1 咬住。注意:把 registry 判定挪到 daemon 侧修可达性时,不会自动修上面的完整性问题——daemon 读的就是同一份 CLI 可写的真实文件。可达性与完整性需要一起解决,否则等于把"够不着"变成"够得着但可被劫持"。

建议:隔离分支应凭显式 root + session capability 更早进入 source-daemon relay,由 daemon 做 registry 判定;同时该 registry 判定所依赖的 entry 必须是 CLI 不可改写的。


P2|type-ahead 会把合法 live turn 误判为 stale

ds.managedTurnOrigin.turnId(live origin)来自 worker 的正在执行的那一轮(按轮铸造 capability),而 ds.session.quoteTargetId 会被每一条新入站消息无条件覆盖(daemon.ts)。

当用户在子任务执行途中补一条消息,当前 capability 对执行轮仍然合法,但授权要求 quoteTargetId === liveTurnId(chat scope 还只看会被覆盖的 currentReplyTarget)→ thread / chat 两种场景都会返回 403 turn_provenance_stale

建议:thread scope 只需把当前 capability 绑到稳定的 session root;chat scope 应查 exact per-turn 的 replyTargets[liveTurnId]reply-target.ts 已保留该 per-turn 权威槽),仅在单槽明确标注同一 turn 时兼容回退。现有"quoteTarget 变了就 stale"的用例应改成 type-ahead 正例。


两个非阻塞

  • macOS credential-only Seatbelt 当前并未屏蔽 .dashboard-secretbuildCredentialIsolationRules 的 deny 集只含 device-auth + marker),所以 macOS 上 report 实际走的是带 HMAC 的直连分支;新增的 Seatbelt capability carve-out 主要服务 session-ready / v3 relay 等路径。功能自洽,但 PR 描述"Linux bwrap 与 macOS Seatbelt 都为 report relay 发布 capability"与实际路径有出入,建议描述更精确。
  • report-session-relay.ts_input / _decision / _meta 实际都被使用,下划线前缀通常表示"未使用",语义相反,可顺手清理(不影响结论)。

@wzono

wzono commented Aug 10, 2026

Copy link
Copy Markdown
Author

已在 4a6ea6ce 更新:提交信息已按仓库规范调整为 fix(report): 修复隔离会话回注鉴权与路由,同时合并当前 origin/master31a71268)并解决全部冲突。

blocking 修复:

  • managed-origin channel 现在可透传到默认 tmux 等持久后端;credential-only Seatbelt/bwrap 只暴露当前会话的可轮换 capability 目录。
  • CLI 不再读取或信任可写 registry 的目标字段。daemon 在证明飞书 seed 确由当前编排 bot 发出后签发 HMAC binding;report 只使用验签后的目标,因此篡改 registry 不能劫持到其他会话。
  • registry 在隔离环境中不可见时,report 仍可经 source daemon 完成 lookup 与 relay;显式 --dispatch-root 同样可达。
  • thread 不再依赖可变的 quoteTargetId;chat 使用 replyTargets[liveTurnId] 精确槽位,允许 type-ahead,同时旧轮 capability 继续被拒绝。

验证结果:11 个相关测试文件共 255/255 通过;pnpm build 通过(domain audit、TypeScript、dashboard bundle、dist audit)。本机 Node 18 低于仓库当前要求的 Node >=22,因此全量矩阵以 CI 为准。

合并 origin/master(31a71268)并解决冲突。

为持久后端透传 managed-origin channel;由 daemon 签发并校验 dispatch binding,避免 registry 目标篡改;按 live turn reply target 处理 type-ahead。
@wzono
wzono force-pushed the fix/report-daemon-ipc-auth branch from 70f772c to 4a6ea6c Compare August 10, 2026 07:44
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.

2 participants