HF攻击最近在安全讨论里被反复提起,大家更习惯把它理解成“Hugging Face 模型仓库相关攻击链路的统称”。它和你平时听到的某个 CVE 不太一样,更像是模型供应链风险的一次集中爆发:你以为只是下载了一个权重文件,实际上同时拿回了依赖包、自定义脚本和配置元数据;加载模型这个动作一旦触发,代码就有可能在训练机、推理服务甚至内网环境里执行。这个问题比我预想严重,是因为大多数团队连“模型目录里到底有哪些文件”都没盘清楚,更不要说“谁加载过它、加载时执行了什么、输出去了哪里”。
这篇文章面向经常下载模型、做微调、部署推理服务、维护模型平台和内网镜像的开发者、运维与安全工程师。我不讲攻击构造,只按真实落地顺序拆:先识别风险面,再做自查,再给防护方案,最后讨论异常处置顺序。如果你现在还能回忆出某个模型是从哪个 commit 下载的,说明你的风险意识已经比不少团队高了;如果完全想不起来,那正好从这一篇开始查。
1. 先理解“HF攻击”为什么不是单一漏洞,而是模型供应链风险
1.1 模型仓库里不只是权重,而是一整套可执行上下文
很多人对模型仓库的理解是:里面住着一个大文件,下载下来就能用。实际上,一份典型模型目录通常包含这些内容:
- 一个或多个权重文件,比如
pytorch_model.bin、model.safetensors、model.ckpt - 配置文件,如
config.json - tokenizer 文件、processor 配置
- 依赖声明,如
requirements.txt - 自定义代码,比如
modeling_xxx.py、generation_xxx.py - README 和示例脚本
- 隐藏文件、
.git目录、LFS 指针文件
这里的关键问题是:模型仓库不是一个单纯的数据包,而是一个混合了数据和代码的包。你用from_pretrained或pipeline加载模型时,框架会根据配置文件决定加载方式。如果模型仓库里带了自定义建模文件,而你的代码又开启了trust_remote_code=True,那加载过程等价于在外网下载一段未经审计的 Python 代码并在本地执行。
真正的风险点不是某个二进制文件必然有后门,而是这条链路默认“信任太多”。这和普通软件分发包已经不太一样了,普通软件至少会有签名、包管理器和来源校验,而模型仓库在很多团队里还是“从网页上找到,复制链接,下载,开始跑”。
1.2 三个让风险被严重低估的原因
第一,权重文件给人“非可执行文件”的错觉。看到.bin、.ckpt、.safetensors,多数人会把它当成普通数据,不会想到某些格式在加载时会触发反序列化逻辑,更不会想到一个模型仓库还能带脚本。于是执行模型加载时,安全告警被忽略了。
第二,加载过程是一个黑盒。AutoModel.from_pretrained一行代码,看起来就像装了个依赖,实际它可能解析配置、读取远程缓存、执行仓库内代码、请求线上资源。黑盒越黑,用户越难判断问题出在哪一步。
第三,风险会被快速横向放大。一台开发机下载了可疑模型,接下来这个模型会被复制到团队共享目录;CI 流程会自动重新拉取;内部镜像可能定时同步公共仓库;同一个缓存目录被多个服务复用。任何一个环节引入问题,都不只是单机事故。
我还想补充一点:模型仓库的“版本”不一定是稳定的。同一个模型名,不同 commit 的内容可能完全不同;发布者也可以后续更新文件,而很多工具只记住了模型名,没有记录 target hash 或 commit。一旦仓库内容被替换,下游在不知道的情况下就会拉走新的内容。
1.3 先判断自己是不是在风险范围内
不需要等攻击事件发生,下面几个问题就能帮你定位:
- 你是否从公共模型仓库下载过模型,或者使用过
clone、download类命令? - 你是否在代码里调用过
from_pretrained、pipeline、torch.load? - 是否开启过
trust_remote_code=True? - 是否运行过模型仓库附带的
requirements.txt或示例脚本? - 团队是否共用同一个模型缓存目录或内网模型镜像?
- 是否在 CI 或自动化部署流程里加载模型?
只要有一项回答是“是”,就应该继续做第三章的自查。很多人会想:我只是在自己的电脑上跑一下,能有多大影响。但模型服务的特性是“权重即逻辑”,加载动作本身就可能改变运行环境,和本地还是线上关系不大。
2. 风险面拆解:下载、加载、推理、托管四个阶段各有各的入口
2.1 下载阶段:依赖、脚本和元数据是第一层入口
下载模型时,大多数人只关注权重文件,忽略了仓库里的其他文件。风险会从几个地方进来:
- 依赖声明:
requirements.txt里指定了特定版本或少见包名,一旦安装,setup.py或安装逻辑可能执行代码。 - 自定义脚本:仓库内可能包含
modeling_xxx.py、configuration_xxx.py,它们会在trust_remote_code=True时被加载执行。 - 元数据和示例:README 或示例代码引导你安装某个命令行工具、运行某个 shell 命令,误操作就会引入风险。
- LFS 和 Git 历史:大文件通过 Git LFS 管理,多个 commit 之间内容可能完全不同;
.git目录中也可能保留旧的恶意版本。
下载阶段最容易被忽略的是“版本固定”。很多团队会写死模型 ID,但没有写死 commit hash。这意味着今天下载的内容和上周下载的内容可能不是同一个东西,以后更新重启也可能悄悄发生变化。更稳妥的做法是像锁定依赖版本一样锁定模型版本。
2.2 加载阶段:pickle 反序列化与远程代码开关是核心风险
加载阶段是风险最集中的位置,因为这里离执行最近。
torch.load在默认情况下使用 pickle 反序列化。pickle 不是一个简单的序列化格式,它在加载对象时会允许调用指定函数,这是它的设计能力。问题在于,当你从不可信来源下载权重文件,再用默认方式加载时,等于给了这个文件一次代码执行机会。torch.load并不是唯一路径,pickle.load、joblib.load、np.load搭配特定参数也存在类似风险。
transformers 框架里的trust_remote_code=True是第二个高风险开关。它在设计上是允许模型仓库自带自定义代码,方便实现一些尚未合入主库的结构。但它的实际效果就是:从远程仓库执行本地不存在的 Python 文件。如果这个文件没有经过评审,整个加载过程就是一个黑盒执行。
更稳的理解是这样的:
safetensors格式本身设计目标就是安全加载张量,不包含 Python 对象,所以经典的反序列化风险会小很多。- 但
safetensors不代表整个模型链路安全。你仍然可能因为依赖、配置文件、预处理逻辑、tokenizer 文件或自定义代码出问题。 - ONNX、GGUF、GGML 这类格式的风险更多在解析器和配套工具链上,来源不受信任时也要做同样的版本和完整性校验。
2.3 推理和微调阶段:回调、数据和凭证可能被进一步利用
模型加载成功之后,危险不一定结束。恶意模型或恶意代码可能在推理阶段:
- 读取输入数据并写入本地文件,等待后续外传;
- 读取环境变量、API Key、云服务凭证;
- 在容器内发起网络请求,探测内网;
- 通过自定义回调函数或训练钩子执行额外逻辑。
微调场景更需要注意。很多微调框架会根据模型仓库内的代码动态加载结构,训练脚本本身也可能来自第三方仓库。加上训练环境通常持有更大的数据权限和 GPU 资源,攻击一旦发生,影响范围比推理服务更广。
我一般会建议:推理服务和训练服务都不要挂载生产密钥。如果服务必须访问内网资源,单独配置白名单和最小权限,不要让整个节点自动获得信任。
2.4 托管与共享阶段:镜像和缓存可能变成放大器
团队到一定规模后,会自建内部模型镜像、缓存服务或者模型平台。镜像能解决统一来源和下载速度问题,但如果同步逻辑只是“从公共仓库定时拉最新版本”,它也会成为风险放大器。
内部共享目录、NFS 模型目录、模型平台的上传功能都可能被滥用。一个用户上传可疑模型,平台如果不过滤,下游所有人都会加载到同一个可疑文件。这个问题和普通软件供应链类似:越靠近上游,责任越大。
下表可以快速对照风险面:
| 阶段 | 风险入口 | 典型表现 | 主要缓解措施 |
|---|---|---|---|
| 下载 | 仓库脚本、requirements、LFS 更新 | 安装依赖时执行代码;模型内容悄悄变化 | 锁定 commit、审查依赖、统一下载入口 |
| 加载 | pickle 反序列化、trust_remote_code | 加载模型时执行自定义代码 | 优先 safetensors、关闭远程代码、评审自定义文件 |
| 推理 / 训练 | 数据回传、读取环境变量、回调逻辑 | 出现外部请求、异常文件写入 | 网络白名单、最小权限、完整日志 |
| 托管 / 共享 | 内网镜像同步、共享缓存、模型发布 | 下游服务批量受影响 | 白名单、扫描、版本不可变、审计发布 |
3. 自查流程:把你的模型目录、依赖和加载代码真正盘一遍
3.1 第一步:清点模型资产和依赖链
不要直接跳到“扫描病毒”,先建立模型资产清单。你需要知道:
- 本地和服务器上有哪些模型目录;
- 每个模型来自哪个仓库、哪个 commit;
- 是谁下载的、在哪台机器、什么时候;
- 哪些服务或代码正在使用这个模型;
- 模型仓库带了哪些依赖文件。
Transformers 常用缓存目录在~/.cache/huggingface/hub,也可能通过HF_HOME、HUGGINGFACE_HUB_CACHE改到别的位置。先确认你的实际路径,再清点。
可以先用下面的命令快速看目录结构:
find /data/models -type f | wc -l find /data/models -type f -name "*.py" | head -50 du -ah /data/models | sort -rh | head -20 git -C /data/models log --oneline -10如果模型目录是从 Git 仓库克隆的,git log能帮你看到提交历史。如果下载的是压缩包或直接下载的大文件,则重点检查目录内的脚本和配置。
3.2 第二步:扫描文件类型和可疑脚本
模型目录里出现大量.py、.sh、.ipynb文件本身不代表有问题,但需要逐个确认用途。更值得关注的是“为什么一个权重仓库里会有可执行脚本”。
可以统计文件扩展名分布:
find /data/models -type f | sed 's/.*\.//' | sort | uniq -c | sort -rn这一步能很快看出仓库里到底有哪些类型。常见的权重格式通常是safetensors、bin、ckpt、onnx、gguf。如果发现py、sh、bat、ps1这些格式,就要进入代码审计流程。
对于safetensors文件,你可以用safe_open只读头部和张量键名,不要直接加载整个文件到模型脚本里:
from safetensors import safe_open path = "/data/models/model.safetensors" with safe_open(path, framework="pt", device="cpu") as f: keys = list(f.keys()) print(len(keys)) print(keys[:10])这个操作不会执行模型内部逻辑,适合用来确认文件结构和基本完整性。
对二进制权重文件,不建议用编辑器直接打开,也不建议写一个脚本直接torch.load去“验证”。检测应该是只读分析,而不是触发加载。
3.3 第三步:审计项目加载代码与参数
模型文件本身只是一半,另一半在你的项目代码里。搜索所有加载模型的地方:
grep -R "trust_remote_code *=\|torch\.load\|pickle\.load\|local_files_only" --include="*.py" /data/projects重点检查这些参数:
trust_remote_code=True:是否真的需要,远程代码来自哪里;local_files_only=False:是否运行时会自动访问网络;torch.load和pickle.load:加载对象是否可信;allow_pickle相关配置:是否被显式打开;pipeline初始化:是否在不知情的情况下走自定义代码路径。
如果项目里有自定义建模文件,打开文件后先看import列表和关键调用,比如os.environ.get、requests、socket、subprocess、eval、exec、__import__。不一定要识别出真正的攻击代码,只要发现“一个模型文件为什么要读取环境变量或发起网络请求”这种问题时,就要标记为高可疑并暂停使用。
3.4 第四步:在隔离环境中做最小化验证
如果某个模型必须使用,但又不能完全确认安全,建议先做一次最小化验证。条件至少是这样的:
- 离线或出网被拦截;
- 不挂载生产数据目录;
- 不注入任何生产密钥;
- 使用低权限用户运行;
- 只加载一个最小脚本,观察行为。
你可以用容器加只读文件系统跑一个加载动作,然后看是否出现网络连接、异常文件写入或额外进程。这里强调的是“拦截外部连接”和“最小权限”,而不是让可疑代码在真实服务器上跑一圈后再判断。
验证通过不等于彻底安全,只能说明没有出现明显的异常行为。对于数据投毒、输出误导这一类问题,需要业务侧单独评估模型效果,不属于加载安全能解决的范畴。
判断标准可以这样定:
- 加载过程无任何外连、无额外文件写入、无异常进程,说明基础加载链路没有明显问题;
- 加载过程出现网络请求、读取密钥、执行 shell 命令,直接标记为高风险;
- 加载过程卡死、重复写入缓存、删除文件,也要立即停止。
4. 防护落地方案:从本地加载习惯到团队模型仓库的整体加固
4.1 加载代码层:安全格式优先,谨慎对待远程代码开关
你在个人项目中可以先从这几个习惯开始:
- 优先使用
safetensors格式,尽量避免直接加载来源不明的.bin、.ckpt。 - 不要随手开启
trust_remote_code=True,很多模型并不需要。如果你需要自定义模型结构,把仓库固定到具体 commit,并由懂代码的人做一次评审。 - 加载时尽量使用
local_files_only=True,避免推理过程中意外请求远程资源。 - 对必须加载旧格式权重的情况,先在隔离环境转换成
safetensors,转换完成后再复制到生产环境。
框架和依赖版本也要定期更新。这类安全问题会随着依赖升级不断得到缓解,但如果你的环境锁死在旧版本,很多防御选项会缺失。更新前先看官方 changelog 和说明,不要只凭网上片段决定。
4.2 网络与权限层:下载、推理、训练环境要分清楚
一种常见的错误是:开发机、训练服务器、推理服务全部使用同一套网络权限,都放在同一个内网,都有同一个账号的密钥。模型加载一旦出问题,横向移动几乎没有阻力。
比较稳妥的分层方式是这样的:
- 下载机:只负责从公共仓库拉取模型,不持有生产密钥,不出现在生产网络;
- 模型暂存区:存放待扫描的模型文件,网络隔离,不允许直接上线;
- 推理服务:只读加载通过扫描的模型,出网策略按业务需求单独配置;
- 训练环境:只能访问训练数据和所需模型,不挂载全部内网。
权限方面,不用 root 运行模型服务,不把宿主机敏感目录挂载进容器,设置只读根文件系统。GPU 服务器也要遵守同样的权限边界,不要以为只有 CPU 机器才需要担心。
4.3 镜像与平台层:内部模型仓库、白名单和扫描
如果你的团队或公司已经建立内部模型镜像,需要注意:镜像只是统一来源和加速下载,不等于内容安全。
建议在镜像同步链路里加入检查动作:
- 同步前记录模型 ID、commit hash、文件 hash;
- 同步后扫描文件类型、依赖声明和自定义代码;
- 只有通过检查的版本进入内网白名单;
- 白名单之外的新模型需要走审批流程,不能直接暴露给所有用户。
模型平台如果允许用户上传模型,也要在平台侧加上传扫描和发布校验。上传者不一定都出于恶意,但发布行为本身会影响下游所有人。平台可以要求发布者提供 safetensors 格式或源码评审记录,降低下游使用风险。
如果你需要识别文件哈希,可以用通用哈希工具对权重文件做摘要,并把摘要存到模型注册信息里。这样即使仓库文件后续被修改,也能快速发现。
4.4 制度层:清单、责任人和可追溯日志
技术手段之外,还缺一套流程。很多团队买了扫描工具但没有明确“谁负责确认模型可以上线”,结果扫描结果只是躺在日志里。
可以落地的最小制度:
- 每个模型都要有负责人,不能只写“由算法团队统一维护”;
- 模型上线前填写清单:来源、commit、文件 hash、使用场景、审批人;
- 下载、加载、部署过程要留日志,至少能回答“谁在什么时间加载了哪个模型”;
- 定期抽查本地缓存和模型目录,发现异常及时标记;
- 对常见风险做一次团队同步,避免新同学一上来就把
trust_remote_code=True当默认配置。
制度不用一开始就很重,先做到“可追溯”,再考虑“自动化审批”。
5. 发现异常后的处置顺序:隔离、取证、恢复、复盘
5.1 先隔离现场,不急着删文件
发现模型加载异常时,第一反应往往是“赶紧把模型文件删掉”。这个做法可以理解,但会破坏现场。如果删掉文件,后面很难判断问题到底出在权重、依赖还是代码里。
更稳妥的顺序是:
- 先断开该服务的外网连接,停止推理任务和训练任务;
- 保留现场:记录时间、服务名、命令、模型路径、日志;
- 导出当前进程信息、网络连接、文件系统变化;
- 对可疑模型目录做只读副本或快照,后续分析用副本,不要动原目录。
如果怀疑密钥泄露,不要等证据收集完再轮换,先按“可能已泄露”处理,把相关密钥轮换掉。这是一条成本最低的止损路径。
5.2 判断影响范围:模型、任务、服务和凭证
隔离完成后,需要确定影响面。先查这几项:
- 可疑模型文件 hash 是否与官方发布一致;
- 哪些机器加载过该模型;
- 哪些任务、服务、容器正在使用同一个模型缓存目录;
- 依赖声明是否被安装到多个环境;
- 模型加载过程中,哪些环境变量和凭证被读取过;
- 推理服务是否处理过敏感数据,数据有没有被写出或外传。
模型共享缓存目录会放大影响范围。如果团队一开始就是一个人下载、所有人共用,这一步骤就会比较被动。但从这一刻开始建立“谁加载了哪个模型”的日志,仍然来得及。
5.3 清理恢复:从可信备份重建,不依赖可疑缓存
清理阶段不要只是把可疑文件挪走。更可靠的方式是:
- 删除可疑缓存和临时文件;
- 从可信备份或只读镜像重新拉取模型;
- 校验重新下载的版本 hash,确认不是缓存残留;
- 固定依赖版本,更新到已确认稳定的版本;
- 重建服务时使用新密钥、新证书,不重新挂载旧敏感目录;
- 重新上线后持续观察网络连接、异常文件和资源使用。
如果团队没有可信备份,那就先把单一模型做成“只读发布”。不要一边清理一边继续让别人从危险来源下载。
5.4 复盘与加固:回到清单和扫描规则持续迭代
处置完不代表结束。真正要回答的问题是:这个可疑模型为什么能进入内网?扫描为什么没有发现?审批为什么没拦住?
复盘时建议重新过一遍:
- 模型来源是否明确;
- 负责人是谁;
- 是否做了文件 hash 核对;
- 扫描规则有没有覆盖这类风险;
- 团队是否有人误开了高风险配置;
- 后续要不要把类似模型加入黑名单。
把这次处置过程整理成一份可复用的排查清单,至少在团队内部做到“下次遇到同类问题,大家知道先隔离、再取证、后恢复”。如果模型数量多,建议把资产清点和 hash 核对做成定时任务,避免依赖人工记忆。
回到最开头的判断:HF攻击比预想严重,不是因为它有一个必杀技,而是模型获取链路在多数团队里默认是信任的。权重文件不是普通二进制,加载行为会直接影响运行环境。把“下载、加载、运行”三个动作从黑盒改成白盒,能解决一大部分风险。我更建议先按第三章做一次资产自查,再考虑上扫描工具和内部镜像,不要在模型清单还没建立时直接铺开一堆安全产品。稳一点,先把第一条加载链路盘清楚,再谈自动化。