你的 CI 流水线固若金汤。容器镜像在部署前经过签名和验证。每个 npm 和 PyPI 依赖项都根据带有固定哈希值的锁文件进行解析。提交需要签名标签。然后,在你模型服务启动脚本的某个地方,有一行代码从模型中心或 S3 存储桶下载数 GB 的二进制大对象(blob)并将其加载到内存中——没有签名检查,没有哈希验证,也没有生产者的记录。这个对你产品实际功能起决定性作用的唯一制品,偏偏是你的供应链工具从未听说过的那个。
这并非虚构的漏洞。安全研究人员已经从公共中心撤下了数百个恶意模型——这些模型在你加载它们的瞬间就会执行攻击者代码,它们是专为绕过中心运行的扫描器而精心设计的。弥补这一漏洞的工具现已存在:安全序列化格式、行业签名规范、以及拒绝未签名权重的准入控制器。大多数团队只是还没意识到,“模型文件”与“来自互联网且未经审计的二进制文件”在本质上是同一类东西。
原罪:加载即执行的格式
模型文件之所以比 JPEG 更值得怀疑,归根结底在于序列化的历史。PyTorch 的默认保存格式建立在 Python 的 pickle 之上,而 pickle 并不是一种数据格式——它是针对虚拟机的一个微型指令流,可以在反序列化期间调用任意 Python 可调用对象。加载 pickle 文件就是在运行别人编写的程序。多年以来,在下载的检查点(checkpoint)上执行 torch.load() 正是这个意思。
攻击者早在大多数机器学习团队之前就意识到了这一点。2024 年初,JFrog 的研究人员在 Hugging Face 上发现了 100 多个能在加载时执行代码的模型,其中一个甚至打开了指向攻击者控制主机的反向 shell——这是一个伪装成“预训练模型”的无声后门。一年后,ReversingLabs 记录了他们称之为 nullifAI 的技术:恶意 pickle 负载被压缩成 7z 格式而非预期的 ZIP 格式,这破坏了中心(hub)的 PickleScan 检测,但仍能被一些过于“热心”的消费者工具加载。Protect AI 在扫描数百万个中心制品时,标记了数万个模型中的数十万个不安全或可疑问题。
这场扫描军备竞赛对扫描器来说结果并不理想。到 2025 年,JFrog 和 Sonatype 仅在 PickleScan 中就披露了 7 个不同的绕过漏洞——包括扫描器拒绝但加载器接受的损坏 pickle 流、归档标记操纵、以及逃避黑名单的子类技巧。任何目睹过 2000 年代杀毒软件败给恶意软件加壳工具的人,都会觉得这种模式非常熟悉。对图灵完备格式进行基于黑名单的扫描,是在原地踏步,而不是防御。
结构性的解决方案是彻底停止使用可执行格式。Safetensors(现隶属于 PyTorch 基金会)将张量存储为带有 JSON 标头的纯数据:没有操作码,没有可调用对象,没有任何可执行内容。ONNX 和 GGUF 同样是惰性的。“仅限 safetensors”的策略将代码执行风险转变为单纯的数据完整性风险。这是真正的进步,但请注意它无法解决的问题:攻击者虽然无法在加载时执行代码,但如果他们能写入你的模型存储桶,他们仍然可以换掉权重,让模型表现异常。这把我们带到了没有人进行过威胁建模的部分。
被篡改的权重比被篡改的代码更糟糕
这就是令人不安的不对称性。如果攻击者修改了你的应用程序二进制文件,你拥有数十年积累的工具来察觉:可重现构建、二进制差异对比、EDR、以及每次更改的代码审查。如果攻击者修改了你的模型权重,到底有什么能捕捉到它?该文件是数十亿个不透明的浮点数。张量没有代码审查。一个插入了针对性后门的微调模型——除了包含触发词的输入外,对其他所有输入都响应正常——对你运行的任何评估(eval)都是不可见的,因为你根本不知道该评估什么。
审视实际的攻击面:
- 具有宽泛写入权限的注册中心存储桶。 大多数公司将微调后的权重存储在对象存储中。审计谁拥有该存储桶的写入权限,你通常会发现答案是“每位数据科学家、几个 CI 服务账户,以及一个以管理员凭据运行的训练作业”。任何一个凭据泄露都意味着静默的权重替换。
- 被投毒的上游检查点。 你基于从中心拉取的基础模型进行微调。命名空间抢注、仿冒组织名称和被劫持的维护者账户都被用于提供被投毒的检查点。你的微调继承了基础模型包含的所有内容,而你的溯源轨迹从一开始就是一个谎言。
- 被入侵的训练流水线。 训练作业会拉取数十个 Python 依赖项。一个恶意的传递依赖项不需要攻击你的基础设施——它只需在辅助生产权重时稍作改动,同时保持训练损失曲线看起来完全正常。
- 内部重新托管。 团队“为了可靠性”将外部模型镜像到内部注册中心,通常使用的是不验证任何内容的复制脚本。镜像变成了一个无法审计的分支:没人能说清内部副本是否仍与上游匹配,因为没人记录上游的字节数据是什么。
共同点在于:在任何情况下,你都无法回答这个问题——“我们提供的字节流是我们预期提供的吗?是谁生产了它们?”这不是一个机器学习问题。这正是软件供应链安全在过去十年中致力于构建机制来回答的问题——签名、溯源认证、透明度日志。只是这些机制从未对准过模型文件。
新兴学科:OMS、Sigstore 与签名权重
这种情况正在改变。2025 年 4 月,OpenSSF 的模型签名项目发布了 1.0 版本,随之而来的是 OpenSSF 模型签名(OMS)规范 —— 这是由 Google、NVIDIA 和 HiddenLayer 共同发起的一项努力,旨在为加密签名模型制品定义行业标准。这些设计决策值得深入了解,因为它们清晰地映射了模型与容器之间的实际差异。
首先,模型“制品”(artifact)很少是单个文件。它通常是一个目录:权重分片(weight shards)、分词器配置(tokenizer config)、生成配置、README 等。OMS 对涵盖包内每个文件的哈希清单(manifest)进行签名,因此签名可以检测到包中任何被修改、添加或删除的文件。通过并行哈希,处理数百 GB 的分片变得可行 —— 对前沿规模(frontier-scale)的模型进行签名仅需几分钟,而非数小时。
其次,OMS 与 PKI 无关。你可以使用原始密钥、企业证书链,或者——最有趣的选项——使用 Sigstore 的无密钥(keyless)流程进行签名。在这种流程中,签名身份是工作负载身份(你 CI 任务的 OIDC 令牌),签名则记录在公共透明日志中。无密钥签名消除了“谁来管理签名密钥”这一扼杀了大多数内部签名方案的异议,而透明日志则为你提供了所有签名事件的仅追加(append-only)审计轨迹。
这并非纸面上的标准化。自 2025 年 3 月以来,NVIDIA 已使用 OMS 对其发布到 NGC 的每个模型进行了签名。Google 的 Kaggle 在托管模型的同时提供签名。Google 自己的安全团队也描述了如何使用 Sigstore 签名来保护内部模型 —— 正是这家公司发现了有人以信任的名义分发恶意模型,现在他们将未签名的模型视为不可验证的模型。在执行端,Sigstore Model Validation Operator 为 Kubernetes 带来了准入控制:尝试挂载未签名或签名不匹配权重的 Pod 将无法启动。这相当于模型权重的镜像签名准入策略,它将签名从一个合规勾选框变成了一个真正能拦截篡改制品的关卡。
值得内化的模式是:训练时签名,加载时验证,中间环节使用透明日志。在制品产生的地方签名 —— 微调任务使用其自身的工作负载身份对其输出进行签名,将权重与其产生的流水线绑定。在制品消耗的地方验证 —— 推理层拒绝加载任何签名验证失败的内容。中间的一切(存储桶、镜像源、缓存、CDN)都变成了不可信的传输媒介,而这正是它们本来的样子。
像锁定依赖项一样锁定你的模型
签名回答了“这是谁生产的”。而第二种更平实的实践则回答了“这是否是我测试过的那个”:哈希锁定(hash-pinning)。你的 package-lock.json 将每个依赖项锁定到一个内容哈希,如果注册表提供了不同的字节,你的构建就会失败。模型引用理应受到同样的对待,但大多时候并非如此。
在你的技术栈中寻找这些“坏味道”:
- 推理配置引用了
my-model:latest 或通过分支名称而非修订哈希引用 Hub 仓库。可变引用意味着你评估的模型与你实际运行的模型可能会在静默中发生偏离。
- 部署流水线在容器启动时重新下载权重。每次下载都有可能接收到不同的字节;如果没有锁定摘要(digest),你永远不会察觉。
- 评估报告显示“通过了 v3 权重测试”,但未记录摘要。哪些字节构成了 v3?如果文件在评估运行后被重新生成、覆盖或“就地修复”,那么该报告将毫无证明力。
修复方法是机械化的:配置中的每个模型引用都应是一个内容摘要,解析一次,加载时验证。Hub 客户端已经支持修订版本锁定;对象存储为你提供 ETag 和校验和;safetensors 文件哈希速度很快。这种纪律在于拒绝让可变名称出现在服务路径的任何地方。当有人问“我们在生产环境中运行的到底是哪些字节,是谁生产的”时,答案应该是一个摘要和一个签名身份,可以通过一次查询检索到,而不是去翻找部署日志进行“考古”。
这种清单问题正在演变为标准化的 AI 软件物料清单(AI BOM)。CycloneDX 的 ML-BOM 规范将 SBOM 的概念扩展到了模型:哪个基础检查点、哪些数据集、哪些训练框架版本、哪些微调运行产生了你正在服务的制品。如果说在第一次 Log4j 式事件让“我们到底在哪运行这个?”变成生死攸关的问题之前,SBOM 听起来像是官僚作风,那么 ML-BOM 现在正处于相同的预警阶段。第一个被广泛投毒的热门基础模型将使“我们的哪些微调模型源自该检查点?”成为每个受影响公司争先恐后想要回答的问题。拥有模型清单的团队将在几分钟内给出答案。
如何从小处着手,不要试图一步到位
完整的规程 —— 从训练到服务的签名溯源、准入控制、ML-BOM —— 是一个季度级别的计划。但由于目前的差距很大,初步措施成本低且立竿见影:
- 在边界禁止可执行格式。 仅允许 Safetensors、ONNX 或 GGUF。在代码审查中强制执行,并通过 CI 检查拒绝模型目录中的 pickle 系列文件(
.bin, .pt, .pkl, .ckpt)。不要迷信“扫描器显示它是干净的” —— 扫描器总是会漏掉一些东西。
- 锁定模型存储桶。 对生产权重存储的写入权限应该像对制品库的写入权限一样稀缺:仅限训练/发布流水线,几乎不给任何人。开启对象版本控制,这样篡改至少是可恢复且可观察的。
- 到处使用摘要锁定。 对服务配置进行一次全面清理,将可变名称替换为内容哈希。这是一天的工作量,却能消除最大的静默偏离风险。
- 为新制品签名。 通过模型签名工具为你流水线产生的每一次微调采用 OMS,使用 CI 的无密钥签名,这样就无需管理密钥。在开始强制执行之前先开始签名;你需要先有已签名的库存,然后才能开启验证。
- 加载时验证。 一旦覆盖范围达到一定程度,就让推理层检查签名 —— 可以通过 Kubernetes 验证算子,或者在你的模型加载封装代码中增加几行逻辑。这一步将上游的所有努力从单纯的文档转变为实实在在的防御。
这些都不需要新的研究或奇特的基础设施 —— 这只是软件供应链的手册,被应用到了它曾跳过的唯一一类制品上。十年前,整个行业在容器镜像上也上演了同样的戏码:非正式地拉取未签名的二进制大对象(blob),经历了几次惨痛的事故,然后签名和准入控制变成了基本门槛。模型权重正处于同一条曲线的早期,只有一个区别会压缩这个时间线 —— 被篡改的容器通常会通过你可以检查的行为暴露自己,而一个被篡改的模型被设计得与一个平庸的模型毫无二致。现在将权重视为可执行文件的团队,未来会将第一起重大权重篡改事件当作新闻来读。而其他所有人,则会将其当作自己正在编写的事故报告来读。
会员专享
余下内容仅对会员开放。
会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。
- —完整文章,包含未公开存档的部分
- —可落地的工作框架,附带权衡与决策依据
- —新文章抢先看,先于公开发布
随时取消 · 一次订阅,畅读全部