feat(MOC-62): MCP 授权可移植保险箱(file 模式 + transfer 镜像/恢复)#307
Merged
Conversation
Owner
Author
|
设计修正(review 后,commit 5a6329b):同步不再是逐 key 并集。chatgpt-codex-connector P2 指出原并集会把只在镜像里的 key 当意外丢失写回 live,从而复活用户已 |
0feca91 to
7a082da
Compare
Codex 默认把 MCP OAuth 凭据存 OS 钥匙串(按 server_name+url,与账号/auth.json 无关)。新增默认开启的 mcpCredentialsPortableStore:开启后向 ~/.codex/config.toml 写根级 mcp_oauth_credentials_store="file",让 Codex 把凭据落 ~/.codex/.credentials.json, 并在 ~/.codex 之外维护镜像 ~/.codex-app-transfer/mcp-credentials.json。 - crates/codex_integration/mcp_credentials.rs:ensure_file_store_mode + sync_mcp_credentials(并集合并:缺失则恢复、存在则捕获;默认取 live 这个 source-of-truth,仅 live 有真实到期且 mirror 严格更新时取 mirror;任一侧损坏 整体跳过不覆盖;原子写 0o600)。10 个单测。 - paths.rs:加 mcp_credentials / mcp_credentials_mirror 两路径。 - 接线:main.rs 启动(复用 #306 的 auto_apply await,避开 config.toml 写竞争) + snapshot.rs 切换后并集同步 + save_settings 仅在开关真变时即时生效。 该 key 是全局偏好,不入 MANAGED_TOML_KEYS(restore 不剥)。 不解决 OAuth 自然过期;token 明文落盘(0o600,Codex 官方支持模式)。Refs MOC-62。
设置页加 #mcpCredentialsPortableStore checkbox(load/save/listener 同 restoreCodexOnExit 范式,默认开 via !== false);i18n 中英文案点明明文落盘 0o600 + 切账号/误删/换机自动恢复 + 不解决过期。Refs MOC-62。
README 中英 config 守护段加可移植保险箱说明、存储清单补 ~/.codex/.credentials.json 与 mcp-credentials.json 镜像;ONBOARDING 仓库地图 codex_integration 描述补该能力。Refs MOC-62。
chatgpt-codex-connector P2:原并集合并把"只在镜像里的 key"一律当意外丢失写回 live —— 用户 codex mcp logout / 撤销某 MCP 后,下次启动/切换又被复活,logout 失效 + 撤销的凭据被恢复(轻度安全问题)。已对照 Codex v0.133 oauth.rs:462-471 确认 delete_oauth_tokens_from_file 是 store.remove(key)+重写整文件的单 key 删除。 改为"整文件灾难性丢失备份"语义:live 整文件不在才从镜像恢复;live 存在则它是 权威,镜像精确跟随(捕获新授权 + 传播删除),绝不把 live 没有的 key 写回 live。 移除不再需要的 expires_at 并集/tie-break;SyncReport 加 dropped。11 单测(含 logout 不复活 / logout-all 清空 / 共享 key 取 live / 损坏跳过)。Refs MOC-62。
chatgpt-codex-connector 第二轮 P2:登出最后一个 MCP server 时 Codex 会删掉整个 .credentials.json(write_fallback_file 空 store 即 remove_file,已对照 oauth.rs:556), 故"整文件不在"既可能是有意登出全部、也可能误删/换机,同步时无从区分 —— 原 Missing→自动恢复会静默复活用户已撤销的凭据。 按用户选择改成「每次缺失弹确认」: - sync 不再自动写 live;live 整文件缺失只报 restore_available(镜像可恢复条数)。 - main.rs 启动 await auto_apply 后,restore_available>0 → emit mcp-credentials-restore-available 事件 → 前端 __TAURI__.dialog.ask 弹确认: 恢复 → POST /api/desktop/mcp-credentials/restore(镜像写回 live,仅 live 仍空时); 忽略 → /discard(删镜像,停止再弹)。 - live 存在仍权威,镜像跟随(捕获 + 传播删除),单 server 主动登出绝不复活。 新增 restore_mcp_credentials_from_mirror / discard_mcp_mirror + 2 路由 + 2 CCApi + 前端监听/确认 + zh/en 文案;README/hint「自动恢复」改「弹确认恢复」。13 单测。Refs MOC-62。
chatgpt-codex-connector 第三轮 P2:restore_available 的一次性 Tauri event 在快路径 (autoApplyOnStart 关 / 无 active provider → sync 几乎即时跑完)可能在前端 listener 注册前就 emit → 提示静默丢失,用户无从发现可恢复的备份。 改成只读状态端点 + 前端 load 轮询: - 新增 restore_available_count(只读,无副作用)+ GET /api/desktop/mcp-credentials/status。 - main.rs 不再 emit 一次性事件;startup_sync 仍负责 ensure file 模式 + 镜像跟随。 - 前端 load 时 CCApi.getMcpCredentialsStatus(),restoreAvailable>0 → 弹确认 (in-flight guard 防重入)。确定性、无竞态。 14 单测(+restore_available_count_only_when_live_missing)。Refs MOC-62。
d729c16 to
9daebfe
Compare
Cmochance
added a commit
that referenced
this pull request
May 29, 2026
…t 漏跑 (#308) stacked PR(base=feature 分支)在底 PR 合并后自动 retarget 到 main 时,no-ai-coauthor 因 `branches:[main]` 过滤 + retarget 非触发事件类型而漏跑 → 该 required check 缺失 → PR BLOCKED,只能手动 close+reopen 补跑(合 MOC-62 #307 时亲历)。去掉 base 过滤、对所有 PR 跑(跟 ci.yml 一致);job 内按 PR_BASE_SHA..PR_HEAD_SHA 扫 commit,任意 base 都正确。 Refs MOC-64
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs MOC-62。Stacked 在 #306 之上(base =
fix/moc-54-misc)—— 复用 #306 给main.rs加的auto_applyJoinHandle,启动同步 await 它后再跑,避开与 apply 抢写config.toml。#306 合并后这条 PR retarget 到 main。背景(先纠正前提)
源码级排查(Codex v0.133)证实:MCP OAuth 凭据按
server_name+url存 macOS 钥匙串,与 provider / 账号 / auth.json 无关 —— 切 provider、甚至 keychain 模式下切账号都不丢。真正会丢的是 OAuth 过期(快照救不回)或凭据被擦除 / 换机。所以本特性不是"防 provider 切换丢失",而是按你的选择把 transfer 做成 MCP 授权的可移植、可恢复的保险箱。做了什么
设置页新增默认开启的开关
mcpCredentialsPortableStore。开启后:~/.codex/config.toml写根级mcp_oauth_credentials_store = "file"→ Codex 把 MCP OAuth 凭据落~/.codex/.credentials.json(0o600 明文,单 JSON blob)。~/.codex之外维护镜像~/.codex-app-transfer/mcp-credentials.json;启动(await auto_apply 后)+ provider 切换后做并集合并:实时缺失的从镜像恢复、镜像缺失的从实时捕获,默认取 live(Codex 现状),仅 live 有真实到期且 mirror 严格更新时取 mirror;任一侧损坏整体跳过不覆盖;原子写 0o600。.credentials.json保留,非破坏)。明确不解决:OAuth 自然过期(过期 token 恢复回去仍过期,需重新授权)。安全代价:token 明文落盘(0o600,Codex 官方支持、注释称"与其它 CLI agent 一致")。
升级注意:默认开 → 首次切到 file 模式后,原本存在钥匙串里的已授权远程 MCP 需重新授权一次(v1 不碰钥匙串以避开跨 app ACL 弹框;钥匙串→文件导入留作可选 Phase 2)。
验证
crates/codex_integration新模块 10 个单测全过(并集 / prefer-fresher / live 无到期不被旧 mirror 覆盖 / 缺失恢复 / 损坏跳过 / 0o600 / ensure key 写删)。cargo check -p codex-app-transfer✓ /cargo fmt --check✓。pr-review-toolkit:code-reviewer对照 Codex v0.133 源码复核:无 BLOCKER;1 个 IMPORTANT(merge tie-break 在 token 无expires_at时可能用旧 mirror 覆盖新授权)已修(改as_u64+ 默认取 live + 加回归测试);NIT 注释措辞已修;另两个 NIT(失败 revert 已 warn / 0o644 短窗与既有write_auth一致)按现状保留。cargo test -p codex-app-transfer有 1 个 既有 flaky 并发测试gemini_oauth::...cancel_slot_epoch...在 macOS 全并行下偶发失败(本分支未碰 gemini_oauth;隔离跑 5/5 过;fix(MOC-54): 杂项修复 — 残留扫描竞态 + 致谢长度门禁 + 活跃度图单点态 #306 同 Linux CI 已验该测试通过)—— 非本 PR 引入,CI(Linux)不受影响。范围
新 feature,独立于 #306 杂项修复。不自动 merge,等你 review。
🤖 Generated with Claude Code