我帮人排查过几台“被种了东西”的服务器,揪到最后,问题都出在一个看起来人畜无害的模型文件上。
准确地说,不是模型文件本身有多可怕,而是很多人下载模型之后,习惯性地用torch.load()或pickle把人家的文件直接加载进内存,然后跑推理、做微调、部署上线。这个动作看起来稀疏平常,但在模型供应链安全视角下,它等于把一个陌生人的可执行压缩包直接放进自己的进程里。只要下载源、上传者或依赖链中任何一环被污染,这台机器就不再属于你了。
这里说的“HF攻击”,不是指某个具体的 CVE 编号,也不是高频交易里的概念,而是围绕 Hugging Face 模型生态产生的一类供应链安全风险:恶意模型、模型投毒、依赖污染、序列化载荷攻击等等。很多人对它的预想是“最多模型效果差一点”,但真实的影响面远不止如此。它可能是服务器被滥用、生产环境数据泄漏、依赖链被劫持,也可能是整个 AI 开发生命周期里最容易被忽视的一环。
所以这篇文章我想把这件事拆开来讲:它到底攻击的是什么,为什么实际危害比大多数人的直觉严重得多,以及普通开发者和团队应该怎么建立一套能落地的模型安全检查流程。
1. 先搞清楚:HF攻击到底攻击的是什么
1.1 模型文件不只是权重,而是一个可执行包裹
很多开发者对模型文件的认知是:一堆浮点数权重,加上配置文件、tokenizer 文件,顶多再有一段推理脚本。这个认知放在标准模型仓库里基本成立,但在下载和贡献完全开放的模型社区里,文件内容并不总是那么“纯净”。
常见的大模型加载方式里,只要涉及 pickle 或基于 pickle 的序列化方案,加载过程就不仅仅是读参数,而是会执行文件里序列化的任意 Python 对象。也就是说,一个恶意构造的模型文件,可以在torch.load()被调用的瞬间执行任意代码。这个代码不一定写在你看到的config.json里,它可以藏在模型权重文件的序列化对象中,看起来就是一堆乱码。
这个问题的根源不在某个框架,而在设计取舍:当时为了让模型权重保存和加载足够灵活,选择了 pickle 这种“对象序列化”方案。缺点是它允许代码执行。后来生态里出现了safetensors这类更安全的格式,也在逐步替代旧方案,但大量存量模型和习惯性代码仍然是旧模式,攻击面不会一夜消失。
在这个背景下,from_pretrained()也不是百分百安全。它内部可能会加载不同的文件,有的仓库可能包含自定义代码、模型源码、tokenizer 实现,这些 Python 文件在加载时会被解释执行。如果一个恶意仓库设计成“看起来很正常的模型”,开发者很容易在无感知的情况下运行了攻击者的代码。
1.2 为什么这类攻击平时不容易被发现
原因很简单:攻击的效果与模型功能的预期之间存在巨大错位。
下载一个模型,预期是它占用 CPU/GPU、消耗内存、输出预测结果。如果模型加载后发生了网络连接、文件写入、环境变量读取,这些行为会让机器变慢、异常发包、CPU 飙高,但很多人的第一反应是“模型太大了”或“训练任务太重”,很少有人会联想到模型文件本身有问题。
模型加载阶段的代码执行又是瞬时的。攻击者可以选择静默潜伏,只在某个特定时间点启动定时任务,也可以只把数据回传到指定服务器,中间不破坏任何业务。等你发现异常时,日志可能已经被清理,进程列表里也看不到明显的恶意进程。
更隐蔽的是,这类攻击不一定要立刻控制你的机器。它可以通过篡改训练数据、修改模型输出、植入后门,让模型在特定触发条件下产生错误决策。这种投毒型攻击不会影响日常使用,但会直接影响 AI 应用的可靠性。一个被投毒的模型,在大多数样例上表现正常,只在特定情况下“变异”,这种效果用普通黑盒测试很难发现。
2. 为什么说“比预想严重得多”
2.1 供应链链路长:来源、依赖、微调、部署都是攻击面
很多人下载模型时只看两个指标:下载量高不高,效果好不好。但在 HF 生态里,一个模型从上传到部署,中间经历了很多环节:
- 模型仓库本身可能被恶意上传,或诱导用户下载。
- 仓库里的多个文件可能引用一个外层依赖,这个依赖可能被劫持或恶意更新。
- 模型加载后,可能调用本地工具、系统命令,甚至读取
~/.ssh/下的密钥。 - 微调过程中,如果训练数据包含恶意样本,模型的记忆里可能被植入后门。
- 部署到生产环境后,模型每次推理都在处理输入数据,如果模型本身被投毒,输出结果可以被定向操控。
任何一个环节被利用,都不只是“模型跑不了”的问题。
尤其值得关注的是“模型微调”这个环节。很多团队会基于开源模型做指令微调,数据一般来自外部整理过的数据集,模型底座来自社区。如果你使用了别人已经微调好的模型,而这个模型被恶意调整过行为偏好,你的应用会在不知情的情况下继承这些行为。这种风险的隐蔽性比单一恶意 pickle 文件更高。
2.2 下载容易,审查难
传统软件供应链里,我们会看依赖库是否经过维护者审计、包管理器是否有签名校验、发布流程是否有 CI/CD 检查。但模型文件的审查难度要高得多。
一个模型文件动辄几百 MB 到几 GB,你不能靠肉眼去看里面是什么。现有的反病毒引擎对 pickle 序列化内容的识别能力很有限,对训练好的权重矩阵更是无能为力。你以为自己下载的是模型,实际上很可能下载了一个“带私货”的归档文件。
平台本身也会做一些安全扫描,但机制不完美。恶意仓库可能在通过审核后再更新文件,也可能利用动态域名、隐藏逻辑等手段避开检测。对于使用者来说,平台审核只能作为第一道防线,不能作为唯一防线。
2.3 平台机制本身仍然依赖用户自律
Hugging Face 这类平台的核心价值是开放和共享,这意味着任何人都能上传模型。平台有安全审查流程,但不可能完全阻止恶意文件进入。作为使用者,你不能假设“能公开下载的模型就是安全的”。
这里有一个常见的认知误区:看到模型页面上的下载量很高、使用者很多,就默认安全。下载量只能说明很多人已经踩过坑,不能说明没有坑。攻击者可以利用一些有吸引力的名字、高仿的大模型命名、甚至伪装成知名组织的账号来传播恶意模型。一旦有足够多的人下载,攻击者就有了大量可用的计算资源和数据入口。
所以在真实的工作流里,我们需要把每一个陌生的模型都当成不可信输入来对待,而不是当成一个官方发布的软件包。
3. 从单次下载到工程化部署,你会踩到哪些坑
3.1 直接加载本地或外部模型文件
最常见的坑是:拿到一个模型就急着用torch.load()加载。很多入门教程里都会这么演示,于是这个动作变成了默认习惯。但如果你不确定模型文件的来源是否可靠,这个动作本身就是高危操作。
正确习惯是:先检查文件格式。如果是safetensors,至少可以避免 pickle 反序列化带来的直接代码执行风险;如果是.bin或.pt文件,就要谨慎一些,尽量在隔离环境里加载。
如果你在使用某个模型的from_pretrained(),也应该留意仓库里是否包含.py文件。trust_remote_code=True是另一个风险点。很多模型仓库需要自定义代码才能加载,于是开发者会在代码里显式信任远程代码。但这个参数一旦打开,等于允许仓库里的 Python 脚本在你的环境中执行。这里不是说你绝对不能开,而是说开了之后,你要为这个行为承担安全责任。
3.2 忽略序列化格式和依赖版本
还有一个容易忽略的坑是依赖版本。模型加载可能依赖某些框架的特定版本,如果版本过期,你可能会在更新时被动拉入一个被污染的依赖。虽然这不一定是 HF 特有的问题,但模型生态的依赖关系往往很复杂,torch、transformers、tokenizers、safetensors 这些库之间版本不匹配时,用户容易“修复安装”装上来源不明的包。
此外,模型仓库中可能包含requirements.txt或类似的安装脚本。如果你机械地运行这些安装指令,攻击者不仅能控制模型文件,还能控制你的依赖环境。
3.3 缺少隔离环境,权限过大
即使你只在本机训练,没有部署到服务器,也不能掉以轻心。很多开发者的本机拥有很高的权限,比如可以直接访问~/.ssh、Docker 套接字、云厂商 CLI 密钥。恶意代码一旦在当前用户下执行,这些敏感资源都可能被读取。
更危险的是把模型部署到容器或 Kubernetes 集群里。如果没有限制容器权限、没有做网络隔离,一个被投毒的模型在推理时可能会访问内部网络、读取集群元数据、调用云厂商 API。这已经不仅是个人开发机的问题,而是生产环境的重大安全事件。
3.4 模型更新没有校验和溯源
很多团队在选型完成后,会固定使用某个版本的模型,但缺少对模型文件本身的完整性校验。模型文件更新、替换、镜像转发,都可能导致实际使用的模型与审查过的版本不一致。
如果没有记录模型来源、下载时间、文件哈希、审核结论,后续排查安全问题时会非常被动。你很难说清楚这个模型当时是从哪个 URL 下载的,是否有人中途替换过。对于长期运行的 AI 系统,这个“来源可溯性”问题会越来越重要。
4. 建立一套可落地的模型安全检查流程
4.1 下载前:先看文件格式、来源、审核记录
在动手下载之前,可以花两三分钟做一轮快速检查:
- 模型文件名、仓库名、组织名是否与官方来源一致。
- 仓库文件列表里有没有可疑的
.pkl、.py、.sh、.tar.gz文件。 - 模型有没有使用
safetensors格式,还是旧的.bin格式。 - 模型页面上有没有明确的说明文档、许可证和社区使用记录。
- 如果是团队中多人使用,是否有人已经审核过这个模型。
如果模型来自陌生账号,且文件名高度模仿知名模型,但文件结构异常、代码缺失、没有文档,就要高度警惕。下载量高不代表安全,但审计记录能提供一定信号。
4.2 运行前:隔离环境 + 静态检查 + 权限限制
我最推荐的原则是:所有不可信模型,都在隔离环境中先跑一遍。
隔离环境可以是 Docker 容器,也可以是独立的虚拟机,关键是要满足几个条件:
- 没有宿主机的敏感目录挂载,比如
~/.ssh、/root/.aws、/var/run/docker.sock。 - 没有直接暴露的内部网络访问权限。
- CPU/内存资源有限制,避免恶意挖矿程序直接耗尽宿主机资源。
- 容器以非 root 用户运行,避免容器逃逸后的权限放大。
在隔离环境里,用find或解包工具先检查文件列表,再用静态扫描工具扫描 pickle 文件中的可疑导入。这些工具可能无法完全识别恶意对象,但能发现一些明显的风险项。
然后进行加载测试,重点观察几件事:
- 加载过程中是否有网络外连行为。
- 是否有进程创建子进程。
- CPU、内存、磁盘读写是否异常。
- 是否修改了工作目录之外的文件。
如果模型在隔离环境中表现异常,就不要把这个模型带入正式环境。不要觉得“其实我也没跑多少数据”,安全问题和概率无关,一旦发生就是一次事件。
4.3 运行后:日志监控 + 出口网络控制 + 资源限制
就算模型已经进入正式环境,也不能认为事情就结束了。建议在部署侧做三件事:
第一,给推理服务配置最小权限。容器和进程只拥有完成任务所需的权限,不应该随意读取全盘文件。如果被加载的代码试图访问不相关的数据目录,权限控制系统会阻止它。
第二,限制出口网络。很多恶意模型的回传行为依赖外部网络,如果推理环境根本不允许访问公网,或者只能访问白名单域名,恶意代码即使执行成功了,数据也传不出去。对 AI 服务的网络策略,尽量做到“默认拒绝,按需放行”。
第三,记录模型加载和推理日志。谁在什么时间加载了什么模型,模型文件的哈希是什么,输出结果有什么异常,这些日志不仅帮助排查问题,也帮助团队形成“模型版本可追溯”的审计链条。
4.4 个人学习和小团队的最小清单
如果是个人开发者或者三五人的算法团队,可能没有专职安全工程师,但也不能完全不做防护。下面这个清单可以直接拿来用:
| 检查项 | 最小要求 |
|---|---|
| 模型文件格式 | 优先使用safetensors;旧格式文件必须隔离分析 |
| 模型来源 | 记录仓库地址、下载时间、文件哈希 |
| 加载方式 | 避免直接torch.load();优先from_pretrained()且不开启远程代码执行 |
| 运行环境 | 独立虚拟环境或容器,非 root 用户,限制网络访问 |
| 静态检查 | 检查文件列表、可疑导入、隐藏压缩包 |
| 动态监测 | 观察加载时的 CPU、内存、网络、子进程行为 |
| 权限控制 | 不挂载宿主机敏感目录,不授予云平台高权限 |
| 日志留存 | 记录何时、何地、何人加载了哪个模型 |
不要觉得这个清单“太麻烦”。真实情况是,模型下载之后如果不做这些检查,你会一直处在“不知道有没有问题”的状态里。一旦发现问题,代价远高于做一轮检查的几分钟时间。
5. 这类问题最终考验的是AI工程化能力
5.1 从“能用模型”到“安全地用模型”
过去几年,AI 开发者的核心诉求是“让模型跑起来”。现在这个门槛已经很低了,主流框架、预训练模型和社区生态都足够成熟,跑通一个模型并不难。难的是把模型当作一个需要持续维护的软件组件来管理,而不仅仅是下载权重。
这里的安全问题,本质上和软件供应链安全是同一个问题:你不能盲目信任外部组件。传统开发里,我们会关心依赖库的漏洞、许可证、代码审计报告;模型开发里,也应该关心模型的来源、序列化格式、加载代码、数据投毒风险。
很多人觉得“安全地用模型”是大型企业才需要考虑的事,但恶意代码并不区分个人开发者和大型团队。个人开发者可能因为一个恶意模型丢失本地数据,小型团队可能因为一个投毒模型在产品里产生严重错误。规模越小的团队,越需要把风险控制在流程里,而不是依赖事后排查。
5.2 模型安全需要像依赖安全一样被对待
你会在pip install一个包之前关注它的维护状态、最近版本、已知漏洞吗?如果会,那么面对一个陌生模型时,至少应该用同样的谨慎程度。
一个可选的做法是:把模型视作依赖项,纳入统一的物料清单管理。具体来说,就是记录项目依赖了哪些模型、从哪里获取、文件哈希是什么、有没有经过安全评估。这些信息可以放在一个小型 Markdown 文件里,也可以用更正规的软件物料清单工具管理。核心目的是让团队在某个模型出现问题时,能快速定位影响范围。
对于长期项目,还可以做定期复扫。模型不会像代码那样频繁更新,但如果平台上的模型源头被污染,或依赖的框架库出现新漏洞,你需要知道自己的系统是否受影响。这种复扫更像是环境巡检,不一定要天天做,但至少要在发布新版本和有安全通告时做一次。
5.3 几个可供参考的长期策略
从更长的时间维度看,下面几个方向值得持续投入:
推动安全加载格式的普及。优先选择safetensors这类不执行任意代码的序列化格式,同时在新项目中避免引入旧式的 pickle 加载逻辑。模型格式越安全,供应链攻击的触发面越小。
建立一套模型准入流程。不一定很复杂,但流程要固定下来:下载前的检查、隔离区的验证、进入生产前的人工复核、上线后的日志监控。流程是防止“临时起意”的必要手段。
将模型安全纳入团队协作规范。算法工程师和平台工程师要共同对模型运行环境负责。算法团队不能只关心模型精度,平台团队也不能只关心服务稳定性。双方至少要明确:当模型来自外部时,谁负责安全评估,谁负责环境隔离,谁负责应急预案。
保持对模型来源的追踪和备份。如果某个模型被恶意更新了,你至少能知道此前使用的是哪个版本,并能从备份中恢复。不要以为下载一次就永远安全,模型文件可能被平台删除、替换或重命名,你的本地副本可能也被其他同步工具污染。
说回到最开始那个朋友的故事。问题根源不在于“他下载了模型”,而在于“他把模型当成一个普通文件直接加载了”。模型文件在 AI 开发流程里,应该被当作“外部代码”,而不是“静态数据”。这个认知转变,比学会任何安全工具体都更重要。
HF 攻击比预想严重得多,本质上不是因为攻击者多高明,而是因为这整套流程太依赖于开发者的安全意识。你无法控制上传者的意图,也无法保证平台的每一次审核都完美,但你可以控制自己的工作流:不加载不可信模型、不运行远程代码、不把所有权限交给一个文件。
如果这篇文章只能留下一句话,那就是:下次准备torch.load()一个陌生模型之前,先想想它是否配得上你的信任。