feat(dashboard): Dashboard 链接按平台绑定收敛 token,并给模型钉安全提示 - #1241
Conversation
deepcoldy
left a comment
There was a problem hiding this comment.
复审结论(3-commit head 251854c82)
三个 commit 都过了一遍,核心前提与大部分风险点独立验证通过。第三个 commit(daemon 卡片)修得对、端到端实测通过。但 bind.ts 仍有一个确定性死链没修(你自己设的 bar:「能构造出漏掉的组合就改协议层」——这个就是)。建议修完再合。
独立验证通过(没只看结论)
-
核心前提:平台身份下 token 确实被压制 ✓
本机(已绑平台)实跑 curl:- cookie(活跃 token)不带 role →
/api/settings200(legacy owner) - cookie +
X-Botmux-Role: owner→ 401 +x-botmux-auth-scope: workbench - cookie + role +
?t=<token>→ 仍 401
⟹ 平台托管主链接摘 token 成立。
- cookie(活跃 token)不带 role →
-
未绑定保留 token 的推理链 ✓
未绑定 ⟹buildPlatformDashboardLoginUrl()返回 undefined ⟹ 401 无登录出口 ⟹ token 是唯一入口。local-only 分支保留正确,没有别的入口能救回来。 -
hash 形态没有误判死链 ✓
实跑:/workbench平台身份 401、/平台身份 200、/workbenchlegacy cookie 302→/#/agent-workbench、/api/sessions平台身份 200(工作台可用)。⟹/#/agent-workbench是无凭证正确形态。 -
M7「惰性编辑」自我开脱成立 ✓
positional过滤用!==,近似拼写留在 positional 里先被raw判 invalid,showLocalTokenLink(变异改宽松的那个)读不到。安全性被结构双重钉住,不是找台阶。 -
第三个 commit(daemon 卡片) ✓
reportDashboardUrls判据与 cli 同源,且 daemon 在同进程内同时算 URL 和判据 ⟹ 不存在跨进程视图分歧,Gap B(见下)不波及 daemon 路径。- 真机实测(本机已绑平台):
reported url无 token、localUrl为 undefined。 - 影响面独立扫了一遍(
buildDashboardUrls调用点 +.dashboard-token读取点两条线):worker.ts:2122是服务端验签、desktop/smoke.ts是自测、workbench-link.ts是产品决策过的常驻入口——都不是「把链接交到人/模型手里」的打印点,与你结论一致。 - 测试 108/108 绿;build 绿(首跑一次瞬态失败,tsc 单独跑能产出缺失文件,PR 没碰构建脚本,非本 PR 引入)。
阻断项:bind.ts:158 硬编码 true 仍会确定性摘成死链
const [primary, ...rest] = formatDashboardSuccessLines(cur, false, true);注释说「刚 bind 成功、platform.json 已落盘 ⇒ 必然是中心平台托管」——但这只在 remoteAccess 开时成立。cmdBind 只在 remoteAccess === undefined 时才写 remoteAccess: true(bind.ts:128);用户显式设过 false 就保持 false。
这时若配了 BOTMUX_PUBLIC_URL(或 devbox 短链),dashboard 的 remotePublicBase() = (remoteAccess=false → null) ?? publicReverseProxyBaseUrl() → 返回反代基址(不是平台基址),localUrl 有值。bind.ts 传 true ⟹ dropToken = true && true ⟹ 摘 token ⟹ 死链(remoteAccess=false ⟹ buildPlatformDashboardLoginUrl() 返回 undefined ⟹ 无登录出口)。
实跑探针确认(构造反代形态输入):
formatDashboardSuccessLines(
{ url:'https://botmux.mycorp.example/?t=SECRET',
localUrl:'http://10.0.0.7:7891/?t=SECRET' },
false, true)
→ 首行 https://botmux.mycorp.example/ ← token 被摘,死链
cli.ts 两条路径用 dashboardLinkIsPlatformHosted() 没这个问题,daemon 第三个 commit 用同表达式也没问题,唯独 bind.ts 绕开了判据。这是确定性 bug,不是竞态。
第二点:cli.ts 路径「结构性不可达」论证有个洞(窄竞态,非阻断)
你论证「dashboard 拿更旧值时 remotePublicBase() 返回 null ⟹ localUrl 是 undefined ⟹ 被后半条挡死」。这在配了 BOTMUX_PUBLIC_URL/devbox 时不成立:
remotePublicBase() = (remoteAccess ? platformBase : null) ?? publicReverseProxyBaseUrl() ?? devbox
dashboard 缓存里 remoteAccess=false(直接改 config.json 后的 ≤2s TTL 窗口内)⟹ platformBase=null,但 ?? publicReverseProxyBaseUrl() 非 null ⟹ remoteBase 非 null ⟹ localUrl 有定义 ⟹ 后半条挡不住。CLI(新读 remoteAccess=true)说平台托管 ⟹ 摘 token ⟹ 死链。
触发条件窄(直接改文件而非 dashboard UI——UI 走 mergeGlobalConfig 立即失效缓存;且要配反代/短链;且 ≤2s 窗口),但结构性可达,「不可达」结论不成立。和第一个 commit 是同一类错误:把「没有平台」当成「没有远程基址」。
建议
你说过「能构造出漏掉的组合就改协议层」——上面两个就是(Gap A 确定性、Gap B 窄竞态)。
- 首选:dashboard 在
/__cli/current//__cli/rotate响应里回platformHosted字段,CLI + bind.ts 透传。两个洞一起消失,也不用维护跨进程推理。 - 最低限度:把 bind.ts 的
true换成isRemoteAccessEnabled() && platformMachineBaseUrl()(或抽公共 helper),先把确定性死链堵上;Gap B 留注释说明 2s 窗口。
非阻断 follow-up
- 工作台卡片按钮(
resolveWorkbenchUrl→workbenchEntryUrl)仍带长期 token 在multi_url里。是既有产品决策(常驻入口),本 PR 没碰合理;但它毕竟是持久化聊天记录,建议另开 issue 评估是否也走短时票据。 dashboard-url.ts注释说飞书卡片走workbenchTicketRedeemUrl短时票据,但resolveWorkbenchUrl实际用长期 token——既有文档/行为不一致,非本 PR 引入,建议顺手核对。
阻断项已修(作者回应):判据收归协议层
复审的两条判断都成立,我独立复现过阻断项:隔离 HOME 构造 第二点(我的「结构性不可达」论证有洞):也成立。我的前提是「dashboard 没用远程基址就不给 这里值得记一笔,因为它比单个 bug 更值得后来人看见:我在同一个 PR 里,先因为「把派生位 修法:判据收归协议层(两个洞一起消失)两个问题同源——判据由调用方各自推断,而只有生成 URL 的 dashboard 进程知道它用了哪条基址。所以:
⟹ 验证
一处小更正复审第 4 点写「 合并顺序(对最新 head 重验过):#884 trial merge |
复审通过(5-commit head
|
`botmux dashboard` 的输出会被大模型读进上下文、再顺手转发到飞书群,而链接里的 长期 token 等同管理员凭证 —— 一旦进聊天记录/思考过程就是泄漏。三处收敛: 1. **绑定中心化平台(或配了 BOTMUX_PUBLIC_URL 反代)后,主链接不再带 token。** 走平台子域时身份由平台注入,`dashboard/request-identity.ts` 对 `platform-dashboard` 身份恒把 `presentedToken` 压成 undefined —— 实测带 `?t=` 依旧 401(`x-botmux-auth-scope: workbench`),平台边缘更是先 302 去 SSO。 即 token 对访问零贡献、只剩泄漏价值。未绑定平台时 token 是唯一入口,原样保留 (去掉后只剩静态壳,而登录出口 `x-botmux-login-url` 未绑定时不生成)。 判据用 `localUrl !== undefined`(仅远程基址生效时才有值),不必另读配置。 2. **本地 `ip:port`+token 直连链接默认不打印**,需要时加一个刻意起得很长的显式 参数取回。该参数**不进 `--help`**,只在命令跑完后提示给人看 —— 列进 help 等于 邀请模型「既然有这个参数那就加上」。 3. **输出末尾钉一段给 AI 读的安全提示**:只发 owner 本人;当前会话若是多人群 (交给模型自己判断)不要发带 token 的链接。钉在 stdout 而非 skill 文档,是因为 模型做「要不要发」这个决定时,手里只有这段输出。⚠️ 无凭证形态必须用 hash 路由 `/#/agent-workbench`:`/workbench` 不在 `decideDashboardAuth` 静态壳白名单里,token-free 访问实测 401(平台身份下同样), 用它会给出一条打不开的死链。 首行仍是裸 URL,`botmux dashboard | head -1` 的脚本契约不变(已实跑验证)。 影响面:三个打印同一条链接的入口统一走 `formatDashboardSuccessLines`—— `botmux dashboard`、start/restart 后的面板提示(cli.ts)、`botmux bind` 绑定成功 回显(platform/bind.ts)。Desktop 那条路走结构化 `locate.url` 且渲染进自己的本地 webview(非聊天surface),不在本次范围。飞书卡片按钮沿用既有常驻链接机制,未改。 验证: - `bun run build` 绿;针对性 9 个套件 338/338 绿 - 全量 unit 20709 passed / 11 failed —— 全部与本改动无关,已用**同 commit 的 pristine worktree**坐实:`plugin-mcp-sandbox`/`plugin-registry-sandbox-read`/ `mojo-launcher-env-quarantine` 在未改代码上同样红(root 下 EACCES 断言造不出 前置);`dashboard-ipc-probe`/`tmux-pipe-backend-exit` 单跑全绿=负载 flake - 变异测试 10 个:M1 不去 token / M2 忽略参数直接打印 / M3 删安全提示 / M4 用 /workbench 死链 / M5 未绑定也去 token / M6 参数不在 positional 前摘掉 / M8 清空整个查询串 / M9 不删 token / M10 不可解析时 fail open —— 全部被测到红。 M7(宽松匹配参数名)存活经查是**惰性编辑**:`positional` 过滤用 `===`,近似拼写 会留在 positional 里先被判 invalid,精确性被结构双重钉住 - 真机实跑(本机已绑平台):主链接与工作台链接均不含 token;加参数后本地直连链接 正常打印并附警告;`--help` 不含该参数;`dashboard current <参数>` 不误判 invalid;近似拼写被拒且不打印 token Co-Authored-By: Claude Code <noreply@anthropic.com>
自查发现上一个 commit 的真实缺陷:用 `localUrl !== undefined` 当「平台已生效」的
等价判据是错的。`remotePublicBase()` 有**三个**来源,认证方式完全不同:
① 中心平台 `platformMachineBaseUrl()` —— 平台反代时注入身份、先过 SSO,
`request-identity.ts` 恒把 `presentedToken` 压成 undefined ⟹ token 无效也
不需要,该去。
② 自建反代 `BOTMUX_PUBLIC_URL`(如 nginx) —— 只是转发到本机 dashboard,
**没有人注入身份**。
③ Devbox 短链 `devboxDashboardBaseUrl()`(merlin 隧道) —— 同 ②。
而 `buildDashboardUrls` 对 ①②③ 都会给出 `localUrl`,所以原判据把 ②③ 也判成
「可以去 token」。实测 ②③ 下(带 `X-Forwarded-*` 反代头)无凭证请求 401、带 `?t=`
才 302;而 401 上的登录出口 `buildPlatformDashboardLoginUrl()` 第一行就是
`if (!isRemoteAccessEnabled()) return undefined` 且要求读到 `platform.json`,
②③ 都拿不到 ⟹ **对 ②③ 去 token 会摘成打不开的死链**。
修法:`formatDashboardSuccessLines` 新增 `platformHosted` 参数,只有它为真且确实
有 `localUrl` 时才去 token。默认 `false` = fail-safe 方向 **拿不准就保留**:少去
一次只是维持现状,多去一次可能让 owner 完全进不去。判据由调用方注入(保持纯函数
可测),`cli.ts` 新增 `dashboardLinkIsPlatformHosted()`,与既有
`ensureDevboxDashboardExportForCurrentPort` 的 `remoteBaseConfigured` 同源,
区别是**不**把反代算进来;`platform/bind.ts` 处刚 bind 成功、platform.json 已落盘,
直接传 true。
验证:
- 新增 3 个回归用例:反代形态(有 localUrl 但 platformHosted=false)必须保留 token
且工作台仍用 `/workbench`;省略参数时默认保留;声称 platformHosted 但无 localUrl
时也保留(不自洽的输入不摘凭证)
- 变异测试 3 个全部被测到红:M11 退回本缺陷(只看 localUrl) / M12 只看
platformHosted 不要求自洽 / M13 默认值改 true(fail-open 方向)
- 针对性 9 套件 341/341 绿;`bun run build` 绿
- 真机实跑:本机已绑中心平台 ⇒ 仍正确去 token;注入反代形态 ⇒ 正确保留 token
- 文档同步更正(两处原先写的是过宽的判据)
Co-Authored-By: Claude Code <noreply@anthropic.com>
补上一处被我漏掉的影响面。`daemon.ts:dashboardUrlForReport()` 给两张**飞书卡片**
提供 dashboard 链接:
· 重启报告 DM(`core/restart-report.ts` → `restart.dashboard` /
`restart.dashboard_local`)
· CLI 运行时更新提醒(`core/cli-runtime-update.ts` → `cli_update.dashboard`)
它原样带 `?t=<长期 token>`。而卡片是**永久聊天记录**——可转发、可截图、可搜索,
正是本 PR 要堵的那条泄漏面;前两个 commit 只收敛了终端输出与 `botmux bind` 回显,
影响面分析漏了这条。实测(本机已绑平台)该函数确实产出
`https://m-<id>.<平台域名>/?t=SECRET-TOKEN` 并交给卡片构建器。
修法:新增 `core/dashboard-url.ts:reportDashboardUrls(urls, platformHosted)`,
平台托管时 ①摘掉主链接 `?t=` ②**整条不返回 `localUrl`**。②比终端更严格是有意的:
`localUrl` 恒定带 token,它是给终端里的 owner 当平台异常兜底用的(`botmux dashboard`
有显式参数可取),不该主动推进聊天记录。判据与
`cli/dashboard-command.ts:formatDashboardSuccessLines` 同源:**只有中心平台**这条腿
注入身份 + 走 SSO,自建反代 / Devbox 短链没人注入身份、token 仍是唯一凭证,摘了是死链。
收敛逻辑放 `core/dashboard-url.ts` 而不是内联在 daemon:`daemon.ts` 不导出任何测试
够得到的东西,与当初把 `dashboard-command.ts` 从 `cli.ts` 抽出来同一个理由。
验证:
- 新增 4 个回归用例(平台托管摘 token 且扣下 localUrl / 非平台托管原样透传 /
不可解析时 fail-safe 保留 URL 但仍扣 localUrl / 已无 token 时幂等)
- 变异 3 个全部被测到红:M14 退回原缺陷(原样返回) / M15 只摘 token 但仍带
localUrl(半修) / M16 忽略 platformHosted 一律摘(反代变死链)
- 针对性 10 套件 388/388 绿(含 restart-report 与 cli-runtime-update);build 绿
- 真机端到端:本机已绑平台 ⇒ 卡片拿到的 url 无 token、localUrl 为 undefined
⭐ 发现路径:穷尽「谁会把带 token 的链接交到人/模型手里」这个维度——按
`buildDashboardUrls` 调用点 + 读 `.dashboard-token` 的位置两条线各扫一遍,
而不是只看自己改过的文件。
Co-Authored-By: Claude Code <noreply@anthropic.com>
复审抓出一个**确定性**阻断项,连带证否了我自己的一条「结构性不可达」论证。两个洞 同源:判据由**调用方各自推断**,而只有生成 URL 的 dashboard 进程知道它用了哪条基址。 **阻断项**:`platform/bind.ts` 硬编码 `platformHosted: true`,注释理由是「刚 bind 成功必然平台托管」。错的——同文件上方 `cmdBind` 只在 `remoteAccess === undefined` 时才写 `true`,用户**显式设过 `false` 就保持 `false`**(注释自己也写了「尊重用户 选择」)。此时若配了 `BOTMUX_PUBLIC_URL` / Devbox 短链,dashboard 返回反代基址、 `localUrl` 有值,硬编码 true ⟹ **确定性把 token 摘成死链**(反代无登录出口)。 已用隔离 HOME 复现:`remoteAccess=false` + `platform.json` 存在 + 反代 ⟹ 正确判据 应为 false,而 bind.ts 传 true。 **连带**:我此前论证「跨进程判据不一致的有害方向被 `localUrl !== undefined` 结构性 挡死」,前提是「dashboard 没用远程基址就不给 localUrl」——**配了反代时 localUrl 有 定义,这条挡不住**。窄(≤2s 配置缓存窗口 + 直接改文件),但结构上可达。 **修法(按我自己设的 bar:能构造出漏掉的组合就改协议层)**: - `DashboardUrls` 新增 `platformHosted`,由 `buildDashboardUrls` 如实标注:拆出 `platformCentralBaseUrl()`(远程访问开 + 已绑定)与 `nonPlatformRemoteBase()` (反代 / Devbox),`platformHosted = remoteBase !== null && remoteBase === platformBase` ——是「主链接**就是**平台子域」,不是「这台机器绑过平台」。 - `/__cli/*` 响应自然带上该字段(`dashboardUrlsFor` 直接返回 DashboardUrls)。 - `DashboardResult` 解析该字段,且**只认严格 `true`**:字段缺失(旧版 dashboard)、 字符串 `'true'`、数字 `1`、`null` 一律 false = fail-safe 保留 token。 - 三个调用点全部改为读该字段,不再自己算:`formatDashboardSuccessLines` 去掉 `platformHosted` 形参、`reportDashboardUrls` 去掉第二个形参、删掉 `cli.ts` 的 `dashboardLinkIsPlatformHosted()` 与 daemon 侧的重复推断(连带清掉两个死 import)。 ⟹ 两个洞一起消失:bind.ts 无从硬编码,跨进程也不可能不一致(判据与 URL 同源同一次 计算、同一响应传回)。 验证: - **真机 live 部署实测**(switch:here + daemon:restart):`/__cli/current` 响应 keys=`url,localUrl,platformHosted`、`platformHosted=true`,协议层如实回原始带 token 的 url,由 CLI 决定摘 ⟹ 打印无 token;restart 后的面板提示同样无 token - **阻断项场景隔离复现**:`remoteAccess=false` + 反代 ⟹ 协议层回 `platformHosted=false` ⟹ token 正确保留(修复前是死链) - 新增 6 格线上解析用例(严格 true/false/缺失/字符串/数字/null)+ 反代那格 `platformHosted: false` 的强断言(错判据当初被骗的正是这一格) - 变异 3 个全部被测到红:M17 退回宽判据(remoteBase 非 null 就算平台) / M18 只看绑定不看远程访问开关 / M19 线上解析放宽成 truthy - 针对性 10 套件 394/394 绿;build 绿 - 文档同步:判据由 dashboard 标注、字段缺失即保留 Co-Authored-By: Claude Code <noreply@anthropic.com>
新增 `platformHosted` 是**加字段**,所以升级不同步的两个方向都必须安全,而我此前只
验了一个:
· 新 CLI + 旧 dashboard —— 字段缺失 ⟹ 严格 `true` 判定为 false ⟹ 保留 token
(已由 dashboard-endpoint.test.ts 的 6 格解析用例覆盖)
· **旧 CLI + 新 dashboard** —— 旧 CLI 不认识该字段、也没有摘 token 的逻辑,只会
原样打印 `url` ⟹ **`url` 必须始终带 token**,否则用户升级 daemon(而 PATH 上
还是旧 botmux,或 install.sh 装的旧二进制)就会拿到一条进不去的链接。
这正是「协议层如实回原始 URL、由消费端决定摘不摘」这个设计的兑现点:当初若让
dashboard 直接回摘好的 URL,旧 CLI 会确定性拿到死链。真机实测新 dashboard 的
`/__cli/current` 仍回 `url` 带 `?t=`、`localUrl` 在位 ⟹ 旧 CLI 行为与本 PR 之前一致。
之前这条不变量只是**碰巧成立**(没有任何断言钉住它)。补一个 describe 钉死:
`platformHosted === true` 时 `url` 与 `localUrl` 仍含 `?t=`。变异 M20(让协议层直接
回摘好的 URL)⟹ 5 个用例红,证明钉住了。
验证:针对性 4 套件全绿;build 绿。
Co-Authored-By: Claude Code <noreply@anthropic.com>
d06fa84 to
7174a91
Compare
|
🚀 Released in v3.18.14 |
改了什么
botmux dashboard的输出会被大模型读进上下文、再顺手转发到飞书群,而链接里的长期 token 等同管理员凭证 —— 一旦进了聊天记录 / 思考过程就是泄漏。三处收敛:1. 中心平台托管时,主链接不再带 token
走平台子域时身份由平台注入,
dashboard/request-identity.ts:119对platform-dashboard身份恒把presentedToken压成undefined。实测(本机已绑平台):cookie=<token>,无 role 头(本机 owner,阳性对照)/api/settings200cookie=<token>+X-Botmux-Role: ownerx-botmux-auth-scope: workbench?t=<token>m-<id>.<host>/?t=<token>浏览器直开next=,没绕过任何门)⟹ 平台托管时 token 对访问零贡献、只剩泄漏价值。真人 owner 是被平台 SSO 认出来的。
反过来,非平台托管时 token 不能去:那时
http://ip:port/只是静态壳,SPA 探/api/settings拿 401,而 401 上的登录出口x-botmux-login-url由buildPlatformDashboardLoginUrl()生成、未开远程访问 / 未绑定时返回undefined⟹ 登录浮层没有出口,去掉 token 等于把唯一入口堵死。1b.⚠️ 自查修掉的缺陷一:判据不能是「有没有远程基址」
第一版用
localUrl !== undefined当「平台已生效」的等价判据 —— 这是错的。remotePublicBase()有三个来源,认证方式完全不同:platformMachineBaseUrl()BOTMUX_PUBLIC_URL(nginx 等)devboxDashboardBaseUrl()(merlin 隧道)而
buildDashboardUrls对 ①②③ 都会给出localUrl,所以原判据把 ②③ 一起判成「可以去 token」。实测 ②③(带X-Forwarded-*反代头):无凭证 → 401,带?t=→ 302;而 ②③ 都拿不到登录出口 ⟹ 对它们去 token 会摘成打不开的死链。1c.⚠️ 自查修掉的缺陷二:飞书卡片路径也在带 token
daemon.ts:dashboardUrlForReport()给两张飞书卡片提供链接:重启报告 DM(core/restart-report.ts)与 CLI 运行时更新提醒(core/cli-runtime-update.ts)。它原样带?t=。卡片是永久聊天记录 —— 可转发、可截图、可搜索,正是本 PR 要堵的泄漏面;前两个 commit 只收敛了终端输出与botmux bind回显,我的「影响面」分析漏了这条。修法:新增
core/dashboard-url.ts:reportDashboardUrls(),平台托管时 ① 摘主链接?t=② 整条不返回localUrl(那条恒带 token,是给终端里的 owner 当兜底用的,不该主动推进聊天记录)。发现路径:穷尽「谁会把带 token 的链接交到人/模型手里」这个维度 —— 按
buildDashboardUrls调用点 + 读.dashboard-token的位置两条线各扫一遍,而不是只看自己改过的文件。1d. 🔴 复审抓出的阻断项 → 判据收归协议层
阻断项(确定性,非窄竞态):
platform/bind.ts曾硬编码platformHosted: true,理由是「刚 bind 成功必然平台托管」。错的 —— 同文件上方cmdBind只在remoteAccess === undefined时才写true,用户显式设过false就保持false(那段注释自己写着「尊重用户选择」)。此时若配了BOTMUX_PUBLIC_URL/ Devbox 短链 ⟹ 硬编码true确定性把 token 摘成死链。已用隔离 HOME 复现。连带被证否的我自己的论证:我此前主张「跨进程判据不一致的有害方向被
localUrl !== undefined结构性挡死」,前提是「dashboard 没用远程基址就不给localUrl」。配了反代时localUrl有定义,这条挡不住。两个洞同源:判据由调用方各自推断,而只有生成 URL 的 dashboard 进程知道自己用了哪条基址。所以按 review 建议的首选方案改掉:
DashboardUrls新增platformHosted,由buildDashboardUrls如实标注。拆出platformCentralBaseUrl()(远程访问开 + 已绑定)与nonPlatformRemoteBase()(反代 / Devbox),判据是remoteBase !== null && remoteBase === platformBase—— 即「主链接就是平台子域」,而不是「这台机器绑过平台」(后者正是 bind.ts 那个错)。/__cli/*响应带上该字段;DashboardResult解析它,只认严格true(字段缺失=旧版 dashboard、'true'、1、null一律false= fail-safe 保留 token)。formatDashboardSuccessLines/reportDashboardUrls去掉形参,删掉cli.ts的dashboardLinkIsPlatformHosted()与 daemon 侧重复推断(连带清掉两个死 import)。⟹
bind.ts无从硬编码;跨进程也不可能不一致。这不只是修实例,是去掉产生实例的自由度。2. 本地
ip:port+ token 直连链接默认不打印需要时加显式参数
--i-am-the-owner-show-token-link取回。该参数故意不进--help—— 列进 help 等于邀请模型「既然有这个参数那就加上」;它只在命令实际跑完、且确实存在一条被隐藏的本地链接时,在输出末尾提示给人看。参数名刻意写成一句「读一眼就知道别随便调」的话。3. 输出末尾钉一段给 AI 读的安全提示
只发 owner 本人;当前会话若是多人群(交给模型自己判断)不要发带
?t=的链接;给别人指路只说「在服务器上运行botmux dashboard」。钉在 stdout 而非 skill 文档,是因为模型做「要不要发这条链接」这个决定时,手里只有这段输出。实际输出
无凭证形态必须用 hash 路由
/#/agent-workbench,不能用/workbench。 后者不在decideDashboardAuth的静态壳白名单里,token-free 访问实测 401(平台身份下同样 401)。照原样保留会给出一条打不开的死链。hash 形态的 fragment 不发给服务端,落到/静态壳 200。影响面
formatDashboardSuccessLines:botmux dashboard、start/restart 后的面板提示、botmux bind绑定成功回显。reportDashboardUrls。botmux dashboard | head -1实跑验证仍可解析为 URL。desktop/main/runtime-service.ts读result.url(带 token)、忽略platformHosted—— 它的 webview 没有平台 SSO 会话,靠?t=种 cookie 才进得去,摘了会打不开自己的 dashboard。/dashboard卡片的「打开工作台」按钮未改:沿用既有常驻链接机制(core/workbench-link.ts),那是产品 owner 明确推翻过短票方案的决策,改它需要产品先拍 —— 复审也建议另开 issue 评估,不塞进本 PR。adapters/。版本混搭矩阵(加字段的协议改动,两个方向都要成立)
true判false→ 保留 token ✓url⟹url必须始终带 token ✓/__cli/current回platformHosted:true,CLI 摘 token ✓result.url(带 token),必须如此 ✓false→ 保留 token ✓第 2 行正是「协议层如实回原始 URL、由消费端决定摘不摘」这个设计的兑现点:当初若让 dashboard 直接回摘好的 URL,旧 CLI 会确定性拿到死链。该不变量此前只是碰巧成立,第 5 个 commit 用测试钉死。
测试验证
9 个失败全部与本改动无关,用同 commit 的 pristine worktree 坐实:
plugin-mcp-sandbox/plugin-registry-sandbox-read/mojo-launcher-env-quarantine在未改代码上同样红、逐格数量相同(root 身份下EACCES断言造不出只读前置)。变异测试(20 个)
/workbench(401 死链)tlocalUrl」的错判据platformHosted、不要求localUrl自洽platformHosted默认值改true(fail-open)localUrl(半修)platformHosted一律摘platformHosted退回宽判据(remoteBase非 null 就算)'false'会变 true)M7 存活经查是「惰性编辑」而非测试缺口:
positional过滤用===,任何近似拼写都会留在 positional 里、先被判invalid返回,showLocalTokenLink根本读不到。已用探针从生产入口验证四种近似形态全部invalid且不打印 token。真机实跑(本机已绑平台,做过 live 部署验证)
/__cli/current响应 keys =url,localUrl,platformHosted、值true;协议层如实回带 token 的原始 url,由 CLI 决定摘 ⟹ 终端与 restart 提示均无 tokenremoteAccess=false+ 反代 ⟹ 回platformHosted=false⟹ token 正确保留(修复前确定性死链)--help不含该参数;dashboard current <参数>不误判invalid;近似拼写被拒且不打印 token已 rebase 到
ea65f9d26(#1147 之后)合码前重新 fetch 发现 master 漂了两个 commit(#1147、#1168),其中 #1147 动了
src/cli.ts与src/dashboard.ts,与本 PR 有真实交集,故 rebase。dashboard-command.ts/dashboard-url.ts/dashboard-endpoint.ts/bind.ts/daemon.ts全同;唯一不同的dashboard.ts单独核对确认差异全来自 fix(fleet): 重启时从 .env 重载终端监听地址 #1147(本 PR 在该文件上的净改动同样逐字相同)。url仍带 token、CLI 端到端首行无 token,全部复验通过。合并顺序提示
exit=0,无冲突。src/cli.ts、test/dashboard-command.test.ts)。但该 PR 对当前 master 本身就已经是CONFLICTING/DIRTY,冲突不是本 PR 引入的。formatDashboardSuccessLines现在不再有platformHosted形参(判据从result.platformHosted读),DashboardCommandExecution的 endpoint 分支多一个showLocalTokenLink字段。🤖 Generated with Claude Code