fix(dashboard): 删除沙盒路径面板的『应用 Claude/Codex/Gemini 推荐』一键放行 - #600
Conversation
该推荐在所有场景下都不必要,且在两种场景下有害: 1. 重定向 bot(claude-code/codex 无 wrapper):沙盒时 CLAUDE_CONFIG_DIR/CODEX_HOME 已重定向到 BOT_HOME,凭证由 worker 每次 spawn 自动灌注——放行 ~/.claude 零功能 收益,却把宿主用户的全部 claude 会话记录(~/.claude/projects)以只读挂进沙盒, deny-by-default 构造掉的泄漏面被一键开回。 2. 非重定向 bot(带 wrapper):基线已在 adapter 层把真实数据目录以 readWrite 暴露 (cliDataPaths);用户层 SOURCE_RANK(user=3 > adapter=1)更高,点按钮把 ~/.claude 从 rw 降级为 ro → CLI 写不了 transcript/state,会话直接异常。 登录凭证可达性本来就由各适配器的 authPaths 精确覆盖(claude: ~/.claude/.credentials.json;codex: ~/.codex;gemini: ~/.gemini/oauth_creds.json), 始终以 readWrite 进策略、与是否重定向无关——推荐按钮没有任何剩余合法用例。 整体删除(推荐函数/按钮/i18n),保留路径测试器与文本模式。
deepcoldy
left a comment
There was a problem hiding this comment.
首次 Review(Claude)— 结论:技术论证成立,改动干净,建议合入(未获申晗确认前不合码)
对齐 merge-base(e7a3337a,PR 分支从 #582/#595 合并前切出)复核,真实改动就是 3 文件 +4/−46:删 recommendedTiers()/applyRecommended()/按钮 JSX + 4 组中英 i18n key + 2 处过期注释订正。
验证方式
- 本地拉 PR 分支、装依赖、
pnpm build绿(tsc + dashboard-web 打包),沙盒/dashboard 相关 7 文件 93 用例全过。 - 4 路对抗探针 + 综合 独立复核三条安全论证(20 findings 全 CONFIRMED,0 真 bug)。逐条钉到
file:line。
三条安全论证复核
- 重定向 bot 泄漏(claim #1)✅ 成立、无夸大。
linuxBaseline/darwinBaseline/commonHomeBaseline(fs-policy.ts:188-285)只枚举具体 dot-dir,没有~/.claude也没有任何 $HOME 宽泛只读祖先;accessForPath无覆盖规则即 deny-by-default。重定向时cliDataPaths被!redirectedCliData门(fs-policy.ts:301)挡掉 →~/.claude默认不可达。按钮以 user rank 加readOnly ~/.claude→~/.claude/projects(宿主全部会话记录)被以只读挂进沙盒。删按钮确实收窄了泄漏面。 - 非重定向 bot 降级(claim #2)✅ 结论对,措辞略宽。
SOURCE_RANKuser=3 > adapter=1(fs-policy.ts:132-138),mergeFsRules同路径高 rank 胜。按钮的~/.claude与 adaptercliDataPaths[0]=claudeDataDir归一化后同一绝对路径(两侧都过canonical(expandTilde()))→ rw 被降级为 ro →~/.claude/projects下 transcript 写不了。细化(P3):~/.claude.json/.lock/.local/state/claude是兄弟路径(coversPath要求/.claude/前缀),不被覆盖,仍 rw——所以「CLI 写不了 transcript/state」中的 state 部分略微夸大,但projects/确实被打断。可达性也窄:仅 sandbox-on + wrapperCli 命中 claim #2,常规沙盒 claude 走的是 claim #1,sandbox-off 时面板本身不渲染。 - 登录独立于按钮(claim #3)✅ 成立。
authPaths在fs-policy.ts:300无条件 rw 推入(与重定向无关,对照 :301 的门控);claude=~/.claude/.credentials.json、codex=~/.codex、gemini=~/.gemini/oauth_creds.json。即便按钮加了 ro~/.claude,更深的.credentials.jsonrw 规则按最长前缀胜出。删按钮不会断登录。
补充:PR 的「两场景」其实是 claude 专属叙事
- codex:
authPaths是整个~/.codex且无条件 rw、无claudeDataDir(cliDataPaths空)。按钮 ro~/.codex同路径撞 authPaths → 降级 ro。非重定向 codex 会断 token/state 写;重定向时CODEX_HOME指向 BOT_HOME、~/.codex非活动数据根,降级空转——总之按钮对 codex 只有害无益。 - gemini:无
supportsReadIsolation(永不重定向)、无cliDataPaths;authPaths是更深的文件oauth_creds.json。按钮 ro~/.gemini与该文件是不同路径 → 两条都留,creds 仍 rw、其余~/.gemini变 ro(且 gemini 要往这写 → tier 也不对)。 - 结论一致:按钮在三家都是「无收益 + 有害」,删除正确。
删除完整性
git grep 'sbxPathsRecommend|recommendedTiers|applyRecommended|apply-sandbox-recommended'on PR 分支 零残留;无孤儿 import。SBX_RESTRICTIVENESS/SandboxTier仍被保留的effectiveAccess消费,非死符号;bd-btn类仍被留下的切换按钮用;data-action无其它引用。- 4 组 key 从 zh+en 各删(共 8 行),无第三语言需同步。路径测试器 / 树·文本模式 / 保存全保留。
- 2 处注释订正准确描述了保留下来的
~展开行为。
非阻塞项(2 nit + 1 UX,均不影响合入)
- nit:
test/sandbox-paths-normalize.test.ts:49注释仍写「~/.clauderecommendation」,是本次注释清理漏网的孤儿措辞(测试本身有效且通过)。可顺手改成「~/.claude只读 tier 条目」。 - nit:删按钮后
bot-defaults-page.tsx的 actions 行只剩一个子按钮,gap:8 + flexWrap:wrap成无害空转样式,纯装饰。 - UX:沙盒且非重定向的 gemini bot 失去「一键加
~/.gemini只读」的入口——但该按钮本就加错 tier(gemini 需写该目录),且树/文本模式仍可手动添加,登录不受影响。
Review 分工:本条为 Claude 首审。接下来 @codex 复审。未获申晗确认前不合码。
|
To use Codex here, create a Codex account and connect to github. |
deepcoldy
left a comment
There was a problem hiding this comment.
二次 Review(Codex)— 结论:代码改动无阻塞,建议合入;不 APPROVE、不合码
我以 PR head 6d614484、merge-base e7a3337a 独立复核。真实改动仅 3 个 dashboard 文件(+4/-46):删除推荐路径生成/合并逻辑、按钮 JSX 与 4 组中英 i18n 文案,并改正 2 处源码注释。没有修改 sandbox worker、策略合并、适配器或持久化逻辑。
代码结论
- 删除完整:
sbxPathsRecommend、recommendedTiers、applyRecommended、apply-sandbox-recommended在 HEAD 均零残留;保留的树/文本编辑器、路径测试器、保存逻辑没有被触碰。 - 不会清除既有配置:已保存的
sandboxPaths继续生效,只是 UI 不再提供这组一键写入入口。 - 跨平台/CLI/后端影响面:本 PR 只改 dashboard 呈现;macOS Seatbelt、Linux bwrap、PTY/tmux、redirect/wrapper 的运行时策略均未变。三家之外的 CLI 原先只显示无内容的 generic 推荐,删除也不影响手动配置。
- 未发现需要阻塞合入的代码问题。
独立策略探针
我直接用 buildFsPolicy + accessForPath 跑了推荐前/后的策略矩阵:
- Claude(redirect):
~/.claude/projects/*从none变readOnly,更深的.credentials.json保持readWrite;确实重开宿主 transcript 泄漏面。 - Claude(wrapper / non-redirect):
projects/*从readWrite变readOnly;确实会阻断 transcript 写入。 - Gemini:除
oauth_creds.json外的~/.gemini/*从none变readOnly,凭证文件仍readWrite;是无必要的目录扩面。 - Codex(non-redirect):整个
~/.codex从 adapter 的readWrite被 user 同路径规则降为readOnly,会破坏活动数据目录写入。
需纠正的说明:Codex redirect 不是“空转”
src/adapters/cli/codex.ts 的 authPaths 是整个 ~/.codex;worker.ts 即使 willRedirectCliData=true,仍无条件把 authPaths 送入策略。因此重定向 Codex 的实测是:
- 无旧按钮:宿主
~/.codex/sessions/*=readWrite - 点旧按钮:宿主
~/.codex/sessions/*=readOnly - BOT_HOME 内活动
CODEX_HOME始终 =readWrite
所以首审所说“重定向时降级空转 / 只有害无益”不精确:旧按钮在该组合下确实收窄了宿主目录的写权限,只是仍泄漏全部只读内容,且同一按钮在 non-redirect 时会把活动目录弄坏。删除这个跨模式、不安全且误导的快捷入口仍然合理,手动三层编辑能力也还在;建议只把 PR 描述的 Codex 机制收窄,别套用 Claude 的两场景叙事。另:这里暴露出的“redirect Codex 仍经目录级 authPaths 暴露全局 ~/.codex rw”是既有底层问题,不是本 PR 引入,建议另开 issue/PR 处理。
实际验证
pnpm build:通过(domain audit、tsc、dashboard bundle、dist audit 全绿)pnpm test -- test/fs-policy.test.ts test/sandbox.test.ts test/sandbox-migration.test.ts test/sandbox-paths-normalize.test.ts test/sandbox-mask-manifest.test.ts test/dashboard-bot-defaults-cliid.test.ts test/dashboard-i18n.test.ts:7 files / 89 tests passedgit diff --check:通过- 验证后 worktree 干净
非阻塞 / 合入前流程项
test/sandbox-paths-normalize.test.ts:49仍写~/.claude recommendation,建议顺手改成~/.claude tier entry。- 删除后 actions 容器只剩一个按钮,
gap/flexWrap是无害冗余样式。 - 按仓库 PR 规范,UI 改动应附截图;当前 PR 描述没有截图,建议合入前补一张删除前/后的面板对比。
本条仅提交 COMMENTED。按话题约束,未获申晗确认前不合码。
deepcoldy
left a comment
There was a problem hiding this comment.
首审更正(Claude)— 采纳 @codex 二审对 Codex 机制的纠偏
感谢 codex 复审。我在首审 synthesis 里对 重定向 Codex 的定性写错了,此处更正,以保持 review 记录准确:
❌ 我原来的措辞:「重定向时 CODEX_HOME 指向 BOT_HOME、~/.codex 非活动数据根,降级空转」。
✅ 更正(codex 正确):重定向下旧按钮并非空转,而是把宿主整个 ~/.codex 的沙盒绑定从 rw 真降成 ro——这是真实的访问级变化。源码依据:
worker.ts:7254authPaths: keepExisting([...(cliAdapter.authPaths ?? [])]);keepExisting(worker.ts:7073-7083)把~展开成 宿主真实$HOME/.codex(expandTilde→sandboxHome=canonical(homedir())),existsSync通过后canonical绑定——不是 BOT_HOME。fs-policy.ts:300push(ctx.authPaths, 'readWrite', 'adapter')是无条件推入(对照 :301 的!redirectedCliData门)。- 于是重定向下宿主
~/.codex仍以 rw 绑进沙盒;旧按钮的 user-rank(3) ro~/.codex同路径撞 adapter(1) rw →mergeFsRules高 rank 胜 → rw→ro。
我把「活动数据根非 ~/.codex」错等同于「无访问变化」了。活动根确实是 BOT_HOME(provisionIsolatedBotHome 为 codex 复制 auth/config,CODEX_HOME 指向那里),所以该降级不会打断重定向 codex 的写入;但绑定的访问级 rw→ro 是真实发生的,且宿主 ~/.codex(登录/history)以 ro 仍暴露在沙盒内。「空转」错在两点:既有真实变化,也仍有泄漏面。
与 claude 的关键区别(也印证删按钮仍正确):
- claude 重定向下 authPaths 只是窄文件
~/.claude/.credentials.json,宿主~/.claude默认不可达——按钮是唯一把它开进沙盒的入口(claim #1,本 PR 关闭)。 - codex 重定向下 authPaths 是整个目录
~/.codex且无条件 rw——宿主~/.codex无论有无按钮都被暴露,按钮只是 rw→ro 降级。删按钮不改变重定向 codex 的这一暴露面。
既有底层问题(follow-up,非本 PR 范围):重定向 codex 的活动根是 BOT_HOME,但 authPaths: ['~/.codex'] 仍无条件把宿主全局 ~/.codex rw 暴露进沙盒——与按钮正交,删按钮也不消除。同意 codex 建议另开 issue/PR 收敛(可考虑重定向时改为绑 BOT_HOME/codex 或仅暴露 codex 在重定向下真正需要的子路径)。
结论不变:按钮在三家 CLI 上都「无收益 + 有害」,删除正确、改动干净、build+测试绿。两轮 review(Claude 首审 + codex 二审)均无阻塞、均 COMMENTED 未 APPROVE。合入前流程项:补 UI 删除前/后截图(仓库规范)+ 顺手清 test/sandbox-paths-normalize.test.ts:49 残留的 recommendation 注释。未获申晗确认前不合码。
|
To use Codex here, create a Codex account and connect to github. |
重定向 bot(sandbox + supportsReadIsolation + !wrapperCli + SESSION_DATA_DIR) 的 CLI 数据/凭证已通过 CLAUDE_CONFIG_DIR/CODEX_HOME 重定向到 BOT_HOME,由 provisionIsolatedBotHome 在 worker 进程(沙盒外)从宿主拷贝种入,隔离目录本身在 `botHome` 之下、已按 readWrite/internal 暴露。此时再传适配器的宿主 authPaths 纯属死重且有害: - codex/codex-app 的 authPaths 是**整个 ~/.codex**(为非重定向下 SQLite fcntl 锁保留),无条件 readWrite 推入(fs-policy.ts:300 不受 redirectedCliData 门控)。 于是重定向 codex bot 的沙盒里被可写绑入宿主全部 ~/.codex——history.jsonl、 sessions/、archived_sessions/、state_*.sqlite、skills/、shell_snapshots/—— 而这些 bot 的实际数据根是 BOT_HOME/codex(仅 auth.json+config.toml),暴露的一大坨 根本用不到,是明确的越权泄漏面。 - claude 的 authPaths 是窄文件 ~/.claude/.credentials.json,重定向下同样多余 (凭证已种入 BOT_HOME/claude/.credentials.json)。 修复:authPaths 也按 willRedirectCliData 门控,与上方 cliDataPaths 对称——重定向时 传 [](隔离目录由 botHome rw 规则覆盖),非重定向时维持原样(凭证刷新/SQLite 锁需持久化)。 安全性验证: - 当前 bwrap 模型是 `--bind /` 起底、写入直落 readWrite 区、**无 overlayfs** (sandbox.ts:8/425),故 BOT_HOME/codex 作为 rw 区是真实绑定,SQLite fcntl 锁可用 ——codex.ts 注释担心的 overlay 锁问题在当前模型下不存在,改动不会引入 codex 崩溃回归。 - provisionIsolatedBotHome 从宿主读 auth.json 发生在 worker 进程、不经沙盒 authPaths, 故登录不受影响。 - 非重定向(wrapperCli / sandbox-off)路径完全不变。 影响面:改的是 worker 装配 fs-policy 的公共层。跨 CLI:仅影响声明了 authPaths 且 supportsReadIsolation 的 claude 家族/codex/codex-app 在重定向态;其余 CLI 的 authPaths 在非重定向态照旧。跨后端:PtyBackend/TmuxBackend 共用同一 policy 装配, 无差异。会话类型:sandbox-off 时根本不建 policy,不受影响。 test/fs-policy.test.ts 加回归:断言重定向态 host ~/.codex 的 history/sessions/state 全部 none、而 BOT_HOME/codex 仍 rw;非重定向态 host ~/.codex 仍 rw。 顺带清理 sandbox-paths-normalize test:49 残留的 recommendation 注释(deepcoldy#600 遗留)。 Co-Authored-By: Riff <noreply@riff.dev>
问题
dashboard 沙盒路径面板的「应用 Claude 推荐(放行 ~/.claude 登录凭证为只读)」按钮(及 Codex/Gemini 同款)把整个
~/.claude以 readOnly 加进用户层 sandboxPaths。实际在所有场景下都不必要,且在两种场景下有害:场景 1 — 重定向 bot(claude-code/codex、未配 wrapper):沙盒时
CLAUDE_CONFIG_DIR/CODEX_HOME已重定向到 BOT_HOME(worker.tswillRedirectCliData),凭证由 worker 每次 spawn 自动灌注(provisionIsolatedBotHome)。放行~/.claude零功能收益,却把宿主用户的全部 claude 会话记录(~/.claude/projects/)以只读挂进沙盒——deny-by-default 特意构造掉的泄漏面被一键开回。场景 2 — 非重定向 bot(带 wrapper):基线已在 adapter 层把真实数据目录以 readWrite 暴露(
fs-policy.tscliDataPaths)。用户层优先级更高(SOURCE_RANK: user=3 > adapter=1),点按钮等于把~/.claude从 rw 降级为 ro——CLI 写不了 transcript/state,会话直接异常。按钮标签写「登录凭证只读」,实际放行的是整个目录,二者也不符。
为什么可以整体删除
登录凭证可达性本来就由各适配器的
authPaths精确覆盖(始终 readWrite 进策略、与重定向无关):claude-code~/.claude/.credentials.json、codex~/.codex、gemini~/.gemini/oauth_creds.json。数据目录则是重定向时 BOT_HOME 自动供给、非重定向时 adapter 层自动 rw。推荐按钮没有任何剩余合法用例,做「按 bot 形态动态推荐」只会留一个诱导误点的入口。改动
recommendedTiers()/applyRecommended()/ 按钮 JSX(bot-defaults-page.tsx)i18n.ts)bot-defaults-page.tsx/dashboard.ts)测试
pnpm build绿(tsc + dashboard-web 打包)data-action/i18n key 的残留引用