# CC Switch 加密配置与切换重构方案(V1) ## 1. 目标与范围 - 目标:将 `~/.cc-switch/config.json` 作为单一真实来源(SSOT),改为“加密落盘”;切换时从解密后的内存配置写入目标应用主配置(Claude/Codex)。 - 范围: - 后端(Rust/Tauri)新增加密模块与读写改造。 - 调整切换逻辑为“内存 → 主配置”,切换前回填 live 配置到当前供应商,避免用户外部手改丢失。 - 新增“旧文件清理与归档”能力:默认仅归档不删除,并在迁移成功后提醒用户执行;可在设置页手动触发。 - 兼容旧明文配置(v1/v2),首次保存迁移为加密文件。 ## 2. 背景现状(简述) - 当前: - 全局配置:`~/.cc-switch/config.json`(v2:`MultiAppConfig`,含多个 `ProviderManager`)。 - 切换:依赖“供应商副本文件”(Claude:`~/.claude/settings-.json`;Codex:`~/.codex/auth-.json`、`config-.toml`)→ 恢复到主配置。 - 启动:若对应 App 的供应商列表为空,可从现有主配置自动创建一条“默认项”并设为当前。 - 问题:存在“副本 ↔ 总配置”双来源,可能不一致;明文落盘有泄露风险。 ## 3. 总体方案 - 以加密文件 `~/.cc-switch/config.enc.json` 替代明文存储;进程启动时解密一次加载到内存,后续以内存为准;保存时加密写盘。 - 切换时:直接从内存 `Provider.settings_config` 写入目标应用主配置;切换前回填当前 live 配置到当前选中供应商(由 `manager.current` 指向),保留外部修改。 - 明文兼容:若无加密文件,读取旧 `config.json`(含 v1→v2 迁移),首次保存写加密文件,并备份旧明文。 - 旧文件清理:提供“可回滚归档”而非删除。扫描 `~/.cc-switch/config.json`(v1/v2)与 Claude/Codex 的历史副本文件,用户确认后移动到 `~/.cc-switch/archive//`,生成 `manifest.json` 以便恢复;默认不做静默清理。 ## 4. 密钥管理 - 存储:系统级凭据管家(keyring crate)。 - Service:`cc-switch`;Account:`config-key-v1`;内容:Base64 编码的 32 字节随机密钥(AES-256)。 - 首次运行:生成随机密钥,写入 Keychain。 - 进程内缓存:启动加载后缓存密钥,避免重复 IO。 - 轮换(后续):支持命令触发“旧密钥解密 → 新密钥加密”的原子迁移。 - 回退策略:Keychain 不可用时进入“只读模式”并提示用户(不建议将密钥落盘)。 ## 5. 加密封装格式 - 文件:`~/.cc-switch/config.enc.json` - 结构(JSON 封装,便于演进): ```json { "v": 1, "alg": "AES-256-GCM", "nonce": "", "ct": "" } ``` - 明文:`serde_json::to_vec(MultiAppConfig)`;加密:AES-GCM(12 字节随机 nonce);每次保存生成新 nonce。 ## 6. 模块与改造点 - 新增 `src-tauri/src/secure_store.rs`: - `get_or_create_key() -> Result<[u8;32], String>`:从 Keychain 获取/生成密钥。 - `encrypt_bytes(key, plaintext) -> (nonce, ciphertext)`;`decrypt_bytes(key, nonce, ciphertext)`。 - `read_encrypted_config() -> Result`:读取 `config.enc.json`、解析封装、解密、反序列化。 - `write_encrypted_config(cfg: &MultiAppConfig) -> Result<(), String>`:序列化→加密→原子写入。 - 新增 `src-tauri/src/legacy_cleanup.rs`(旧文件清理/归档): - `scan_legacy_files() -> LegacyScanReport`:扫描旧 `config.json`(v1/v2)与 Claude/Codex 副本文件(`settings-*.json`、`auth-*.json`、`config-*.toml`),返回分组清单、大小、mtime;永不将 live 文件(`settings.json`、`auth.json`、`config.toml`、`config.enc.json`)列为可归档。 - `archive_legacy_files(selection) -> ArchiveResult`:将选中文件移动到 `~/.cc-switch/archive//` 下对应子目录(`cc-switch/`、`claude/`、`codex/`),生成 `manifest.json`(记录原路径、归档路径、大小、mtime、sha256、类别);同分区 `rename`,跨分区“copy + fsync + remove”。 - `restore_from_archive(manifest_path, items?) -> RestoreResult`:从归档恢复选中文件;若原路径已有同名文件则中止并提示冲突。 - 可选:`purge_archived(before_days)` 仅删除 `archive/` 内的过期归档;默认关闭。 - 安全护栏:操作前后做 mtime/hash 复核(CAS);发生变化中止并提示“外部已修改”。 - 调整 `src-tauri/src/app_config.rs`: - `MultiAppConfig::load()`:优先 `read_encrypted_config()`;若无则读旧明文: - 若检测到 v1(`ProviderManager`)→ 迁移到 v2(原有逻辑保留)。 - `MultiAppConfig::save()`:统一调用 `write_encrypted_config()`;若检测到旧 `config.json`,首次保存时备份为 `config.v1.backup..json`(或保留为只读,视实现选择)。 - 调整 `src-tauri/src/commands.rs::switch_provider`: - Claude: 1. 回填:若 `~/.claude/settings.json` 存在且存在当前指针 → 读取 JSON,写回 `manager.providers[manager.current].settings_config`。 2. 切换:从目标 `provider.settings_config` 直接写 `~/.claude/settings.json`(确保父目录存在)。 - Codex: 1. 回填:读取 `~/.codex/auth.json`(JSON)与 `~/.codex/config.toml`(字符串;非空做 TOML 校验)→ 合成为 `{auth, config}` → 写回 `manager.providers[manager.current].settings_config`。 2. 切换:从目标 `provider.settings_config` 中取 `auth`(必需)与 `config`(可空)写入对应主配置(非空 `config` 校验 TOML)。 - 更新 `manager.current = id`,`state.save()` → 触发加密保存。 - 保留/清理: - 阶段一保留 `codex_config.rs` 与 `config.rs` 的副本读写函数(减少改动面),但切换不再依赖“副本恢复”。 - 阶段二可移除 add/update 时的“副本写入”,转为仅更新内存并保存加密配置。 ## 7. 数据流与时序 - 启动:`AppState::new()` → `MultiAppConfig::load()`(优先加密)→ 进程内持有解密后的配置。 - 添加/编辑/删除:更新内存中的 `ProviderManager` → `state.save()`(加密写盘)。 - 切换:回填 live → 以目标供应商内存配置写入主配置 → 更新当前指针(`manager.current`)→ `state.save()`。 - 迁移后提醒:若首次从旧明文迁移成功,弹出“发现旧配置,可归档”提示;用户可进入“存储与清理”页面查看并执行归档。 ## 8. 迁移策略 - 读取顺序:`config.enc.json`(新)→ `config.json`(旧)。 - 旧版支持: - v1 明文(单 `ProviderManager`)→ 自动迁移为 v2(已有逻辑)。 - v2 明文 → 直接加载。 - 首次保存:写 `config.enc.json`;若存在旧 `config.json`,备份为 `config.v1.backup..json`(或保留为只读)。 - 失败处理:解密失败/破损 → 明确提示并拒绝覆盖;允许用户手动回滚备份。 - 旧文件处理:默认不自动删除。提供“扫描→归档”的可选流程,将旧 `config.json` 与历史副本文件移动到 `~/.cc-switch/archive//`,保留 `manifest.json` 以支持恢复。 ## 9. 回滚策略 - 加密回滚:保留 `config.v1.backup..json` 作为明文快照;必要时让 `load()` 回退到该备份(手动步骤)。 - 切换回退:临时切换回“副本恢复”路径(现有代码仍在,快速恢复可用)。 ## 10. 安全与性能 - 算法:AES-256-GCM(AEAD);随机 12 字节 nonce;每次保存新 nonce。 - 性能:对几十 KB 级别文件,加解密开销远低于磁盘 IO 和 JSON 处理;冷启动 Keychain 取密钥 1–20ms,可缓存。 - 可靠性:原子写入(临时文件 + rename);写入失败不破坏现有文件。 - 可选增强:`zeroize` 清理密钥与明文;Claude 配置 JSON Schema 校验。 - 清理安全:归档而非删除;不触及 live 文件;归档/恢复采用 CAS 校验与错误回滚;归档路径冲突加后缀去重(如 `-2`、`-3`)。 ## 11. API 与 UX 影响 - 前端 API:现有行为不变;新增清理相关命令(Tauri)供 UI 调用:`scan_legacy_files`、`archive_legacy_files`、`restore_from_archive`(`purge_archived` 可选)。 - UI 提示:在“配置文件位置”旁提示“已加密存储”。 - 清理入口:设置页新增“存储与清理”面板,展示扫描结果、支持归档与从归档恢复;首次迁移成功后弹出提醒(可稍后再说)。 - 文案约定:明确“仅归档、不删除;删除需二次确认且默认关闭自动删除”。 ## 12. 开发任务拆解(阶段一为本次交付) - 阶段一(核心改造 + 清理能力最小闭环) - 新增模块 `secure_store.rs`:Keychain 与加解密工具函数。 - 改造 `app_config.rs`:`load()/save()` 支持加密文件与旧明文迁移、原子写入、备份。 - 改造 `commands.rs::switch_provider`: - 回填 live 配置 → 写入目标主配置(Claude/Codex)。 - 去除对“副本恢复”的依赖(保留函数以便回退)。 - 旧文件清理:新增 `legacy_cleanup.rs` 与对应 Tauri 命令,完成“扫描→归档→恢复”;首次迁移成功后在 UI 弹提醒,指向“设置 > 存储与清理”。 - 保持 `import_default_config`、`get_config_status` 行为不变。 - 阶段二(清理与增强) - 移除 add/update 对“副本文件”的写入,完全以内存+加密文件为中心。 - Claude settings 的 JSON Schema 校验;导出明文快照;只读模式显式开关。 - 阶段三(安全升级) - 密钥轮换;可选 passphrase(KDF: Argon2id + salt)。 ## 14. 验收标准 - 功能: - 无加密明文文件也能启动并正确读写; - 切换成功将内存配置写入主配置; - 外部手改在下一次切换前被回填保存; - 旧配置自动迁移并生成加密文件; - Keychain/解密异常时不损坏已有文件,给出可理解错误。 - 清理:扫描能准确识别旧明文与副本文件;执行归档后原路径不再存在文件、归档目录生成 `manifest.json`;从归档恢复可还原到原路径(不覆盖已存在文件)。 - 质量: - 关键路径加错误处理与日志; - 写入采用原子替换; - 代码变更集中、最小侵入,与现有风格一致。 - 清理操作具备 CAS 校验、错误回滚、绝不触及 live 文件与 `config.enc.json`。 ## 15. 风险与对策 - Keychain 不可用或权限受限: - 对策:只读模式 + 明确提示;不覆盖落盘;允许手动恢复明文备份。 - 加密文件损坏: - 对策:严格校验与错误分支;保留旧文件;不做“盲目重置”。 - 与“副本文件”并存导致混淆: - 对策:阶段一保留但不依赖;阶段二移除写入,文档化行为变更。 - 清理误删或不可逆: - 对策:默认仅归档不删除;删除需二次确认且仅作用于 `archive/`;提供 `manifest.json` 恢复;归档/恢复全程 CAS 校验与回滚。 ## 16. 发布与回退 - 发布:随 Tauri 应用正常发布,无需前端变更。 - 回退:保留旧明文备份;将切换逻辑临时改回“副本恢复”路径可快速回退。 ## 17. 旧文件清理与归档(新增) - 归档对象: - `~/.cc-switch/config.json`(v1/v2,迁移成功后) - `~/.claude/settings-*.json`(保留 `settings.json`) - `~/.codex/auth-*.json`、`~/.codex/config-*.toml`(保留 `auth.json`、`config.toml`) - 归档位置与结构:`~/.cc-switch/archive//{cc-switch,claude,codex}/...` - `manifest.json`:记录原路径、归档路径、大小、mtime、sha256、类别(v1/v2/claude/codex);用于恢复与可视化。 - 提醒策略:首次迁移成功后弹窗提醒;设置页“存储与清理”提供扫描、归档、恢复操作;默认不自动删除,可选“删除归档 >N 天”开关(默认关闭)。 - 护栏:永不移动/删除 live 文件与 `config.enc.json`;执行前后 CAS 校验;跨分区采用“copy+fsync+remove”;失败即时回滚并提示。 ## 18. 变更点清单(代码) - 新增:`src-tauri/src/secure_store.rs` - 修改: - `src-tauri/src/app_config.rs`(load/save 加密化、迁移与原子写入) - `src-tauri/src/commands.rs`(switch_provider 改为内存 → 主配置,并回填 live) - `src-tauri/src/legacy_cleanup.rs`(扫描/归档/恢复旧文件) - 保持: - `src-tauri/src/config.rs`、`src-tauri/src/codex_config.rs`(读写工具与校验,阶段一不大动) - 前端 `src/lib/tauri-api.ts` 与 UI 逻辑 ## 19. 开放问题(待确认) - Keychain 失败时是否提供“本地明文密钥文件(600 权限)”的应急模式(当前建议:不提供,保持只读)。 - 加密文件名固定为 `config.enc.json` 是否满足预期,或需隐藏(如 `.config.enc`)。 - 是否需要提供“自动删除归档 >N 天”的开关(默认关闭,建议 N=30)。 --- 以上方案为“阶段一”可落地版本,能在保持前端无感的前提下完成“加密存储 + 内存驱动切换”的核心目标。如需,我可以继续补充任务看板(Issue 列表)与实施顺序的 PR 规划。