Skip to content

feat(dashboard): 设置页新增「分享可编辑链接」按钮 - #1063

Open
47seek wants to merge 1 commit into
deepcoldy:masterfrom
47seek:feat/dashboard-share-edit-link
Open

feat(dashboard): 设置页新增「分享可编辑链接」按钮#1063
47seek wants to merge 1 commit into
deepcoldy:masterfrom
47seek:feat/dashboard-share-edit-link

Conversation

@47seek

@47seek 47seek commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

改了什么

管理员在 dashboard 设置 → 访问权限 新增一个「复制可编辑链接」按钮:点一下即复制一条带写权限 token 的本地直连链接(localUrl),发给同事在内网打开即获得全量编辑权(改配置、开关会话),省去登宿主终端跑 botmux dashboard

为什么

此前把编辑权交给别人只能在终端 botmux dashboard 手动 rotate 出链接,网页上没有任何入口;而 botmux dashboard 打印的首行是平台子域链接(m-<id>.<platform>/?t=),平台侧对非 owner 有独立鉴权,同事打开会被挡。本按钮直接复制本地直连形式(http://<lanhost>:<port>/?t=),才是内网里同事真正打得开的那条。

实现

  • 后端 src/dashboard.ts:新增 GET /api/dashboard/edit-link。仅本地管理 token(authed,即与其它管理写操作同源的能力)可访问——未登录 401、无 active token 时 404;命中返回 dashboardUrlsFor(activeToken)url + localUrl。复用既有 activeTokendashboardUrlsFor,未改动鉴权模型。
  • 前端 src/dashboard/web/settings-page.tsx:access 区新增 ShareEditLinkRow,仅 canWrite 管理员渲染。复制时优先取 localUrl(平台子域 url 会拦非 owner,本地直连才是同事能打开的)。按钮结构与相邻 OAuth 回调基址/群名前缀行同款(.settings-subfield + .actions),视觉一致。
  • 文案 src/dashboard/web/i18n.ts:中英文各一组,含安全提示——token 即完整编辑权,谁拿到都能改,收回需重新 botmux dashboard rotate(旧链接同时失效)。

影响面

仅 dashboard(后端一个只读端点 + 前端设置页一个组件)。不涉及会话 / CLI / 后端类型 / 卡片 / 其它 IM。鉴权模型未变,只读访客看不到按钮、也拿不到端点数据。

验证

  • test/dashboard-auth.test.ts 99 条全绿
  • 改动的 3 个文件 tsc 零报错;dashboard bundle 通过
  • live daemon 实测:管理员可见并渲染紧凑按钮,点击复制到本地直连可编辑链接;/api/dashboard/edit-link 未登录 401、登录返回 url+localUrl;只读访客不可见按钮

截图(access 区,点击后)

复制成功后显示绿色提示「Editable link copied — a teammate can open it on the internal network to edit.」;按钮为紧凑描边胶囊,与「Use the address I am on / Save」同款。

管理员在设置→访问权限点一下即可复制带写权限 token 的本地直连链接(localUrl),
发给同事在内网打开即获得全量编辑权(改配置、开关会话),省去登终端跑 botmux dashboard。

- 新增 GET /api/dashboard/edit-link:仅本地管理 token(authed)可访问,未登录 401、
  无 active token 时 404;返回 dashboardUrlsFor(activeToken) 的 url + localUrl。
- 设置页 access 区新增 ShareEditLinkRow(仅 canWrite 管理员可见),复制 localUrl
  (平台子域 url 会拦非 owner,本地直连才是同事能打开的),紧凑按钮与相邻 OAuth/前缀行同款。
- 中英文文案含安全提示:token 即完整编辑权,收回需重新 botmux dashboard rotate。

影响面:仅 dashboard(后端只读端点 + 前端设置页),不涉及会话/CLI/后端类型;
读取复用既有 activeToken 与 dashboardUrlsFor,未改鉴权模型。

验证:dashboard-auth 单测 99 条全绿;tsc 对改动文件零报错;dashboard bundle 通过;
live daemon 实测——按钮渲染、点击复制到内网直连链接、未登录 401、只读访客不可见。
@47seek
47seek requested a review from deepcoldy as a code owner August 28, 2026 09:41
@deepcoldy

Copy link
Copy Markdown
Owner

感谢这个 PR,出发点很实在——「把编辑权交给同事」此前确实只有 SSH 上宿主机跑 botmux dashboard 一条路,网页上没有入口,这是个真痛点。代码本身也很干净:复用了 activeTokendashboardUrlsFor,按钮样式跟相邻行对齐,canWrite 门控和 401/404 分支都处理了,PR 描述写得清楚,还附了 live 实测。

不过在评审时发现几处问题,主要集中在「这条链接实际授予的权限」和「文案承诺的权限」之间的落差上,想先跟你同步一下:

1. 这条链接授予的是完整宿主管理权,包含一个裸 bash

文案写的是「改配置、开关会话」,但拿到这个 token 的人 legacyAuthed 判定为真,于是 src/dashboard.ts:3601 那道门(只有一句 if (!legacyAuthed) return jsonRes(res, 403, …))会放行,后面就是 debug-terminal.ts 里的 pty.spawn('/bin/bash', ['-l']) —— 跑在 dashboard 进程里、落在真实文件系统上的可写 shell。

WS 那半段也不会再拦一次:debug-terminal.tsisLegacyManagementRequest 判的同样是 kind === 'legacy-dashboard',它设计上只挡「平台隧道注入 cookie」那种场景,对局域网直连拿着链接进来的人不构成阻碍。

值得一读的是 debug-terminal.ts 的顶部注释,它自己声明这条 shell 的安全边界「比 write-link 更高一档」,并明确写着经中心化平台进来的平台身份「含平台 owner」一律拒绝。也就是说在既有设计里,连平台 owner 都拿不到这个 shell —— 而这条可以贴进任何聊天窗口的链接把它交了出去。

2. 这条链接会绕过平台的角色降权机制

这一条可能是最需要讨论的。同一个人、同一台机器,两条路径的结果不同:

  • 走平台子域(带 X-Botmux-Role: teammate)→ kind='platform-dashboard'terminalCapability='readonly'
  • 走本 PR 复制的局域网直连链接 → kind='legacy-dashboard'userId='legacy-owner'(完整权限)

PR 描述把「平台子域对非 owner 有独立鉴权,同事打开会被挡」作为选择 localUrl 的理由。但从 request-identity.ts 的实现看,那个「挡」是设计意图而非兼容性障碍 —— teammate 就是被刻意限制成 readonly 的。

需要说明的是,这个绕过能力并非本 PR 新造:任何持有 activeToken 的人历来都有。本 PR 的影响是把分发门槛降到了一键,并且把这个绕过写成了功能理由。这一层更偏产品方向,可能需要维护者拍板。

3. 文案里的收回方法不生效(这条改一行即可)

中英文案都写着「想收回运行 botmux dashboard 重新生成即可(旧链接同时失效)」。但裸 botmux dashboard 走的是 current 分支 —— dashboard-command.ts 的 usage 里自己写着「没有则创建,不轮换已有 token」,只有 botmux dashboard rotate 才会轮换。

所以管理员照文案操作,会以为链接已收回,实际旧链接依然有效。安全提示里这类错误影响比较大(让人误以为已经处置完了),建议改成 botmux dashboard rotate

4. 仓库里已有一个做同一件事的端点,可以复用它的防线

src/dashboard/standing-link.tsGET /api/workbench/standing-link,顶注写的就是「owner 自取常驻链接、不必 SSH」,和本 PR 目标一致。它带五项保护,新端点目前都没有:

standing-link 本 PR
同源校验 ✅ 跨站 403
cache-control: no-store
referrer-policy: no-referrer
审计留痕 ✅ 写不进就不发链接
无权时 404(端点视作不存在) 401(回显端点存在)

同源那道不只是形式:control-csrf.ts 顶注明确写了 SameSite=Lax 拦不住同域不同端口的 http://127.0.0.1:<别的端口>,也拦不住平台 m-<id>.<host> 的兄弟子域,并称这两条「正是最现实的攻击面」。缺了它,管理员浏览器里任何一个页面都能静默 GET 走这枚 token。顺带一提 no-store 在紧邻的 /api/autostart 上是有设的,这里漏掉可能只是 jsonRes 默认不带头。

另外 standing-link 顶注还立了一条红线:「长期 token 不得进聊天记录」(因为卡片历史、转发、截图都比首次投递活得久),飞书卡片那个按钮因此只发 30 分钟短票。本 PR 的使用场景正对着这条红线,也建议一并讨论。

关于复用的一个提醒:不能直接把前端改指过去。dashboard.ts:3483 的接线是 workbenchEntryUrl(dashboardUrlsFor(token).url),取的是 .url 而非 .localUrl —— 远程访问开启且已绑定时,它发出的恰好就是平台子域形态,正是你想避开的那条。建议的做法是复用它那套门禁与语义,但链接构造改成按需取 localUrl ?? url(比如给它加一个形态参数)。至于 /workbench?t=/?t= 的路径差异不构成障碍:/workbench 会 302 到 /?t=…#/agent-workbench,种的是同一枚 cookie、同一个 token,只是落地页不同。

5. 127.0.0.1 绑定时复制出的链接指向同事自己的机器

getDashboardExternalHost()config.ts:40-48)在 loopback 绑定时刻意返回 127.0.0.1(注释原文:A 127.0.0.1 bind must link to 127.0.0.1)。此时复制出的是 http://127.0.0.1:<port>/?t=…,同事打开的是他自己那台机器,功能静默失效但 UI 依然显示「已复制」。建议该配置下禁用按钮或给出提示。

6. 新端点目前没有测试覆盖

grep -rn "edit-link" test/ 零命中。你提到的 test/dashboard-auth.test.ts 99 条我实际跑过,确实全绿 —— 但这 99 条不经过新端点,所以它证明的是「没有回归」,而非「新端点行为正确」。对照 standing-link 有两个专门的测试文件。


最低修改清单

  1. 文案改为 botmux dashboard rotate(第 3 条)
  2. 文案如实声明权限范围:完整宿主管理权、含 debug-terminal 裸 bash、且会绕过平台 teammate 降权 —— 这是管理员决定「发不发给这个人」时唯一真正需要知道的事实(第 1、2 条)
  3. 补齐 standing-link 那五项防线,或按上面的提醒复用它(第 4 条)
  4. 127.0.0.1 绑定时禁用或警告(第 5 条)
  5. 给新端点补测试(第 6 条)

我这边的验证情况

  • bun run build 通过,dashboard bundle 正常
  • test/dashboard-auth.test.ts 99 条全绿(你描述属实)
  • test/agent-workbench-standing-link.test.ts 有 12 条失败,我在 master b94386086 上对照复现了同样的失败 —— 属于 pre-existing,与本 PR 无关,不用管
  • 上述权限判定都穿真实的 resolveDashboardIdentity / resolveDashboardRequestGate / standingLinkSameOrigin 跑过探针核对,探针跑完即删,未改动仓库
  • 无合并冲突

以上是自动评审的初步意见,可能有理解偏差,尤其第 2 条涉及产品方向,最终以维护者审阅为准。功能本身解决的痛点是真实的,主要是希望「文案承诺的权限」和「实际授予的权限」能对齐,再补上仓库里已经为这件事写好的那几道防线。辛苦了 🙏

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