降级态密文保护(INV-PERSIST-07)
wesgine 中 CellSpec 密钥降级后的回写保护机制,防止打不开的密文被空值覆盖。
问题场景
缺陷是三步,每步都成功、都不报错:
spec.key丢失 →decodeSpec打不开 inline provider key,清空该字段、点名、放 Cell 挂上- Cell 正常跑 → 有人改名 / 加 MCP / 编辑渠道
- 覆盖 → 那次写把已成空串的字段重新封密,
Seal("") == "",omitempty丢字段,磁盘上的密文没了
运维后来从备份恢复 spec.key,发现凭据不在。历史里没有一行说是谁花掉的——花掉它的那次写是关于渠道的。
台账机制
两侧台账在修复之前就存在,也已挂上 Observe 快照——只是没有任何写入路径查过它们。
// Hypervisor 持有
h.degradedSecrets map[string][]string // cellID → 降级字段路径
h.degradedSharedProviders map[string][]string // providerName → 降级字段路径
三扇写门、三种答案
门 1:persistCellSpec(拒绝)
查 h.degradedSecrets[spec.ID]:台账点名的每个字段路径都在本次写入里带非空值才放行。
func (h *cellHandles) persistCellSpec(spec CellSpec) error {
degraded := h.degradedSecrets[spec.ID]
if len(degraded) > 0 {
// 逐字段遍历:台账里的每个路径必须在 spec 中有非空值
missing := config.WalkSecretRefs(spec, degraded)
if len(missing) > 0 {
return ErrDegradedSecretOverwrite // 409
}
}
// ...
}
门 2:Cells().Create(纯 INSERT)
撞行即错,够不到已存在的行——安全性完全建立在"够不到已存在的行"上。
门 3:convergePersistedSpec(原样保留)
把 decode 留下的 SealedOriginals 放回:
func convergePersistedSpec(got persistedSpec, spec CellSpec) {
// 未被改动的字段:用 got.SealedOriginals 原样放回
// 已改动的字段:用新值
}
只对刚读出来、未被改动的那份文档成立。
写成功后重算台账
必须做。否则症状比原缺陷更像 bug:
用户重填凭据 → 保存成功 → 下一次不相干的编辑仍被拒,理由是一个现在明明打得开的字段 → 守卫被删 → 缺陷穿着修复的衣服回来。
共享 Provider 池
池的处理与 spec 不同:
| Spec | 共享池 | |
|---|---|---|
| 处置 | 拒绝 | 合并 |
| 理由 | 整体文档,逐字段合并无定义 | 按名字数组,合并语义数据定义 |
池拒绝会让运维改 A provider 被 B 的坏 key 扣为人质。
判据放在 SharedProviderStore.Save 内部——两个调用方自动受保护。
封密顺序
封密(Seal)→ 放回保留字节
不是:
放回保留字节 → 封密
secretbox.Seal 对已封密的值直接报错。只对 ref.Value == "" 的字段放回——重填凭据的人必须赢过保留机制。
闸门规则
65-degraded-secret-no-writeback.sh 八条规则:
persistCellSpec读h.degradedSecrets[且遍历字段Cells().Create是纯 INSERTconvergePersistedSpec传SealedOriginals- 写门只有三扇——第四条核对数量
- 写成功后清台账
- 共享池在
Save内部保留 ON CONFLICT DO UPDATE不出现在 CREATE 路径- 点名七个残差测试(不是数个数)
反模式
| 禁止 | 正确做法 |
|---|---|
| 降级挂载后 re-seal 整个 spec | persistCellSpec 查台账拒绝 |
守卫函数体改成 return nil | 闸门正向断言函数体仍有读取 |
| 写成功后不清台账 | 台账在 boot 时由 decode 刷新,运行期写入必须自己说 |
| 新增第四个 encodeSpec 调用方 | 三扇门三种答案,第四种不适用 |
给 CREATE 加 ON CONFLICT DO UPDATE | Create 的安全性建立在纯 INSERT |