Skip to content

[Design Proposal] 统一「WeSight 凭据隔离 + 用户配置尊重」架构:所有 key 走自有 store + 进程 env 注入,文件写入采用 marker 化的 scoped block #33

@stephenlzc

Description

@stephenlzc

一、设计原则(三个统一契约)

WeSight 在凭据与配置管理上对外做以下三条承诺。所有新代码与重构都应满足:

P1 — 已配置就不动

若用户的本地 CLI 配置文件(~/.claude/settings.json / ~/.codex/config.toml / ~/.codex/auth.json / ~/.openclaw/openclaw.json / ~/.cc-switch/cc-switch.db / ~/.config/opencode/opencode.json 等)已经存在或含用户字段,WeSight 不得以任何形式(覆写、整块替换、整体重建)修改用户非 WeSight 字段。

P2 — 未配置才 bootstrap

若用户的本地配置文件不存在对应 block 为空,WeSight 可以写入最小可用配置以让 CLI 跑起来;写入后必须在该 block 上加 marker 标记,标记 WeSight 的所有权。

P3 — WeSight 凭据不落盘到非 WeSight 文件

WeSight 自己的 provider API key 只能存在于 WeSight sqlite(app_config.providers.<n>.apiKey),注入到子进程的方式只能是 spawn env 变量;任何写入用户文件的形式都必须用 ${WESIGHT_APIKEY_*} / ${LOBSTER_APIKEY_*} 占位符,永不写明文

说明:以下 path:line 形式的引用在 GitHub 中会自动渲染为可点链接。

二、当前架构问题(量化)

维度 违规点 文件:行 严重度
P1 Codex config.toml 全文覆写 src/main/libs/externalAgentConfigSync.ts:672 HIGH
P1 Claude env 整块替换 src/main/libs/externalAgentConfigSync.ts:319-331 HIGH
P1 OpenClaw models.providers 整体重建 src/main/libs/openclawConfigSync.ts:941-1000 HIGH
P1 applyProviderToLive 整文件覆写 src/main/libs/externalAgentProviderStore.ts:1272, 1405-1406 HIGH
P1 exec-approvals.json 强制升 security=full src/main/libs/openclawConfigSync.ts:1758-1768 MEDIUM
P2 Codex config.toml 不查空就写 src/main/libs/externalAgentConfigSync.ts:672 HIGH
P2 WesightModel 模式无 marker 多处 MEDIUM
P3 cc-switch DB 明文 WeSight key src/main/libs/externalAgentConfigSync.ts:540-588 HIGH
P3 Claude env.ANTHROPIC_API_KEY 明文 src/main/libs/externalAgentConfigSync.ts:319-331 HIGH
P3 Codex auth.json.OPENAI_API_KEY 明文 src/main/libs/externalAgentConfigSync.ts:670-676 HIGH
P3 OpenClaw gateway env 含所有 enabled provider key src/main/libs/openclawConfigSync.ts:1618-1633 LOW

三、提议的新架构

3.1 三层分层

┌──────────────────────────────────────────────┐
│ Layer 1: WeSight 私有 Store                  │
│   - wesight.sqlite → app_config.providers[N] │
│   - 唯一可信源 (single source of truth)       │
└──────────────────────────────────────────────┘
              │ env injection (per-spawn)
              ▼
┌──────────────────────────────────────────────┐
│ Layer 2: Child Process Env                   │
│   - spawn({ env: {...process.env,            │
│       WESIGHT_APIKEY_*, ANTHROPIC_*, ...} })  │
│   - 不落盘                                     │
└──────────────────────────────────────────────┘
              │ 占位符 + marker block
              ▼
┌──────────────────────────────────────────────┐
│ Layer 3: 用户本地 CLI 文件                    │
│   - 仅 WeSight marker 块可写                  │
│   - key 用 ${WESIGHT_APIKEY_*} 占位符         │
│   - user 字段永久保留                         │
└──────────────────────────────────────────────┘

3.2 关键原语(团队已具备,需推广)

原语 位置 用途
getEnhancedEnvWithTmpdir src/main/libs/coworkUtil.ts:1423-1457 每次 spawn 构造新 env,不持久化。应当是所有 key 注入的唯一入口。
shouldInjectCoworkModelConfig src/main/libs/agentEngine/externalCliRuntimeAdapter.ts:580-585 gate 开关,决定 WeSight env 是否注入。应当推广为统一的 LocalCli 行为。
buildEnvForConfig src/main/libs/claudeSettings.ts:536-544 已能把 WeSight key 转成 ANTHROPIC_* env。应当替代"写文件"那一步。
mergeHermesManagedDotenvBlock src/main/libs/hermesConfig.ts:166-213 marker 模式参考实现。应当复制到所有引擎的对应 block。
writeIfChanged / syncFileIfChanged src/main/libs/hermesConfigSync.ts:58-62 / src/main/libs/openclawConfigSync.ts:2169-2184 内容不变不写。应当推广到所有 sync 函数。
ensureGatewayToken / writeMinimalConfig 的「不降级」守卫 src/main/libs/hermesEngineManager.ts:528-575 / src/main/libs/openclawConfigSync.ts:2224-2237 bootstrap-when-empty。应当作为所有 sync 函数的默认前置。
__wesightProviderMeta 内部命名空间 src/main/libs/externalAgentLocalEnv.ts:38 内部 key 命名空间。应当推广为 env.__wesight_managed 等 marker 字段。
LocalCli 读路径 src/main/libs/externalAgentLocalEnv.ts:97-144 { readonly: true, fileMustExist: true } + 只 SELECT。应当作为所有 LocalCli 路径的样板。
atomicWrite / atomicWriteFile / writeJsonObject 多处 原子写。已统一。

3.3 模块重构计划

Stage 1 — 抽离一个统一的 mergeWithManagedBlock helper

新文件 src/main/libs/managedConfigBlock.ts

// 签名(伪代码)
function mergeWithManagedBlock<T>(opts: {
  targetPath: string;
  parser: (text: string) => T;
  serializer: (config: T) => string;
  managedKey: string;          // e.g. "env.__wesight_managed"
  managedValue: object;        // e.g. { model: true, auth: true }
  userValue: T | null;         // null = bootstrap-when-empty
  preserveUserFields: (keyof T)[];
}): { written: boolean; mergedConfig: T };

行为:

  1. targetPath;不存在 → 走 userValue bootstrap 路径,写入后该 block 标 marker。
  2. 存在 → 解析;分离 WeSight marker block 与 user block;用 preserveUserFields 保留 user 字段;merge 之后 writeIfChanged 短路。

Stage 2 — 改造所有 sync 路径走 helper

替换:

  • mergeClaudeSettingsWithProvidersrc/main/libs/externalAgentConfigSync.ts:333-360
  • buildCodexConfigsrc/main/libs/externalAgentConfigSync.ts:220-235
  • mergeOpenCodeConfigForWesightModelsrc/main/libs/externalAgentConfigSync.ts:683-688
  • mergeQwenCodeConfigForWesightModelsrc/main/libs/externalAgentConfigSync.ts:693-699
  • mergeDeepSeekTuiConfigForWesightModelsrc/main/libs/externalAgentConfigSync.ts:708-713
  • mergeHermesConfigForWesightModelsrc/main/libs/hermesConfig.ts:348-401
  • syncOpenClawConfigsrc/main/libs/openclawConfigSync.ts:1543-1566)中的 models.providers 子段

Stage 3 — 改 key 注入为 spawn env 唯一

buildEnvForConfigsrc/main/libs/claudeSettings.ts:536-544)从「也写到文件」改为「只返回 env 对象」;下游 syncClaudeCodeFromWesightModel / syncCodexFromWesightModel 不再写 key 到文件,只写占位符。

Stage 4 — cc-switch 占位符化

仿 OpenClaw 的 WESIGHT_APIKEY_* 模式(src/main/libs/openclawConfigSync.ts:1606-1633):

  • cc-switch 启动时由 WeSight 注入 env;
  • settings_config 内的 ANTHROPIC_API_KEY 改为 ${WESIGHT_APIKEY_ANTHROPIC}
  • cc-switch 端需要确认支持 ${ENV} 占位符语法。

四、详细需求(按 P1 / P2 / P3 分解)

4.1 P1 — 已配置就不动

  • 任何 sync*FromWesightModel 函数必须先 readFile,再 merge,再 writeIfChanged
  • merge 顺序统一为 {...managed, ...user, ...managedExtras}(user 字段优先级高)。
  • applyProviderToLive 不再走 writeJsonFile,改为 merge 路径。

4.2 P2 — 未配置才 bootstrap

  • sync 函数前置 if (!fs.existsSync(targetPath)) 短路。
  • 已有路径但对应 block 为空 → 仍走 bootstrap(最小可用配置 + marker)。
  • 已有路径且对应 block 非空 → 走 P1 merge。

4.3 P3 — 凭据隔离

  • 移除所有「把 WeSight key 写到用户文件」的实现。
  • ~/.claude/settings.jsonANTHROPIC_API_KEY 改为 ${WESIGHT_APIKEY_ANTHROPIC} 占位符;spawn 时注入。
  • cc-switch DB 同理。
  • Codex ~/.codex/auth.jsonOPENAI_API_KEY 改为 ${WESIGHT_APIKEY_OPENAI}
  • 增加单元测试:buildEnvForConfig 的输出 + getEnhancedEnvWithTmpdir 的 env 包含真实 key;反之,文件内容永远不含真实 key。

五、复现 / 用例

  1. 用户已有 ~/.codex/config.toml,含自定义 [features] 与注释 → WeSight 保存 model 后这些字段原样保留。
  2. 用户在 WeSight 配置 Anthropic provider → ~/.claude/settings.json 中只有 marker block(env.__wesight_managed 标记),user 原 env.ANTHROPIC_API_KEY / FOO_TOKEN 全部保留。
  3. 用户用 cc-switch → cc-switch DB 中无明文 WeSight key,gateway 启动时由 WeSight 注入。
  4. 用户停用 WeSight(卸载或清配置)→ 所有 WeSight marker block 被识别为孤立可清理,不影响 user 字段。
  5. 用户在两台机器上登录不同账号 → WeSight key 各自隔离,互不污染。

六、实施后可改进

维度 改进
安全 WeSight key 不再落盘到 cc-switch DB / Claude env / Codex auth;降低 key 泄露面
数据完整性 用户本地配置不再被静默改写;删除 WeSight 时其他工具不受影响
可维护性 mergeWithManagedBlock helper 统一所有 sync 逻辑;新增引擎时只需提供 parser/serializer/managedKey
可观察性 所有 WeSight 写入都有 marker;用户和 reviewer 一眼可分辨 WeSight 段
可测试性 当前 externalAgentConfigSync / openclawConfigSync 几乎无单测;helper 化后易于 mock + 断言 marker 行为
跨工具兼容 Claude Code / Codex / OpenClaw / cc-switch 都能与 WeSight 和平共存
符合"产品级桌面应用"定位 桌面应用对外契约必须尊重用户既有环境;这是从"内部工具"到"产品"的标志性改动

七、风险与回滚

  • 风险:cc-switch 占位符语法如果不被 cc-switch 端支持,Stage 4 需要 fallback(保留明文但用 0o600 权限文件 + 警告用户)。
  • 回滚:每个 Stage 独立可发版;Stage 1–3 都不涉及跨进程协议变更,回滚成本低。
  • 灰度:建议 Stage 1 + 2 先在 Claude/Codex 灰度一两个 release,再推广 OpenClaw / OpenCode / Qwen / DeepSeek。

八、验收清单

  • 抽离 mergeWithManagedBlock helper 并被 ≥4 个 sync 函数复用
  • 10 个引擎的 sync 路径都走 marker 化
  • WeSight key 在用户本地文件、cc-switch DB 中均不可见(grep + sqlite 验证)
  • writeIfChanged / syncFileIfChanged 覆盖率 ≥90%
  • 新增 managedConfigBlock.test.ts 覆盖 P1 / P2 / P3 三种场景
  • 用户从「卸载 WeSight」之后,本地 CLI 文件只剩 user 字段、WeSight 段被识别为可清理

九、补充:相关 issue

  • 姊妹 bug issue:列出 6 步可立即修复的具体破坏行为;本 design proposal 的 Stage 1 + 2 一旦合并,bug issue 即可关闭。
  • 姊妹 feature request:依赖本 design proposal 的 helper 完成「LocalCli 统一 + 认证状态探测」。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Status
    In Progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions