OpenProgram · 历史设计归档 · 2026-08-13

已禁用的文件凭据加固设计

本文保留旧方案及实施记录,仅供追溯,不再作为当前实现要求。

当前决定:凭据继续使用普通本地文件读写。0600/0700 强制、owner/type/symlink/inode 检查、权限审计与修复和 revision conflict 均已禁用。只保留避免并发丢写所需的普通写锁。除非用户以后单独明确提出,不得重新启用旧加固方案。

当前实现
普通文件 I/O 只保留数据清单、备份脱敏和兼容接口。
本文状态
已拒绝 以下设计内容不再进入实现计划,正文与证据仅为归档。
代码位置
openprogram/auth/credentials/safety/legacy.py。当前生产代码不导入、不使用;除非用户以后单独明确提出,否则保持停用。
归档说明。 下文从"凭据使用流程"开始是旧方案原文与其集成实施记录。出现的 0600、0700、ACL、symlink、owner、inode、doctor/repair 和锁要求均不是当前产品合同。

0当前实现与边界

当前生产代码采用普通本地文件 I/O。后续第 1–5 节保留旧加固方案的设计边界、参考资料和验收模型,全部属于归档提案,不是当前实现状态。

当前写入

openprogram/auth/credentials/io.py 保留基本跨进程写锁、临时文件写入、flush/fsync 和 atomic replace,用于避免部分写入与丢写;它不检查权限、owner、symlink、inode 或 revision。

当前清单

openprogram/auth/credentials/inventory.py 提供秘密路径和字段清单,用于审计、备份和兼容接口;清单本身不启用旧加固 writer。

停用代码

openprogram/auth/credentials/safety/legacy.py 及其停用测试保留为归档材料。生产代码不导入 safety 包,也不提供 doctor/repair 的旧加固合同。

明确排除

0600/0700 强制、ACL/owner/symlink/inode 校验、revision conflict、旧 restore journal 和系统凭据库均不属于当前产品合同;重新设计必须由单独需求明确提出。

状态边界。 后文出现的 0600、0700、ACL、symlink、owner、inode、doctor/repair、revision conflict 和 restore journal,只描述被拒绝的历史提案,不能作为当前实现证据。

1批准设计时基线:秘密清单和证据边界

本节保留旧加固提案的设计时基线。清单按当时写入代码逐项核对,而不是按目录名推断。混合文件只把明确字段视为秘密;临时令牌也进入清单,但备份策略与长期凭据不同。当前实现状态以第 0 节为准。

文件/字段秘密性质当前写入保障已确认缺口
auth/<provider>/<account>.json
profiles/<name>/auth/…
AuthStore pool:API key、OAuth、现有 credential_process 配置等认证记录较完整 0600-at-create、文件 fsync、replace、restrict_to_user、稳定 lock、外部修改检测固定 .tmp;无 O_NOFOLLOW;无目录 fsync;不同 writer 尚未共用同一契约
config.json[api_keys]混合配置中的 provider API keys最终文件以 0600 打开;更新路径有进程内锁和可用时的 FileLock直接 O_TRUNC 最终文件;无临时文件、fsync、replace、权限验证;“atomic”注释不符合崩溃语义
profiles/<name>/.envAccount 环境变量凭据同目录普通临时文件后 replace临时文件创建时不是 0600;无 fsync、restrict_to_user、符号链接防护或跨进程锁
channels/<channel>/accounts/<id>/credentials.json渠道账号凭据replace 后调用 restrict_to_user;进程内 RLock创建先 write_text 后收紧;固定 .tmp;无 0600-at-create、fsync、O_NOFOLLOW 或跨进程锁
channels/<channel>/access.json 的 pending pairing code短期认证材料;同文件 allowlist 是元数据mkstemp 私有创建、chmod、replace、FileLock无文件/目录 fsync、最终权限验证;临时 pairing code 不应进入备份
mcp_servers.json 的 env、headers、bearer、OAuth client secret混合配置中的秘密;任意 env/header 值均按秘密处理较完整 0600-at-create、O_NOFOLLOW、文件 fsync、replace、restrict_to_user固定临时名;无目录 fsync;读改写没有统一跨进程锁
mcp_tokens/<server>.jsonaccess/refresh token 及客户端认证信息0600、O_NOFOLLOW、replace、restrict_to_user写入未 flush+fsync;固定临时名;无目录 fsync或跨进程锁
web/tokenWeb 运行期 bearer token较完整 随机同目录临时名、O_EXCL/O_NOFOLLOW、0600、文件 fsync、replace、权限限制、回读验证、生命周期锁无目录 fsync;属于 never_backup,关闭时仅逻辑删除

已核对为非秘密的状态

channels/…/account.json 是账号元数据;owner.json 是稳定 principal ID;WeChat cursor.json 是运行进度游标;web/access.json 记录 bind/origin/fingerprint。它们可以继续采用私有权限,但不进入“原始秘密不可返回”和 credential backup 的清单。若字段含义以后改变,必须先更新清单再写入。

目录和跨平台基础

ensure_state_dir 已在 POSIX 将 profile root 收紧为 0700,这是已有纵深保障,不能代替每个文件的创建时权限与验证。restrict_to_user 当前在 POSIX 使用 chmod 0600,在 Windows 以固定 argv 调用 icacls 移除继承并授权当前用户与 SYSTEM,但它是 best-effort 并吞掉失败;目标实现必须验证结果并把失败返回给调用方。

2批准设计时的当前行为与目标行为

2.1 用户录入或导入秘密

当前行为与风险

setup、账号管理、Channel、MCP API/CLI 各自解析输入。Web config 和 Account 接口已拒绝把显示用 mask 当成新 key;MCP 的合并逻辑能处理结构化 masked 响应,但提取出的纯 mask 字符串仍可能被作为新值写回。导入/恢复与交互录入没有共用秘密字段清单。

需要完善与完成后行为

所有入口使用三态更新:字段省略表示保留,显式 delete 表示删除,只有通过格式验证的新明文表示替换。任何 mask、presence marker 或 redacted sentinel 均拒绝写入。导入先按清单分类和验证,再进入同一 writer。

2.2 OpenProgram 保存

当前行为与风险

AuthStore、MCP config、Web token 已有多数原子写保障;config.json 可能在崩溃时截断,Account/Channel 临时文件可能以宽权限出现,MCP token 可能 replace 尚未落盘的数据。固定临时名还扩大了并发和符号链接风险。

需要完善与完成后行为

所有清单内 writer 只调用共享 private atomic writer;先写同目录唯一私有临时文件,fsync 后原子 replace,再持久化目录项并验证 owner-only 权限。读改写在稳定锁下执行,冲突不静默覆盖。

2.3 运行时解析与使用

当前行为与风险

运行时从多个文件解析 provider、Account、MCP 和 Channel 凭据。既有 credential_process 只按 argv 执行、shell=False,有超时、解析规则和进程内 cache;这是现有明确能力,不是通用 secret backend。

需要完善与完成后行为

读取前验证路径类型、owner 和权限;拒绝 state root 之下的符号链接、非普通文件和外来 owner。读取失败只报告凭据类别、路径和修复动作,不记录值。设计不增加外部取密钥命令,也不扩大 credential_process 契约。

2.4 用户查看、编辑与删除

当前行为与风险

Web config、Auth CLI 和 MCP 的常规列表响应多数只返回 has_value 与 mask;但账号管理仍有按账号返回完整 API key 的 reveal 端点和界面按钮,mcp edit 也会把原始 mcp_servers.json 交给 $EDITOR。Channel 删除使用 rmtree(ignore_errors=True),可能把失败显示为成功。

需要完善与完成后行为

移除账号 key reveal 的 API、Web 与 CLI 入口;旧 reveal 路由返回稳定的弃用错误,不返回原值。MCP 改为结构化 masked 编辑,不再由产品打开原始秘密文件。其他 API/Web/CLI 同样不得返回原始秘密。删除在锁内执行、清理运行时 cache、验证文件不存在并明确返回失败;不声称对 SSD、日志结构文件或备份执行安全擦除。

2.5 备份、恢复与迁移

当前提示与实际范围不一致。 默认 INCLUDED 包含带 api_keys 的 config.json、带 credentials.json 的 channels/、带 env/header/bearer/OAuth secret 的 mcp_servers.json;CREDENTIAL_ENTRIES 却只包含 auth 与 mcp_tokens。因此未传 --include-credentials 时虽然打印 “credentials excluded”,默认归档仍可能含秘密。profile AuthStore 和 .env 又不在完整包含清单中。当前备份不能描述为已经安全。

当前行为与风险

backup create 有显式 --include-credentials 和警告,但分类依赖顶层名称;归档是创建后 chmod,缺少 0600-at-create、fsync 与目录 fsync。restore 校验路径并拒绝符号链接,但使用 tar.extractall 后没有按秘密清单重新限制和验证,也没有多文件崩溃事务。顶部“迁移到 system keychain”注释已经过时。

需要完善与完成后行为

保留显式 opt-in 产品语义,不自动改成默认包含凭据。默认备份按字段清除 api_keys、MCP secret 和 Channel credential,排除 profile .env/AuthStore 及 pending pairing code;恢复缺失或 redacted 字段时保留本机现有秘密。只有明确 --include-credentials 和警告后才包含清单允许的长期秘密;web/token 与 pairing code 永不备份。

恢复先在 state parent 下的同一文件系统 staging 目录验证 manifest、成员路径、文件类型、JSON 结构和 secret inventory;拒绝 symlink、hardlink、未知秘密路径和越界成员。每个目标以共享 writer 发布,并记录恢复 journal。发布前失败不改变旧状态;中途失败按 journal 反向恢复旧文件。权限修复和最终验证全部成功后才报告完成。默认 pre-restore 备份不擅自获得凭据权限;执行带凭据恢复时,用户的同一显式授权允许 pre-restore snapshot 包含凭据,并再次显示明文备份警告。

2.6 诊断与故障恢复

当前行为与风险

写入路径各自补 chmod/ACL,缺少一个能发现历史 0644 文件、外来 owner、symlink、残留临时文件和不完整 restore 的清单驱动诊断。Windows ACL 失败目前可能被吞掉。

需要完善与完成后行为

openprogram doctor credentials 只输出类别、路径和状态;--repair 只修复当前用户拥有的普通文件/目录,不跟随 symlink,不接管其他 owner。无法验证 owner-only 时返回非零。残留临时文件只在取得对应锁且满足命名和年龄规则时删除。

3其他项目怎么设计

对比只使用官方文档/源码或仓库已有 reference corpus。外部项目均按本文日期可见文档或本仓固定源码快照描述,不代表最新版本,也不把文档声明等同于端到端运行验证。完整版本边界见 feature matrix 的方法与证据。

项目可确认设计对本设计的使用方式证据范围
Claude Code官方文档声明凭据安全存储;gateway 文档提供有 TTL 和明确优先级的 apiKeyHelper。采用明确优先级、缓存边界和不回显原则;不因其系统存储选择而采用 Keychain。官方 getting started;官方 gateway 文档;本仓快照矩阵
Codex本仓固定快照记录可选 keyring store;具体能力受版本与配置限制。仅作为竞品事实保留;OpenProgram 明确拒绝该 backend,采用文件契约与审计。本仓 reference corpus 方法表;官方配置参考
OpenClawSecretRef 支持 env/file/exec,启用与解析有 preflight/audit;plaintext 兼容仍存在,OAuth/运行时生成材料不都归入只读 ref。修改采用其“凭据面清单驱动验证/审计”思路;拒绝增加 env/file/exec backend。官方 credential surface;官方 secrets 文档
OpenCode官方 provider 文档明确本地凭据文件 ~/.local/share/opencode/auth.json。说明本地文件方案是可独立成立的产品选择;不据此推断其原子写、权限修复或备份成熟度。官方 provider 文档;本仓固定源码快照
Hermes本仓源码引用显示其 auth 文件路径采用 advisory lock、版本字段与 lock timeout;另有 backup/import 能力。采用稳定锁和版本冲突思路;备份安全仍按 OpenProgram 自身 secret inventory 验收。官方源码仓库;本仓固定源码快照
CodeBuddy本仓 npm 快照记录系统凭据存储与多种凭据入口。只保留竞品事实;不把支持 Keychain 当成 OpenProgram 必须采用的理由。本仓 feature matrix 记录的 codebuddy 2.109.3 包快照
pi-mono本仓固定快照仅提供部分相关证据,不足以确认完整凭据持久化、权限和备份契约。不纳入设计依据,不对成熟度作推断。本仓固定源码快照

4我们后续怎么计划

4.1 采用、修改、拒绝

采用 文件写入基本契约

owner-only 创建、同目录原子 replace、文件 fsync、目录 fsync、稳定 lock、版本冲突、清单驱动审计、秘密不回显。

修改 清单和备份设计

SecretRef 类项目的 credential surface 思路改为 OpenProgram 固定文件/字段清单;清单同时驱动 writer、backup、restore、doctor 和测试。

拒绝 新 secret backend

不采用 Keychain、keyring、加密数据库;不新增通用 env/file/exec 密钥解析或任意 shell 命令。现有 credential_process 不扩张。

4.2 单一 secret inventory

引入静态、可测试的注册表。每条记录至少包含路径 matcher、秘密字段 matcher、writer、生命周期(persistent/ephemeral)、备份策略(redact_default、include_on_opt_in、never_backup)和删除动作。任何新持久化秘密若未注册,测试直接失败;备份不得再只看顶层目录。

4.3 private atomic writer 的机械契约

  1. 路径:先解析并验证 state root。允许用户显式配置的 state root 自身是指向当前用户拥有、owner-only 目录的别名;root 以下任何路径分量、最终文件或临时文件为 symlink 时均拒绝。使用 lstat,只接受当前用户拥有的普通文件/目录。
  2. 目录:POSIX 秘密目录必须是 0700;Windows 必须移除继承并只授权当前用户与 SYSTEM。创建前后都验证,不以父目录权限替代文件权限。
  3. 临时文件:在目标同一目录用不可预测唯一名称和 O_CREAT|O_EXCL|O_NOFOLLOW 创建,POSIX mode 为 0600;Windows 创建后立即应用并验证 owner-only ACL。禁止固定 .tmp。
  4. 写入:完成序列化后写入全部字节,flush 并 fsync 临时文件;在发布前再次验证 owner 和权限。
  5. 发布:只以同目录 os.replace 发布。因此临时文件与目标天然同一文件系统,不会发生 EXDEV;如果实现偏离而出现跨文件系统错误,直接失败,禁止回退为 copy。
  6. 目录持久化:POSIX 对父目录执行 fsync,保证 rename 的目录项持久化。Windows 的 Python 路径没有可移植目录 fsync,验收标准是文件 flush/fsync、原子 Replace/Move 语义和 ACL 回读验证,并明确记录该平台差异。
  7. 最终验证:replace 后再次限制并验证最终文件。权限验证失败必须返回错误,不能记录成功;临时文件在 finally 中按已验证路径清理。
  8. 失败语义:replace 前失败保持旧文件;replace 失败保持旧文件;replace 成功而目录 fsync 失败时新文件已可见,返回结构化 committed_not_durable,不再以旧内容反向覆盖。

4.4 并发和外部编辑

每个逻辑记录使用 owner-only 稳定 sibling lock,进程内锁与跨进程 lock 同时保留,锁覆盖完整 read-modify-write。API/Web 更新携带 revision/ETag;读取时计算版本指纹。手工编辑器不持锁,因此写入前发现版本变化就返回 conflict,不静默覆盖。固定临时文件名全部移除。

4.5 权限审计与修复

4.6 错误、删除和日志边界

错误类型区分 permission、ownership、symlink、conflict、serialization、replace、fsync 和 committed_not_durable。日志、异常、遥测和 doctor 输出只包含 credential kind、相对路径、operation、errno/ACL 状态,不包含原始值、命令 stdout 或完整 header/env。删除是逻辑 unlink/rmtree 后验证,不承诺介质安全擦除;备份中的旧值直到用户 prune 或保留期结束仍可能存在。

4.7 兼容与实施批次

批次内容完成条件
批次 0先建立 inventory,并把 private atomic writer 的最小实现用于备份归档本身;随后修正默认排除/字段 redaction、过时 Keychain 注释和恢复后的权限验证。备份归档必须从首次可见起即为 owner-only,不能等批次 1 再修。默认归档扫描不到任何注册秘密;opt-in 归档精确覆盖允许备份的长期秘密;归档创建权限、fsync/replace 与警告、manifest 一致。
批次 1实现共享 private atomic writer;迁移 config、Account .env、Channel、MCP token;再让 AuthStore、MCP config、Web token 使用同一 primitive。所有 writer 通过相同 POSIX/Windows 契约测试,无固定 tmp 或直接截断最终秘密文件。
批次 2secret inventory、doctor/repair、masked 三态编辑、删除结果验证;移除产品内原始 MCP 文件编辑入口。历史权限、符号链接、mask 回写、原始秘密响应和删除失败均有负向测试。
批次 3staged restore、journal/rollback、完整 manifest、跨平台 backup/restore 验收。注入各故障点后旧状态或新状态之一完整成立,不出现静默部分成功。

4.8 测试矩阵

维度必须覆盖的用例机械判定
创建与权限umask 000/022;首次创建;覆盖 0644 历史文件;0700 目录;Windows ACL 继承开启/关闭秘密从首次可见起即 owner-only;最终权限回读通过
崩溃持久化写入中、文件 fsync 前后、replace 前后、目录 fsync 失败旧/新完整值;无截断 JSON;目录 fsync 失败返回 committed_not_durable
并发线程、两个进程、手工外部修改、锁超时无丢失更新;冲突或超时明确返回
路径攻击目标 symlink、任一路径分量 symlink、预置固定 tmp、非普通文件、外来 owner拒绝且不修改链接目标;实现没有固定 tmp
文件系统同目录 replace;模拟 EXDEV;POSIX 目录 fsync;Windows Replace/Move + ACL 回读不发生 copy fallback;平台差异符合契约
显示与编辑API/Web/CLI GET;mask 原样提交;省略/替换/删除;MCP structured edit响应无原文;mask 不可写入;三态一致
备份默认、--include-credentials、mixed JSON、profile、Channel、MCP、runtime token默认零原始秘密;opt-in 只含允许项;runtime/pairing token 永不进入;manifest 与提示一致
恢复与删除恶意 tar、权限过宽、symlink、字段缺失、恢复中崩溃、删除失败、旧备份残留验证失败不发布;缺失秘密保留;journal 可恢复;删除不虚报;不声称安全擦除

5feature matrix 实心圆 gate

“凭据存操作系统钥匙串”继续保持 OpenProgram ·,并标为评估后拒绝/非计划项。它不是文件加固的验收代理。当前矩阵不新增“文件凭据持久化一致性”能力行,避免把设计状态误计为实现状态。

将来只有同时满足以下条件,才可新增独立能力行并标实心圆:
  1. 所有由 OpenProgram 持久化的秘密文件/字段均注册在 inventory,且无未登记 writer;
  2. 全部 writer 在 POSIX 和 Windows 通过共享创建权限、原子写、fsync/replace、ACL/owner 验证、并发与 symlink 测试;
  3. doctor/repair 能发现并按边界修复历史权限,拒绝 symlink 与外来 owner;
  4. 默认备份含零原始秘密,opt-in 与 inventory 精确一致,恢复会验证并重新限制权限;
  5. API/Web/CLI 不返回原始秘密,mask/sentinel 不可作为新秘密写入;
  6. 崩溃、目录 fsync、冲突、删除和 restore journal 的失败语义全部有自动化测试。

满足后新增行时,必须同时重算功能总数、OpenProgram 得分、候选数、旧/新增差异数和页面叙述。

6当前代码定位

当前凭据文件实现位于 openprogram/auth/credentials/。生产代码不导入 safety/legacy.py;实际 writer 行为以 credentials/io.py、清单以 credentials/inventory.py 为准。本文不把旧提案中的 commit、测试数量或审查结果作为当前实现证据。

当前状态仅保留普通写锁、atomic replace 和 fsync 等基本写入保障。0600/0700、owner/symlink/inode 检查、权限修复、revision conflict、restore journal 和旧 doctor/repair 流程均未启用。

相关文档:统一认证存储 · 凭据验证统一 · 功能矩阵。本文是已拒绝方案的历史归档。