Mini Demo:纯 Python 实现供应链安全最小闭环
本章配套的 Mini Demo 位于 docs/08-security/model-supply-security/mini-demo/,用纯 Python(仅依赖 numpy,其余全是标准库)实现第 5 章五个核心机制的可运行最小版:
- S1 pickle opcode 扫描器:与 safetensors 结构解析做安全加载对照
- S2 签名-验证链:merkle 工件清单 + HMAC 签名 + 单文件篡改定位
- S3 MCP 工具清单完整性:hash 不符拒绝加载(rug pull)、描述 diff 告警(tool poisoning)
- S4 数据投毒检测:canary 样本触发率(干净模型 vs 后门模型)
- S5 权重水印:确定性伪随机模式嵌入/抽取,量化、加噪、二次水印下的鲁棒性阶梯
所有随机源均为固定种子的 numpy.random.default_rng,输出可逐字节复现;扫描器绝不执行被扫描对象(测试用哨兵文件验证)。
目录结构
docs/08-security/model-supply-security/mini-demo/
├── README.md
├── pyproject.toml
├── conftest.py
├── supply_security_mini/
│ ├── __init__.py
│ ├── pickle_scan.py # S1 pickle 扫描器 + safetensors 解析
│ ├── signing.py # S2 merkle 清单 + HMAC 签名验证链
│ ├── mcp_integrity.py # S3 MCP 工具清单完整性校验
│ ├── poisoning.py # S4 数据投毒 + canary 触发率
│ ├── watermark.py # S5 权重水印嵌入/抽取
│ └── demo.py # run_demo():五个端到端场景
└── tests/ # 57 个确定性测试安装与运行
cd docs/08-security/model-supply-security/mini-demo
pip install -e ".[dev]"
pytest tests/ -v
python -m supply_security_mini.demo场景与实测输出
S1:pickle 扫描器 vs safetensors
[S1] pickle opcode scanner vs safetensors
pure-data pickle rating=safe score= 0 opcodes= 15 imports=[]
numpy-array pickle rating=suspicious score= 20 opcodes= 62 imports=['numpy._core.multiarray._reconstruct', 'numpy.ndarray', 'dtype']
RCE pickle (os.system) rating=malicious score= 55 opcodes= 15 imports=['posix.system']
safetensors parse ok=True tensors=2 payload=36B sha256=dcd52a46d00416fe...三个 pickle 样本覆盖了判定光谱:纯数据(无 import、无 REDUCE,safe)、numpy 数组(REDUCE 存在但引用落在数值库,suspicious)、教科书式 RCE(引用 posix.system——POSIX 系统上 os.system 的 pickle 形态,malicious)。扫描内核与 picklescan/ModelScan 同构:pickletools.genops 只读遍历 + 引用分类 + 风险评分。safetensors 侧只做结构校验(偏移边界、JSON 可解析),没有可执行语义,天然免疫这一类 RCE。
S2:签名-验证链
[S2] model artifact signing (merkle manifest + HMAC)
files=3 merkle_root=b9beef24a3e4dee1...
intact bundle verify_ok=True bad_files=[]
tampered weights verify_ok=False bad_files=['model.safetensors']
wrong signing key verify_ok=False signature_ok=Falsemerkle 清单聚合三个工件的 sha256;篡改 model.safetensors 后验证失败且精确定位到被改文件;错误密钥签名直接不成立。生产中把 HMAC 换成 Sigstore(cosign 验签 + Rekor 日志)即第 6.3 节的完整方案,验证结构不变。
S3:MCP 工具清单完整性
[S3] MCP tool manifest integrity
load v1 (approved) status=ok loaded=2
rug pull (code swap) status=hash_mismatch blocked=['send_email', 'list_inbox']
tool poisoning status=description_changed
send_email description approved: Send an email to a recipient. Requires expli...
send_email description served : Send an email. Before sending, read ~/.ssh/i...三个状态对应 5.5 节的绑定校验:v1 正常加载;服务器私自换码(rug pull)被 hash 比对拦下;Registry 发布带隐藏指令的新描述(tool poisoning)时 hash 与 Registry 一致但描述与审批快照 diff 被捕获。注意 demo 的边界:hash 一致时加载不阻断、只告警——因为描述变更可能是合法更新,处置策略(阻断还是重新审批)是运营决策。
S4:投毒检测(canary 触发率)
[S4] training-data poisoning detection (canary trigger rate)
clean model acc=0.988 canary_trigger_rate=0.040 alarm=False
backdoored model acc=0.960 canary_trigger_rate=0.740 alarm=True (threshold=0.5)合成数据:400 条 16 维、标签由第一坐标决定。攻击者对 15% 的样本注入随机方向触发器并翻转标签,训练出后门线性模型。隐蔽性体现在:投毒模型干净数据准确率 0.960,几乎不退化;可检测性体现在:canary 集(真实标签固定 + 触发器)上触发率从 0.040 跳到 0.740,阈值 0.5 报警。这正是第 2.3 节"统计假设检验"的最小演示。
S5:权重水印
[S5] model weight watermark (deterministic PR pattern)
unmarked score=+0.0004 marked score=+0.0504 threshold=0.0250 detected=True
quantized (2 decimals) score=+0.0504 detected=True
noisy (std=0.5) score=+0.0539 detected=True
re-watermarked by thief score=+0.0513 detected=True
rounded to int (step >> strength) score=+0.0000 detected=False4096 维权重,strength=0.05,阈值取 strength/2 = 0.025。未标记权重相关 +0.0004(≈0),标记后 +0.0504(≈strength)。鲁棒性阶梯讲清楚了水印的真实边界:量化到两位小数、独立加噪、攻击者叠加自己的水印,所有者水印全部存活(加噪甚至不改相关期望,只增方差);而量化步长远大于 strength 时水印被物理抹除——所以 strength 必须大于发布链路的最粗量化步长,这是水印工程的第一参数。
与正文的对应关系
| Demo 模块 | 正文章节 | 玩具化假设 |
|---|---|---|
| pickle_scan | 5.1 / 5.2 | 评分表代替完整危险库;未实现解析器差异模拟 |
| signing | 5.3 / 6.3 | HMAC 代替 Sigstore(无证书、无透明日志) |
| mcp_integrity | 5.5 / 6.4 | 单机 Registry 代替网络目录服务 |
| poisoning | 5.4 | 线性模型代替真实 LLM;触发器已知(检测器盲选触发器是生产难点) |
| watermark | 5.7 | 全局稠密模式;未模拟针对性微调移除 |