Add Phase 5 review cross-check ruling
Concede two errors in the original review: it cited 01-vision.md for a 'perpetual offline use' promise the vision never makes (only 'offline verifiable'), and it described the clock-rollback check too broadly - that check only covers same-list rollback, not an older list paired with a correspondingly rolled-back clock. Accept Codex's O5 (verified: StoreRevocations replaces the cache after signature check with no cross-list monotonicity, so signed revocation lists can be replayed) and O6, plus four precision refinements. Add the threat-boundary rationale for O5: monotonicity does not stop a local attacker but does stop distribution-channel replay, a ~38-day revocation rollback window is exploitable without any clock control, and benign CDN staleness alone silently rolls back revocation state - making the 'reject older list' check a correctness requirement, not just hardening. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -132,3 +132,100 @@ Phase 5 的密码学实现可作为授权基线:指纹派生域分隔且隐私
|
||||
4. **[装配]** O4:随发布配置落地端到端授权,保持"无头 core 已完成、端到端未接通"的准确表述。
|
||||
|
||||
> 声明:本报告为静态审计与攻击路径推演,逐项核了实现(域分隔哈希与 GUID 规范化、base64 重编码回比、9 字段精确形状、`ErrMachineMismatch` 跨机拒绝、`StateFor` 时钟回拨与 revoked 优先、快照净化字段、`IsAuthorized` 状态门禁、双端 `UnconfiguredAuthorizationLoader` 装配);O1/O2 的事实均来自当前文档与代码重读,非记忆推断。因 WSL 无 Go 未执行测试,测试通过为 Codex 自述。
|
||||
|
||||
## Codex 复核补充与整改建议
|
||||
|
||||
### 对原结论的判断
|
||||
|
||||
原审查对 T-501~T-503 的密码学、输入验证、隐私边界和 fail-closed 授权路径的正面结论成立。复核时已重新执行 `go -C core vet ./...`、`go -C core test -count=1 ./...`、`go -C app-modern test -count=1 ./...`、`go -C app-win7 test -count=1 ./...`,并完成 modern/Win7 Windows amd64 主程序交叉构建,均通过。该证据不替代 T-601 的真实 Windows/Win7 环境验证。
|
||||
|
||||
O1、O3、O4 均为合理建议;O2 是当前最需要先裁定的明确 MVP 文档冲突。以下补充修正其优先级、边界和遗漏项。
|
||||
|
||||
### O1 细化 · 明确离线连续性策略,而非默认“永久离线”
|
||||
|
||||
缺失、无效或超过宽限的撤销名单会使 `AuthorizationService` 返回 `unavailable`,因此在**未来接入该授权 composition 后**,所有通过当前启动门禁保护的软件都不能启动。名单有效期最多 31 天、宽限最多 7 天,故从 `generated_at` 起的最大连续离线窗口为约 38 天;若签发的 `expires_at` 更早,窗口会更短。
|
||||
|
||||
这与当前“离线可验证”的实现一致,但 `01-vision.md` 只承诺“离线可验证”,并未承诺“永久许可证可无限期离线使用”。因此该项应由产品显式定义为可接受的离线连续性上限和首次导入分发方式,而不应把“永久使用与更新权分离”视为已经写入的既定事实。若决定放宽,必须先重新设计威胁模型和授权协议,不能在客户端为缺失名单增加 allow-all 回退。
|
||||
|
||||
### O2 细化 · 先消除 P0 冲突,试用协议不要悄然改写 License v1
|
||||
|
||||
`02-requirements.md` 将试用列为 P0 用户能力和 MVP 验收,T-503/API/架构则明确 `perpetual:false` 只展示为非永久状态、不会产生本地到期或试用授权;现状确实不能满足“试用与正式状态在盒子和子软件中一致”。该冲突应优先于后续发布装配关闭。
|
||||
|
||||
产品应二选一:
|
||||
|
||||
1. 若试用仍为 MVP,先新建试用/到期授权任务,并定义可跨盒子与子软件验证的签名期限、时钟和撤销策略;由于 License v1 已冻结九字段,不应静默修改 v1,宜定义兼容的后续协议版本或独立签名授权对象。
|
||||
2. 若试用不再属于 MVP,将其从 `02-requirements.md` 的用户角色、P0 清单和验收标准同步移到后续版本,并把 `perpetual:false` 的产品语义写清,避免被误解为会自动到期。
|
||||
|
||||
### O3 细化 · 分离信任根是降低泄露爆炸半径,不是跨协议解析漏洞
|
||||
|
||||
License v1 与 Revocation List v1 的精确字段形状不同,因此共用密钥不会导致一份文档被另一验证器误解析。风险在于同一私钥泄露后,攻击者可以同时伪造许可证和替换撤销名单。发布端应记录这一信任边界,并与 `key_id`、轮换和紧急吊销设计一起评估独立的许可证/撤销签名密钥;现有 core 的显式注入边界可支持不同公钥,无需为此放宽验证逻辑。
|
||||
|
||||
### O4 细化 · 将 production composition 写入 T-603 的可验收闭环
|
||||
|
||||
双端 production `cmd` 目前故意注入 `UnconfiguredAuthorizationLoader`,所以这不是已部署能力。T-603 的正式任务规格应明确包含:受控生产 trust root、机器摘要采集、许可证文件选择、签名撤销名单获取/刷新、失败诊断、以及干净机器上的导入→授权→启动→撤销验证;不得以 testdata、环境变量、未签名本地文件或 allow-all verifier 代替。M4 在这些端到端证据和子软件侧证据具备前不能宣称完成。
|
||||
|
||||
### O5 · 已签名撤销名单的回滚/replay 风险(新增安全项)
|
||||
|
||||
当前 `LicenseStore.StoreRevocations` 对任意仍能通过签名和格式验证的名单直接原子替换;它不比较现有缓存的 `generated_at`、也没有已签名递增 revision 或受保护的最高水位。攻击者若能重放一份仍在有效期内的旧签名名单,便可能抹去较新名单中的撤销记录;把时钟调回旧名单的有效区间时,现有 `now.Before(generated_at)` 检查也无法识别这种“旧名单 + 旧时钟”的组合。
|
||||
|
||||
这不否定当前针对“同一名单时钟回拨”的 fail-closed 检查,但不能将其表述为完整的时钟/回滚防御。发布协议应增加单调 revision(或等价的受签名序列语义),客户端至少拒绝比已接受名单更旧的远端更新;同时明确:仅依赖可被本机完全恢复的本地文件,无法抵抗拥有本机存储写权限的攻击者,完全防回滚需要可信在线或硬件/系统级锚点。该项应在 T-603 的撤销获取设计前裁定并覆盖重放测试。
|
||||
|
||||
### O6 · 子软件独立验权仍缺跨仓库交付证据(新增闭环项)
|
||||
|
||||
`02-requirements.md` 要求子软件独立启动时也能识别授权;本仓库不含子软件源码是既定边界,不构成当前 core 缺陷。但在生产 trust root、许可证存储位置、撤销刷新和试用语义尚未对外形成可消费契约前,无法证明子软件与盒子得到一致结论。T-603 或其前置跨仓库任务应冻结该消费契约,并在干净机器上用至少一个真实子软件完成“有效、跨机复制、撤销、过期/宽限(如保留)”的一致性验证。
|
||||
|
||||
### 建议处理顺序(复核后)
|
||||
|
||||
1. **产品裁定:O2**。决定试用是否仍为 MVP,并同步需求、API、架构、任务与验收标准。
|
||||
2. **协议/威胁模型:O1 + O5**。定义离线连续性、首次名单分发、名单单调性与回滚威胁边界;没有结论前不接入生产撤销源。
|
||||
3. **发布安全设计:O3**。冻结 trust root、密钥用途、`key_id`/轮换和紧急吊销方案。
|
||||
4. **端到端交付:O4 + O6**。将 production composition 和子软件跨仓库验证写入 T-603 及其验收,完成后再更新 M4/`current-state.md` 的完成表述。
|
||||
|
||||
## 交叉复核裁定(定稿)
|
||||
|
||||
> 复核视角:Claude 全栈开发工程师,对 Codex 复核补充逐条核验后裁定。
|
||||
> 核验方式:grep `01-vision.md` 原文核对引用;读 `core/storage/license_store.go:StoreRevocations` 与全代码 `GeneratedAt` 比较点核验 O5;因 WSL 无 Go,未执行测试。
|
||||
> 日期:2026-07-20
|
||||
|
||||
### 承认原审核错误
|
||||
|
||||
本轮 Codex 复核比原审核更严谨。原审核有**一处文档误引**与**一处表述过宽**,应更正:
|
||||
|
||||
- **误引 `01-vision.md`——原审核错。** 原 O1 写"这与 `01-vision.md` 的…'永久使用与更新权分离'的直观预期存在张力"。核实:`01-vision.md:35` 只写"离线授权 | Ed25519 签名许可证 + 机器绑定,**离线可验证**,支持换绑与恢复",**全文无"永久使用与更新权分离"**。该表述出自最初的 Obsidian 方案笔记,非本仓库愿景文档。愿景只承诺"离线可验证",而当前实现**满足**该承诺。O1 的实质结论(缺名单即全面拒绝、约 38 天上限、需分发方案与产品裁定)仍成立,但**不应以愿景冲突立论**。
|
||||
> 方法教训:这是原审核第二次凭记忆引用文档(Phase 2 原 P3 为第一次)。后续引用文档必须先 grep 原文,不得凭记忆。
|
||||
- **"时钟回拨防御"表述过宽——应限定范围。** 该检查仅覆盖"**同一份名单** + 时钟回拨";对"**更旧的名单** + 相应回拨的时钟"无效,因为此时 `now` 晚于旧名单的 `generated_at`,`now.Before(GeneratedAt)` 不触发。原审核不应将其表述为完整的时钟/回滚防御。
|
||||
|
||||
### 已确认的事实(可作为后续任务前提)
|
||||
|
||||
| 项 | 结论 | 证据 |
|
||||
| --- | --- | --- |
|
||||
| 愿景未承诺永久离线 | 属实(原审核误引) | `01-vision.md:35` 仅"离线可验证";无"永久使用与更新权分离" |
|
||||
| 撤销名单可被重放/回滚 | **属实(原审核漏报)** | `core/storage/license_store.go:StoreRevocations` 验签后**直接 `writeReplacementDocument` 覆盖**;全代码仅有 `revocation.go:117` 与 `authorization.go:199` 两处 `now.Before(GeneratedAt)` 新鲜度检查,**无任何跨名单单调性/高水位比较** |
|
||||
| O1 影响以生产装配为前提 | 属实 | 双端 cmd 现注入 `UnconfiguredAuthorizationLoader`,尚非已部署能力 |
|
||||
| License v1 九字段已冻结 | 属实 | `verifier.go` 精确 9 字段形状校验;试用不得静默改 v1 |
|
||||
| 共用密钥非跨协议解析漏洞 | 属实 | License v1 与 Revocation v1 精确字段形状不同,不会互相误解析;风险为泄露爆炸半径 |
|
||||
|
||||
### 采纳
|
||||
|
||||
- **接受 O5(新增安全项)** 及其定性:当前仅有"同名单时钟回拨"防御,缺单调性;并接受其诚实边界——仅靠本地可恢复文件无法抵抗有本机写权限的攻击者,完全防回滚需可信在线或硬件/系统级锚点。
|
||||
- **接受 O6**(子软件跨仓库消费契约与干净机器一致性验证纳入 T-603 前置)。
|
||||
- **接受四条精修**:O1 以生产装配为前提且**不得为缺名单加 allow-all 回退**;O2 不得静默修改冻结的 License v1,应定兼容后续协议版本或独立签名授权对象;O3 澄清为爆炸半径而非解析漏洞;O4 将 production composition 写入 T-603 可验收闭环、M4 在此前不得宣称完成。
|
||||
|
||||
### 补充:O5 的威胁边界与定级理由
|
||||
|
||||
Codex 已正确指出本地攻击者可绕过。补充三点以明确**为何仍必须实现客户端单调性检查**:
|
||||
|
||||
1. **挡不住本地攻击者,但挡得住分发渠道重放。** 单调性检查若置于 `StoreRevocations`,有本机写权限者可直接覆盖缓存文件绕开;但它能拦截**被污染的 CDN 缓存、恶意镜像或中间代理回放旧名单**——这是它的真实价值域。
|
||||
2. **无需时钟控制即存在约 38 天的撤销回滚窗口。** 旧名单只要仍在自身 `31 天有效期 + 7 天宽限`内即判为 Current/Grace,因此**不碰时钟**、仅回放一份 38 天内的旧名单即可抹掉该期间新增的撤销;回滚更久才需配合时钟。该"无需时钟即可利用"路径使其优先级高于纯理论攻击。
|
||||
3. **良性陈旧同样触发静默回滚。** 因无单调比较,一次拿到过期 CDN/代理缓存的刷新会把较新名单**覆盖成更旧的**。故单调性不只是安全加固,而是**撤销可靠投递的正确性要求**。
|
||||
|
||||
因此建议把"**客户端至少拒绝比已接受名单更旧的更新**"作为最低要求项落地,不仅列为 T-603 设计前裁定。
|
||||
|
||||
### 最终处理顺序(定稿)
|
||||
|
||||
1. **[产品裁定]** O2:裁定试用归属;若保留 MVP,新协议版本或独立签名对象承载试用/到期,**不改冻结的 License v1**;同步需求/API/架构/任务/验收。
|
||||
2. **[协议与威胁模型]** O1 + O5:定义离线连续性上限、首次名单分发方式、**名单单调 revision 语义**与回滚威胁边界;客户端落地"拒绝更旧名单"最低要求;结论前不接入生产撤销源;**任何情况下不得为缺名单加 allow-all 回退**。
|
||||
3. **[发布安全设计]** O3:冻结 trust root、密钥用途、`key_id`/轮换与紧急吊销;记录 License/Revocation 单密钥现状并评估分离。
|
||||
4. **[端到端交付]** O4 + O6:production composition 与子软件跨仓库一致性验证写入 T-603 验收;完成前不更新 M4/`current-state.md` 的完成表述。
|
||||
|
||||
> 裁定:Codex 本轮补充全部成立——纠正原审核一处文档误引与一处表述过宽,新增 O5(真实安全项,原审核漏报)与 O6,四条精修均改进了原结论。Phase 5 的密码学与隐私正面结论(无验签绕过、无跨机复制通过、无原始标识泄露)双方一致,不需回滚或返工;O5 作为新增安全项进入协议/威胁模型处理顺序。
|
||||
|
||||
Reference in New Issue
Block a user