cc-switch Codex 密钥迁移机制拆解
2026/9/20 23:06:00 网站建设 项目流程

cc-switch Codex 密钥迁移机制拆解

【免费下载链接】cc-switchA cross-platform desktop All-in-One assistant for Claude Code, Codex, OpenCode, OpenClaw, Grok Build & Hermes Agent. Only official website: ccswitch.io项目地址: https://gitcode.com/GitHub_Trending/cc/cc-switch

cc-switch v3.20.1 引入一处 Codex 集成上的 Breaking Change:第三方供应商切换从"密钥写入 auth.json(Codex 登录凭证文件)"改为配置即密钥——API 密钥注入config.toml(Codex 主配置文件)的experimental_bearer_token字段,auth.json回归纯官方 ChatGPT 登录文件。本文基于codex_config.rs的写入计划、安全门与预检代码,拆解执行顺序、拒绝逻辑与升级影响。

Codex 0.149 为何让旧版 auth.json 切换失效

旧契约下,cc-switch 把第三方 API 密钥写进auth.jsonOPENAI_API_KEY,依赖 Codex 的"环境凭证继承":自定义供应商表没有自带凭证时,自动借用auth.json里的密钥。上游 Codex 0.149 取消了这一继承——自定义供应商不再读取auth.json,于是旧默认模式切换后的请求一律 401。

源码注释把新契约的边界写得很直白:

// Third-party switches are config-only. Since Codex 0.149 // (openai/codex#39214) custom providers no longer inherit ambient auth // from auth.json, so the API key travels as a provider-scoped // `experimental_bearer_token` in config.toml (honored since Codex 0.48). // auth.json is reserved for the official ChatGPT login: kept when the // preservation setting is on, deleted otherwise. It never carries // third-party keys,

见 codex_config.rs。密钥落点从"共享登录文件"变为"供应商自己的表内字段"(Codex 0.48 起识别该字段),auth.json由此恢复单一职责:只承载官方 ChatGPT 登录。这一改动同时消除了一个更隐蔽的风险——第三方密钥与官方 OAuth 令牌共处一文件,跨供应商卡片可能互相"借走"不属于对方的凭证。

配置即密钥:第三方切换写入计划的执行顺序

所有 Codex 实时配置写入(供应商切换、代理接管备份重建、备份恢复)都收敛到一个纯函数式入口 plan_codex_live_write:全部变换——迁移、注入、盖章——都在构建"写入计划"(CodexLiveWritePlan)时完成,写层只负责按计划落盘。第三方分支的执行顺序如下:

  1. 全表语义预检preflight_codex_provider_table_conflicts对每个供应商表(含非活动表)检查 0.149 在加载期拒绝的字段组合,无法靠归一化修复的形状直接报错,避免写出"Codex 起不来"的配置(codex_config.rs);
  2. 官方分支提前返回:官方卡片走write_full_auth路径,密钥永不经过config.toml(codex_config.rs);
  3. 提取携带密钥extract_codex_api_keyauth.OPENAI_API_KEY或配置文本已有的experimental_bearer_token取值,两处皆空则卡片视为"无密钥";
  4. 迁移废弃保留表:把旧版接管投影遗留的[model_providers.openai].ollama.lmstudio表重命名到 cc-switch 自有的cc-switchid(常量见 codex_config.rs)——必须在安全门之前做,否则"废弃表 +openai_base_url"的混合形状会被误判;
  5. 归一化旧式改址:内置openai供应商 + 顶层openai_base_url的旧形状改写为cc-switch自定义表,因为旧形状没有表可以承载密钥;
  6. 判定登录文件去留remove_auth_file = !preserve_official_login,即"保留官方登录"开关关闭时删除auth.json而非覆写;
  7. 两道安全门:任一命中即以可操作错误拒绝本次切换(下一节展开);
  8. 注入令牌并盖章set_codex_experimental_bearer_token把密钥写入活动供应商表,随后按登录保留状态对齐requires_openai_auth(见 codex_config.rs)。

落盘阶段 write_codex_live_for_provider 先原子写config.toml,配置提交成功后才删除auth.json;删除失败只降级为警告,不会把一次已经生效的切换回滚成"未切换"假象。顺序上"先配置、后清理"保证了崩溃窗口内不会出现"登录没了、密钥也没落位"的最坏组合。

提交前预检与两道安全门的拒绝逻辑

安全门保护同一个不变量:第三方路由解析出的凭证永远不能来自auth.json。两个判定函数分别覆盖"有密钥却没地方放"和"无密钥却会退回官方登录"两种泄漏形状。

门一:携带密钥、但配置没有可承载令牌的供应商表(自定义model_provider缺表,或仅靠顶层openai_base_url改址):

match active_codex_model_provider_id(&doc) { Some(id) if is_custom_codex_model_provider_id(&id) => doc .get("model_providers") .and_then(|item| item.as_table_like()) .and_then(|table| table.get(&id)) .and_then(|item| item.as_table_like()) .is_none(), _ => doc .get("openai_base_url") .and_then(|item| item.as_str()) .map(str::trim) .is_some_and(|url| !url.is_empty()), }

见 codex_config.rs。0.149 下顶层令牌字段不被读取,把密钥留在顶层等于把凭证交给一个无人消费的位置——官方 OAuth 登录会替代它发往第三方端点,所以这类形状必须拒绝而不是"尽力而为"。

门二:无密钥、且活动表会以requires_openai_auth = true回退官方登录:

fn codex_provider_table_falls_back_to_official_auth(table: &dyn toml_edit::TableLike) -> bool { table .get("requires_openai_auth") .and_then(|item| item.as_bool()) .unwrap_or(false) && table.get("env_key").is_none() && table.get("experimental_bearer_token").is_none() }

见 codex_config.rs。整份配置的判定再区分三种路由(codex_config.rs):自定义表回退、内置openai被顶层openai_base_url改址、以及其余保留内置供应商(ollamalmstudio等)永不回退 OAuth 登录。易错点在"短路"名单:auth/aws子表被有意排除在自带凭证之外——0.149 校验它们与requires_openai_auth互斥,把它们当作"有凭证"会让死配置蒙混过关(codex_config.rs)。

拒绝必须发生在指针移动之前,这是本次变更的第二个关键设计。preflight_codex_live_write复用与写层完全相同的计划构建,只丢弃结果:

pub fn preflight_codex_live_write( category: Option<&str>, auth: &Value, config_text: Option<&str>, ) -> Result<(), AppError> { plan_codex_live_write( category, auth, config_text, crate::settings::preserve_codex_official_auth_on_switch(), ) .map(|_| ()) }

见 codex_config.rs。其文档注释说明动机:若在current指针移动后才被写层拒绝,下一次切换会把旧实时配置回填进被拒卡片的数据库行,污染卡片。切换主流程因此在移动指针前调用 preflight_codex_live_write_for_state,预检与实写共用同一套归一化、安全门与令牌注入逻辑,二者不会漂移。

requires_openai_auth 盖章与登录保留开关的新契约

"保留官方登录"(login preservation)开关在新契约下只剩一个语义:开 = 第三方切换完全不动 ChatGPT 登录;关 = 删除auth.json。为了让 Codex 的登录界面与磁盘真实状态一致,每次直连切换都会对活动供应商表盖章:

if provider_table .get("requires_openai_auth") .and_then(|item| item.as_bool()) == Some(preserve_official_login) { return Ok(config_text.to_string()); } provider_table.insert( "requires_openai_auth", toml_edit::value(preserve_official_login), ); Ok(doc.to_string()) }

见 align_codex_requires_openai_auth_with_login_preservation。盖章只触碰自带令牌短路(experimental_bearer_tokenenv_key)的表——给无短路的表盖上true,请求凭证会被路由到保留的官方 OAuth 登录,恰好是安全门要阻止的泄漏;而保留预设自带的旧值(0.149 之前auth.json承载第三方密钥时代的遗留)不可信,一律以磁盘写后状态为准覆盖。

与之配套的是数据库回填时的"令牌回收":从实时配置读回时,若config.toml携带注入令牌,会把它提回auth.OPENAI_API_KEY并清出配置文本(codex_config.rs),保证存储的供应商卡片不泄露生成物、始终保持规范形状。这也解释了编辑对话框行为的变化:表单不再从共享的auth.json预填密钥,而是重建自卡片自己的表内 bearer,避免同 base URL 的卡片密钥互相收敛(修复 #6534)。

旧配置形态自动修复、用户影响与参数速查

升级后的第一次切换/接管会按固定顺序执行一组无副作用的修复:废弃保留表重命名(幂等)、无name自定义表补名(0.149 对任何无名单元表整份拒绝加载)、旧式openai_base_url改址迁移为cc-switch表(codex_config.rs)、以及加载期字段冲突预检。这些修复全部幂等,已归一化的文本原样通过。

切换涉及的关键字段速查:

键名类型默认值/取值说明
experimental_bearer_tokenstring按需注入第三方密钥在config.toml的落点,优先写入活动供应商表;保留 id 路由下落顶层(0.149 不读,且会被安全门拒绝)
requires_openai_authbool每次切换盖章与磁盘上的登录保留状态对齐,只驱动 0.149 的登录界面,不改变已短路表的请求凭证
model_providerstringopenai(缺省时)活动路由选择器;缺失的第三方卡片按内置openai处理,改址走openai_base_url再迁移为自定义表
auth.jsonOPENAI_API_KEY/登录材料)文件仅官方登录第三方切换不再写入;保留开关关闭时整个文件被删除,失败删除只出警告
保留官方登录开关bool(设置)关 = 第三方切换删除auth.json;开 = 登录跨切换保留,且代理注入 OAuth 卡片的requires_openai_auth恒为false

用户侧影响面(来自 v3.20.1 升级说明):

  • Codex 0.48 之前的版本读取不到experimental_bearer_token,第三方认证直接失效,需升级 Codex;
  • 保留开关关闭(默认)时,第三方切换改为删除而非覆写auth.json;要恢复 ChatGPT 登录,切回绑定 Auth Center 账号的官方卡片,或codex login
  • 此前"碰巧能用"的两类卡片现在会被切换期拒绝:空配置卡片(无表承载密钥)、无密钥且依赖requires_openai_auth = true或裸openai_base_url改址借用官方登录的卡片——需补[model_providers.<id>]条目或 API 密钥;
  • 0.149 兼容修复只在"下一次实时写入"时生效,旧形状不会单独触发重写;
  • 修复 #6534 之前已互相污染的密钥不会自动纠正,需在每张卡片上重输一次。

回归测试如何锁住密钥落位契约

测试围绕三个不变量布防:

  1. 拒绝形状不可写:codex_config.rs 断言携带密钥但无可承载表形状的preflight_codex_live_write返回错误(第三方与官方上下文均拒),而归一化后的形状通过——锁死"门一先于指针移动生效";
  2. OAuth 卡片不被门误杀:live.rs 针对 xAI Grok OAuth 这类"设计上无密钥"的代理注入卡片,断言中和requires_openai_auth快照后预检必须放行,即 v3.20.1 中"切换被安全门误拒"的回归防护;
  3. 保留表迁移无损且大小写精确:codex_config.rs 验证旧接管投影的保留表被重命名、活动路由保持可达、保留登录时凭证不泄漏;codex_config.rs 进一步锁定上游大小写语义——OpenAI这类变体是合法自定义 id,而oss/ollama-chat在 0.148/0.149 上并非保留 id,迁移器不得误伤。

延伸阅读

  • codex_config.rs:写入计划、两道安全门、令牌注入与保留表迁移的完整实现入口;
  • live.rs:切换指针移动前的预检调用与代理注入 OAuth 卡片的标志中和;
  • mod.rs:供应商切换主流程,预检与current提交的事务顺序;
  • codexProviderPresets.ts:预设侧修复源,代理注入 OAuth 预设的requires_openai_auth = false在此产出;
  • CHANGELOG.md:v3.20.1 的变更说明、升级注意事项与受拒卡片的处置指引。

【免费下载链接】cc-switchA cross-platform desktop All-in-One assistant for Claude Code, Codex, OpenCode, OpenClaw, Grok Build & Hermes Agent. Only official website: ccswitch.io项目地址: https://gitcode.com/GitHub_Trending/cc/cc-switch

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询