问题描述
WeSight 当前在 WesightModel 模式下保存模型、调用"Apply to Live"、或触发 cowork:config:set 时,会以全文覆写或整块替换的方式修改用户本地 CLI 的全局配置文件,并直接把 WeSight provider key 明文写入这些文件。这违反了三条用户可观察的契约:
- 用户已有的凭据被静默替换(数据被改);
- 用户的非凭据字段(注释、
[features] 块、自定义 provider 条目)被擦除(数据丢失);
- WeSight 自己的 key 离开 WeSight 的管控范围,泄漏到任何能读这些共享配置文件的工具里(安全)。
说明:以下 path:line 形式的引用在 GitHub 中会自动渲染为可点链接(例如 src/main/libs/externalAgentConfigSync.ts:672 会跳转到对应行)。
一、详细问题点 / 受影响代码
| 引擎 |
目标文件 |
代码位置 |
现象 |
严重度 |
| Codex |
~/.codex/config.toml |
src/main/libs/externalAgentConfigSync.ts:672 |
atomicWrite 全文覆写,不读已有 TOML |
HIGH(数据丢失) |
| Codex |
~/.codex/auth.json |
src/main/libs/externalAgentConfigSync.ts:670-676 |
OPENAI_API_KEY 被 WeSight key 替换 |
HIGH(凭据被改) |
| Claude |
~/.claude/settings.json(同步路径) |
src/main/libs/externalAgentConfigSync.ts:319-331, :657 |
env 整块被 WeSight ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN 替换 |
HIGH(凭据被改) |
| Claude |
~/.claude/settings.json(applyProviderToLive) |
src/main/libs/externalAgentProviderStore.ts:1271-1273 |
writeJsonFile 整文件写入 |
HIGH(数据丢失) |
| Codex |
~/.codex/auth.json + config.toml(同上路径) |
src/main/libs/externalAgentProviderStore.ts:1405-1406 |
整文件写入 |
HIGH(数据丢失) |
| OpenClaw |
~/.openclaw/openclaw.json → models.providers |
src/main/libs/openclawConfigSync.ts:941-1000, :1543-1566 |
每次 sync 从 resolveAllEnabledProviderConfigs() 整体重建,user 自加 provider 被擦 |
HIGH(数据丢失) |
| OpenClaw |
~/.openclaw/openclaw.json → gateway / discovery / skills / cron |
src/main/libs/openclawConfigSync.ts:1090+ |
无脑覆写 |
MEDIUM(数据丢失) |
| OpenClaw |
~/.openclaw/exec-approvals.json |
src/main/libs/openclawConfigSync.ts:1758-1768 |
首次 sync 自动把 agents.main 升到 security=full, ask=off |
MEDIUM(安全策略被改) |
| cc-switch |
~/.cc-switch/cc-switch.db → providers.settings_config |
src/main/libs/externalAgentConfigSync.ts:540-588 |
WeSight key 以明文写入 cc-switch DB,任何能读该 DB 的进程都能拿到 |
HIGH(凭据泄漏) |
二、复现步骤
复现 1 — Codex 整文件覆写
- 在
~/.codex/config.toml 加 [features].web_search_request = true 和一行注释
- 在 WeSight 保存任意 model
- 重开
config.toml → 自定义 [features]、注释消失,只剩 WeSight 写入的 block
复现 2 — Claude env 块被替换
- 在
~/.claude/settings.json → env 填 ANTHROPIC_API_KEY=<你自己的真实 key>
- 在 WeSight 保存 model
- 重开
settings.json → env.ANTHROPIC_API_KEY 已被 WeSight key 替换
复现 3 — OpenClaw user provider 被擦
- 在
~/.openclaw/openclaw.json → models.providers 加一个非 WeSight 注册的 provider 条目
- 触发任何一次模型保存 / agent CRUD / IM 配置变更
- 重开
openclaw.json → 你的 provider 条目消失
复现 4 — cc-switch 明文 key 落盘
- 安装 cc-switch + WeSight
- WeSight 自动创建
WeSight - Anthropic 行,其 settings_config JSON 字段含明文 WeSight API key
- 任何进程只需
sqlite3 ~/.cc-switch/cc-switch.db 'select settings_config from providers' 即可拿到
三、期望行为 vs 实际行为
| 维度 |
期望 |
实际 |
Codex config.toml |
user 字段保留;WeSight 只追加 [model_providers.wesight] block |
整文件覆写(externalAgentConfigSync.ts:672) |
Claude settings.json → env |
user 的 ANTHROPIC_API_KEY / 其它 FOO_TOKEN 全部保留;WeSight 用 marker 圈出自己的 block |
env 整块替换(externalAgentConfigSync.ts:319-331, 657) |
OpenClaw models.providers |
spread existing.providers 在前,只 override WeSight 命中的 key |
整体重建(openclawConfigSync.ts:941-1000) |
| WeSight key 落点 |
只在 WeSight sqlite app_config.providers.<n>.apiKey |
明文出现在 4 个共享全局文件 + 1 个 DB |
applyProviderToLive |
走 merge 路径 |
走 writeJsonFile 整文件路径(externalAgentProviderStore.ts:1272, 1405-1406) |
exec-approvals.json |
已有 agents.main 配置时尊重之 |
无脑升 security=full, ask=off(openclawConfigSync.ts:1758) |
四、预计实现路径
按风险从低到高分六步,每步独立可发版:
Step 1 — OpenClaw models.providers spread 顺序修复
- 文件:
src/main/libs/openclawConfigSync.ts:1543-1566
- 改法:把
resolveAllEnabledProviderConfigs() 生成的 managed.providers 改成 {...existing.providers, ...managed.providers},并对每条 managed provider 内部只覆盖匹配 id 的字段。
- 验证:在
~/.openclaw/openclaw.json 手加一个 models.providers.<custom>,触发 sync,重启检查 <custom> 仍在。
- 风险:低;改动局限在 OpenClaw 子树。
Step 2 — Codex config.toml 增量写入
- 文件:
src/main/libs/externalAgentConfigSync.ts:672
- 改法:调用
atomicWrite 前先 readFile 解析现有 TOML;如果已有 model_provider / model 或任何 [[model_providers]] 表,则只 merge/追加 [model_providers.wesight] block 而不覆写。参照 Hermes 的 mergeHermesConfigForWesightModel(src/main/libs/hermesConfig.ts:348-401)的「spread per section + writeIfChanged」结构。
- 验证:见复现步骤 1。
- 风险:中;需要小心 TOML 解析的边界(注释、字符串中的
[ 等),可借助现有的 toml parser。
Step 3 — Claude settings.json → env 标记符化
- 文件:
src/main/libs/externalAgentConfigSync.ts:319-360
- 改法:把
buildClaudeEnvForConfig 改为只输出 WeSight 自己的 env 子集(ANTHROPIC_AUTH_TOKEN / ANTHROPIC_API_KEY / ANTHROPIC_BASE_URL / ANTHROPIC_MODEL);写入时用 marker 标记(同 Hermes .env 的 # >>> WeSight managed: <id> 模式,但因为是 JSON,封装为 env.__wesight_managed = { auth: true, model: true })。下次写入先读旧值,把非 marker 字段保留。
- 风险:中;需要把 marker 当作「WeSight own」与「user own」的分界。
Step 4 — applyProviderToLive 改走 merge 路径
- 文件:
src/main/libs/externalAgentProviderStore.ts:1271-1273, :1405-1406
- 改法:把
writeJsonFile(getClaudeSettingsPath(), settingsConfig) 改为调用 mergeClaudeSettingsWithProvider(src/main/libs/externalAgentConfigSync.ts:333-360)的等价 merge;Codex 同理。
- 风险:低;纯重构,无外部行为变化(merge 之后行为更对)。
Step 5 — cc-switch 明文 key 改为占位符
- 文件:
src/main/libs/externalAgentConfigSync.ts:540-588
- 改法:
upsertCcSwitchClaudeProvider 写入的 settings_config 中,ANTHROPIC_API_KEY 改为 ${WESIGHT_APIKEY_ANTHROPIC} 占位符;cc-switch 启动时由 WeSight 注入的 env 解析(仿 OpenClaw 的 WESIGHT_APIKEY_* 机制,src/main/libs/openclawConfigSync.ts:1606-1633)。
- 风险:中;cc-switch 自身可能不支持占位符语法,需先确认。
Step 6 — exec-approvals.json 加"不升级"守卫
- 文件:
src/main/libs/openclawConfigSync.ts:1758-1768
- 改法:仅在
~/.openclaw/exec-approvals.json 不存在或没有 agents.main 时写入默认值;否则保留。
- 风险:低;可立刻发版。
五、实施后可改进
- 数据安全:用户已有的 CLI 配置不再被 WeSight 静默改写;cc-switch DB 不再含明文 WeSight key。
- 多工具共存:用户在 Claude Code、Codex、cc-switch、OpenClaw 之间切换时,每个工具的本地配置互不污染。
- 可审计性:所有 WeSight 写入都有 marker 标记,用户可一眼看出"哪部分是 WeSight 管的",可以随时删 WeSight 段而不影响其他字段。
- 降低支持成本:减少"我升级 WeSight 之后 Claude Code 报 invalid key"类工单。
- 后续 feature 解锁:mark 化后,可以支持"暂时禁用 WeSight 对该 CLI 的管理"等用户可控制开关。
六、验证清单
七、补充:相关 issue
- 详细的产品级设计动机和原语复用方案见姊妹 design proposal。
- 配套的 renderer 侧增强(认证状态探测、UI 徽章)见姊妹 feature request。
问题描述
WeSight 当前在
WesightModel模式下保存模型、调用"Apply to Live"、或触发cowork:config:set时,会以全文覆写或整块替换的方式修改用户本地 CLI 的全局配置文件,并直接把 WeSight provider key 明文写入这些文件。这违反了三条用户可观察的契约:[features]块、自定义 provider 条目)被擦除(数据丢失);一、详细问题点 / 受影响代码
~/.codex/config.tomlsrc/main/libs/externalAgentConfigSync.ts:672atomicWrite全文覆写,不读已有 TOML~/.codex/auth.jsonsrc/main/libs/externalAgentConfigSync.ts:670-676OPENAI_API_KEY被 WeSight key 替换~/.claude/settings.json(同步路径)src/main/libs/externalAgentConfigSync.ts:319-331, :657env整块被 WeSightANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN替换~/.claude/settings.json(applyProviderToLive)src/main/libs/externalAgentProviderStore.ts:1271-1273writeJsonFile整文件写入~/.codex/auth.json+config.toml(同上路径)src/main/libs/externalAgentProviderStore.ts:1405-1406~/.openclaw/openclaw.json → models.providerssrc/main/libs/openclawConfigSync.ts:941-1000, :1543-1566resolveAllEnabledProviderConfigs()整体重建,user 自加 provider 被擦~/.openclaw/openclaw.json → gateway / discovery / skills / cronsrc/main/libs/openclawConfigSync.ts:1090+~/.openclaw/exec-approvals.jsonsrc/main/libs/openclawConfigSync.ts:1758-1768agents.main升到security=full, ask=off~/.cc-switch/cc-switch.db → providers.settings_configsrc/main/libs/externalAgentConfigSync.ts:540-588二、复现步骤
复现 1 — Codex 整文件覆写
~/.codex/config.toml加[features].web_search_request = true和一行注释config.toml→ 自定义[features]、注释消失,只剩 WeSight 写入的 block复现 2 — Claude env 块被替换
~/.claude/settings.json → env填ANTHROPIC_API_KEY=<你自己的真实 key>settings.json→env.ANTHROPIC_API_KEY已被 WeSight key 替换复现 3 — OpenClaw user provider 被擦
~/.openclaw/openclaw.json → models.providers加一个非 WeSight 注册的 provider 条目openclaw.json→ 你的 provider 条目消失复现 4 — cc-switch 明文 key 落盘
WeSight - Anthropic行,其settings_configJSON 字段含明文 WeSight API keysqlite3 ~/.cc-switch/cc-switch.db 'select settings_config from providers'即可拿到三、期望行为 vs 实际行为
config.toml[model_providers.wesight]blockexternalAgentConfigSync.ts:672)settings.json → envANTHROPIC_API_KEY/ 其它FOO_TOKEN全部保留;WeSight 用 marker 圈出自己的 blockenv整块替换(externalAgentConfigSync.ts:319-331, 657)models.providersexisting.providers在前,只 override WeSight 命中的 keyopenclawConfigSync.ts:941-1000)app_config.providers.<n>.apiKeyapplyProviderToLivewriteJsonFile整文件路径(externalAgentProviderStore.ts:1272, 1405-1406)exec-approvals.jsonagents.main配置时尊重之security=full, ask=off(openclawConfigSync.ts:1758)四、预计实现路径
按风险从低到高分六步,每步独立可发版:
Step 1 — OpenClaw
models.providersspread 顺序修复src/main/libs/openclawConfigSync.ts:1543-1566resolveAllEnabledProviderConfigs()生成的managed.providers改成{...existing.providers, ...managed.providers},并对每条 managed provider 内部只覆盖匹配 id 的字段。~/.openclaw/openclaw.json手加一个models.providers.<custom>,触发 sync,重启检查<custom>仍在。Step 2 — Codex
config.toml增量写入src/main/libs/externalAgentConfigSync.ts:672atomicWrite前先readFile解析现有 TOML;如果已有model_provider/model或任何[[model_providers]]表,则只 merge/追加[model_providers.wesight]block 而不覆写。参照 Hermes 的mergeHermesConfigForWesightModel(src/main/libs/hermesConfig.ts:348-401)的「spread per section + writeIfChanged」结构。[等),可借助现有的tomlparser。Step 3 — Claude
settings.json → env标记符化src/main/libs/externalAgentConfigSync.ts:319-360buildClaudeEnvForConfig改为只输出 WeSight 自己的 env 子集(ANTHROPIC_AUTH_TOKEN/ANTHROPIC_API_KEY/ANTHROPIC_BASE_URL/ANTHROPIC_MODEL);写入时用 marker 标记(同 Hermes.env的# >>> WeSight managed: <id>模式,但因为是 JSON,封装为env.__wesight_managed = { auth: true, model: true })。下次写入先读旧值,把非 marker 字段保留。Step 4 —
applyProviderToLive改走 merge 路径src/main/libs/externalAgentProviderStore.ts:1271-1273, :1405-1406writeJsonFile(getClaudeSettingsPath(), settingsConfig)改为调用mergeClaudeSettingsWithProvider(src/main/libs/externalAgentConfigSync.ts:333-360)的等价 merge;Codex 同理。Step 5 — cc-switch 明文 key 改为占位符
src/main/libs/externalAgentConfigSync.ts:540-588upsertCcSwitchClaudeProvider写入的settings_config中,ANTHROPIC_API_KEY改为${WESIGHT_APIKEY_ANTHROPIC}占位符;cc-switch 启动时由 WeSight 注入的 env 解析(仿 OpenClaw 的WESIGHT_APIKEY_*机制,src/main/libs/openclawConfigSync.ts:1606-1633)。Step 6 —
exec-approvals.json加"不升级"守卫src/main/libs/openclawConfigSync.ts:1758-1768~/.openclaw/exec-approvals.json不存在或没有agents.main时写入默认值;否则保留。五、实施后可改进
六、验证清单
[features]+ 注释仍在config.tomlANTHROPIC_API_KEY/FOO_TOKEN仍在settings.jsonmodels.providers.<custom>仍在openclaw.jsonsettings_config不含明文 key(只有${WESIGHT_APIKEY_*})exec-approvals.json不被自动覆写mergeClaudeSettingsWithProvider保留__wesight_managed之外的字段;buildCodexConfig在已有 user 字段时走 merge 路径七、补充:相关 issue