模型文件能解密还不够:为 NPU 资产建立独立 CI 门禁
模型文件进入仓库后,最初的检查只有“程序能不能读取”。后来需求提高到“模型必须加密”,第一版 verifier 又只证明“能解密”。这两种检查都不够,因为它们没有回答:提交的是不是规定格式的加密资产,解密出来的内容又是不是我们批准的那个模型。
这次 CI 改造最终把模型资产拆成了三层完整性合同。
第一次误判:主工程能编译,模型就没问题
模型通常不是 C++ 编译输入。即使文件缺失、未加密或内容被替换,主 ROS/workspace build 也可能完全通过。
把模型检查埋在大型 build job 里还有两个问题:反馈慢,失败原因也容易被海量编译日志淹没。策略门禁应该独立表达,而不是顺带执行。
第二次误判:能解密就代表资产正确
“解密成功”只能证明密钥与文件格式大致匹配,不能证明:
- 仓库里提交的一定是加密 envelope,而不是明文模型;
- envelope 内部一定是目标模型;
- 模型更新时 lock file 已同步;
- 打包脚本、服务默认值和 health check 指向同一份资产。
如果 verifier 只运行 decrypt 命令,错误模型同样可以顺利通过。
最终合同:Envelope、Decrypt、Hash
独立 CI gate 执行三步:
1. 检查文件必须具有规定的 encrypted envelope
2. 使用 CI secret 解密到临时空间
3. 计算内层 payload SHA-256,并与 model.lock 比较model.lock 是批准内容的权威记录。它校验的是解密后的 payload,而不是外层密文,因为密文可能随着 nonce、header 或加密实现变化而变化。
这样可以明确区分:
- 保密性策略:仓库中不得出现明文模型;
- 可用性:CI 环境能够正确解密;
- 完整性:解密后的内容与批准版本一致。
为什么必须是独立 Job
独立 job 的价值不只在速度。它让模型策略成为 pipeline 上可见、可审计的 gate:
- 只在模型或 lock file 变化时触发;
- 几秒内给出明确失败原因;
- 不依赖完整 ROS workspace 编译;
- 文档和 contract test 可以直接引用同一规则;
- 主 build 失败与资产策略失败不会混在一起。
一次环境踩坑:Verifier 只在 CI 里能编译
独立 verifier 最初假定固定的 OpenSSL 头文件路径,本地 macOS 环境直接编不过。这个问题如果没有本地复现,很容易误判为“CI 专用工具无需兼容”。
修复方式是优先通过 pkg-config 或包管理器前缀解析依赖,把 Linux CI 和本地开发环境的差异显式处理。策略工具越独立,越应该减少对完整工程环境的隐式依赖。
策略必须同步到哪些地方
模型文件名或路径改变时,需要把以下位置视为同一合同:
- CI trigger 与 verifier;
model.lock;- 打包 payload;
- systemd/launch 默认路径;
- health check;
- 模型目录 README;
- contract test。
只改其中一处,会出现“CI 绿、包里还是旧模型”或“服务启动后找不到新路径”的漂移。
这条门禁不能证明什么
它不是强 DRM,也不证明设备绑定、防内存抓取或模型在运行时没有被替换。它证明的是仓库与交付流水线中的资产符合规定格式、能被正确解密,并且内容与 lock file 一致。
把边界写清楚很重要。一个成功的 SHA-256 gate 不能被包装成完整运行时安全方案。
结论
二进制模型不是普通附件,而是需要版本、完整性和保密性合同的运行时资产。把检查拆成独立 CI gate,能够让“必须加密、必须可解密、必须内容匹配”成为不可绕过的交付规则。