OpenAI 和 Hugging Face 在 AI 工程供应链里经常同时出现:前者提供模型能力与 API,后者是最大的模型和数据集分发平台。当 Hugging Face 平台发生安全事件后,OpenAI 完成事件审查并升级安全标准这一动作,本质上是所有使用第三方 AI 资产的企业都应该做一遍的供应链复盘。很多人以为安全审查就是“把模型删掉、换一个源”,实际要看的是凭证、依赖、加载器、缓存、日志和回滚路径。这篇博客从工程视角拆解一次 Hugging Face 相关事件审查应该如何展开,以及安全标准升级后,使用者本地、CI 和生产环境分别应该落到什么配置上。
外部能拿到的细节通常有限,但不影响我们梳理审查方法。下面按“风险边界 -> 证据采集 -> 标准升级 -> 操作清单 -> 环境差异 -> 排查链路 -> 工程固化”的顺序,把一次 Hugging Face 事件审查做成可以复用的技术方案。
1. Hugging Face 事件审查先要看清供应链风险面
1.1 Hugging Face 在 AI 工程链路中的角色
Hugging Face 不只是模型下载站。它同时承担模型仓库、数据集仓库、Spaces 演示环境、Git LFS 文件托管和 huggingface_hub SDK 的分发入口。对于使用 Transformers、Diffusers、Datasets 的团队来说,from_pretrained、load_dataset、hf download这些操作已经是日常的一部分。
问题也出在这里。普通软件供应链里,代码通过 Git 或包管理器分发;AI 供应链里,模型权重、分词器、配置文件、数据集脚本都会通过同一个平台分发。一个 Hugging Face 仓库被替换、一个 commit 被重写、一个下载链接被恶意复制,影响范围会沿着“下载 -> 缓存 -> 加载 -> 微调 -> 推理”这条链路扩散。
因此在审查这类事件时,第一件事不是问“哪个文件有问题”,而是先画一张资产图:哪些服务器、哪些笔记本、哪些 CI 任务、哪些后台服务曾经从 Hugging Face 拉取过内容。资产图不完整,后面的哈希校验和密钥轮换都会漏。
1.2 模型权重、数据集脚本和缓存文件都可能成为风险载点
不能把“模型文件”理解成一个单纯的二进制文件。模型文件的风险取决于加载方式。
.safetensors设计目标就是避免反序列化任意对象,它的加载流程更可控。.bin、.pt、.pth、.ckpt这类文件如果由 PyTorch 的torch.load直接加载,可能在反序列化过程中触发 Python 对象构造逻辑,也就是常说的 pickle 风险。更隐蔽的是,一个仓库里除了权重文件,还可能有数据集脚本、自定义模型代码、requirements.txt、Shell 脚本、Spaces 入口文件。只要加载器执行了其中的任意一行,问题就不只是“文件被下载”了。
下表整理了常见风险载点:
| 文件或入口类型 | 常见场景 | 主要风险 | 审查方式 |
|---|---|---|---|
.safetensors | Transformers 模型权重 | 相对较低,仍要校验哈希 | 对比仓库 sha256 |
.pt/.pth/.bin/.ckpt | PyTorch 权重、微调产物 | pickle 反序列化可能执行代码 | 优先转 safetensors |
| 数据集脚本 | load_dataset加载 | 脚本可执行任意代码 | 关闭 trust_remote_code |
| 自定义 modeling 文件 | trust_remote_code=True | 远程代码直接运行 | vendor 到本地并审查 |
| 镜像站或网盘分享 | 绕过官方通道下载 | 来源不可控,无法溯源 | 只允许内网固定镜像 |
| 缓存目录 | HF 默认缓存 | 旧文件残留,绕过新策略 | 清理或迁移到只读目录 |
另一个容易被忽略的地方是缓存目录。Hugging Face Hub 在本地会保留~/.cache/huggingface/hub/之类的缓存,同一个仓库名和 revision 在后续加载时可能不会重新下载。事件审查时如果只检查业务代码里的下载路径,不检查缓存目录,就会漏掉“文件虽然来自 Hugging Face,但实际已经落在磁盘上并被加载”的情况。
1.3 审查目标不是追责,而是还原影响面
一次安全事件审查的产出,最终要回答几个问题:
- 哪些仓库、哪些 revision、哪些文件被下载过。
- 这些文件在什么时间、被哪个进程加载执行过。
- 加载之后有没有产生异常的网络请求、文件写入或命令执行。
- 运行环境中是否存在 API key、数据库密码、云凭证等敏感信息。
- 最后要给出处置措施:清理文件、轮换密钥、升级加载策略、补审计日志。
所以不要急着在受害机器上执行“删除所有模型文件”这种操作。删除之前要先保留证据。比较好的顺序是:先隔离,再复制日志和文件哈希,最后再处置。这样后续还能核对影响范围。
2. 审查链路:资产清单、文件溯源、日志定位
2.1 先建立资产清单,再讨论漏洞
审查的第一步是找到所有可能相关的模型文件和代码调用点。可以直接在服务器或代码仓库里执行查找命令。
先找大文件,判断哪些是权重文件:
find . -type f \( -name "*.bin" -o -name "*.pt" -o -name "*.pth" -o -name "*.ckpt" -o -name "*.safetensors" -o -name "*.gguf" \) -print0 | xargs -0 ls -lh | sort -k5 -h再找代码里触发 Hugging Face 下载和加载的地方:
grep -R "from_pretrained\|load_dataset\|huggingface_hub\|snapshot_download" --include="*.py" .对于已经上线运行的推理服务,还需要检查进程和运行目录:
ps -ef | grep -E "python|uvicorn|gunicorn" | grep -v grep lsof -p <pid> | grep -E "huggingface|model|tmp|cache"这里的重点是建立一个“代码路径 -> 文件路径 -> 进程 ID”的对应关系。只找到文件不找到加载它的代码,后面依然无法判断影响。
2.2 用 Hugging Face 仓库元数据还原来源
在本地文件之外,还需要确认模型实际来自哪个 Hugging Face 仓库。可以使用huggingface_hub的 API 信息,把仓库 ID、commit SHA、更新时间、是否私有记录下来。
from huggingface_hub import HfApi api = HfApi() repo_id = "your-org/your-model" info = api.model_info(repo_id) print("repo_id:", info.id) print("sha:", info.sha) print("private:", info.private) print("last_modified:", info.last_modified) files = api.list_repo_files(repo_id) for filename in files: print(filename)不要用“在页面上看到的文件名”作为唯一凭据。Hugging Face 仓库内容是可变对象,相同的 repo_id 在不同时间可能指向不同的 commit。事件审查时要把 commit SHA 记录下来,而不是只记仓库名。如果以后需要复现审查,可以固定到同一个 commit 上。
2.3 从日志找执行痕迹和网络出口
模型加载本身通常不会打印详细日志,但异常行为会留下痕迹。优先检查以下几类:
- 系统或服务的标准输出是否出现
torch.load、pickle相关提示。 - 模型加载后是否有新的 TCP 连接。
- 是否有进程在运行时读取了用户的 SSH 私钥、云凭证、shell history 或临时目录。
- 是否有新增的计划任务、启动脚本或模型目录里的可执行文件。
可以用下面命令缩小范围:
journalctl -u my-model-service --since "2025-01-01 00:00:00" | grep -iE "torch.load|huggingface_hub|pickle|error"也可以采样进程的网络连接:
lsof -p <pid> | grep -E "TCP|UDP"如果发现进程在加载模型后访问了与业务无关的域名,要立刻记录目的 IP 和端口,并把该进程的容器镜像或可执行文件保存下来,不要直接删除。
3. 升级后的安全标准应该落在哪些配置上
3.1 不允许在每台机器直连 Hugging Face 下载
升级安全标准后,最核心的变化不是“多扫两次毒”,而是改变下载拓扑。每台推理机、每台开发机、每个 CI 任务都直接访问 Hugging Face,意味着平台侧一旦发生仓库替换或账号入侵,影响面会成倍扩大。
建议的做法是建立一层“内部可信源”:
- 在受管服务器或内部对象存储中下载一次模型。
- 计算每个文件的 SHA-256。
- 生成 MANIFEST 文件。
- 业务机器只从内部可信源拉取,并校验 MANIFEST。
这种模式下,Hugging Face 不再直接暴露给所有终端,而只暴露给少数负责拉取和同步的机器。即使上游仓库被污染,内部模型注册表仍然保留上一份已验证的产物。
下载到本地并生成校验信息的命令可以这样组织:
hf download your-org/your-model --revision <commit-sha> --local-dir ./model-cache sha256sum ./model-cache/* > ./model-cache/MANIFEST.sha2563.2 用哈希固定模型产物,而不是使用 latest
模型仓库不像代码仓库那样每次发布都打一个稳定 tag。很多团队习惯下载latest,但latest是一个变化的目标。安全标准升级后,应该把 repo_id、revision、文件哈希作为一组不可拆分的约束。
下面是一份最小策略文件:
{ "your-org/your-model": { "model.safetensors": "sha256:abc123..." }, "your-org/your-dataset": { "data.parquet": "sha256:def456..." } }校验代码可以放在 Python 脚本里:
import hashlib import json from pathlib import Path def sha256_file(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as f: for chunk in iter(lambda: f.read(1024 * 1024), b""): digest.update(chunk) return digest.hexdigest() manifest = json.loads(Path("models.policy.json").read_text()) for repo_id, files in manifest.items(): for filename, expected in files.items(): expected_hash = expected.removeprefix("sha256:").lower() actual_hash = sha256_file(Path("models") / repo_id / filename) if actual_hash != expected_hash: raise SystemExit( f"hash mismatch: {repo_id}/{filename}, " f"expected {expected_hash}, got {actual_hash}" ) print("all verified")这里要注意一个常见错误:只看文件大小。Hugging Face 上的文件可能是 Git LFS 指针,也可能是大文件本身。下载后文件大小和 Web 页面显示不一致时,要优先怀疑没有真正拉取到模型权重,而不是直接忽略。
3.3 加载模型时关掉不必要的远程代码和 pickle
在代码层面,有几条应该成为默认红线。
第一条:能使用.safetensors就不要使用torch.load加载.pt/.pth。代码出现torch.load(..., weights_only=False)要进入人工审查。如果只是为了加载权重,应该改为:
from safetensors.torch import load_file tensors = load_file("models/your-org/your-model/model.safetensors")第二条:Transformer 模型加载时,默认关闭远程代码执行。典型的加载方式应该是:
from transformers import AutoModelForCausalLM, AutoTokenizer model_path = "./models/your-org/your-model" tokenizer = AutoTokenizer.from_pretrained( model_path, local_files_only=True, trust_remote_code=False, ) model = AutoModelForCausalLM.from_pretrained( model_path, local_files_only=True, trust_remote_code=False, use_safetensors=True, )local_files_only=True会强制只读取本地目录,不会继续访问网络;trust_remote_code=False会阻止执行仓库里的自定义 modeling 文件。如果模型确实需要自定义代码才能加载,正确做法是把自定义代码下载到本地,经过代码审查后再放回模型目录,而不是直接把trust_remote_code打开。
第三条:数据集加载同样要控制脚本执行。load_dataset在加载特定数据集时可能会运行数据集脚本,推荐写法是:
from datasets import load_dataset ds = load_dataset( "your-org/your-dataset", revision="<commit-sha>", trust_remote_code=False, )如果加载失败且提示需要远程代码,要回到“审查脚本 -> vendor -> 本地加载”的流程,不要盲目允许。
3.4 API Key、镜像和共享目录更需要注意
事件审查里最容易出错的一环是 API Key。模型文件被加载后,恶意代码可能会读取环境变量、shell history、云厂商临时凭证和项目目录下的.env文件。凡是可能接触过受影响模型的个人 token、CI token、云端密钥,都应该在事件处置窗口内轮换。
轮换不是只改一个变量。应该把新密钥写入密钥管理服务,让旧密钥立即失效,再检查代码里是否还有硬编码。
可以先用命令扫描仓库历史:
git log -p --all | grep -iE "sk-[A-Za-z0-9]{20,}" || true如果有条件,使用 Gitleaks 等工具做全量扫描:
gitleaks detect --source . --report-format json --report-path leak-report.json对于网络受限团队常用的内网镜像或镜像站,需要额外核对一件事:镜像站是否记录了上游 repo_id 和 commit。如果镜像只是“转发下载链接”而不保存原始 commit 信息和文件哈希,就无法判断镜像内容是否和上游一致。使用镜像站之后,依然要在下游完成文件哈希校验,不能认为“走了镜像就安全”。
4. 在 Hugging Face 下载模型和数据集的可执行清单
4.1 下载前先确认仓库、owner 和 revision
下载前第一件事是确认仓库归属。Hugging Face 上存在同名或仿冒仓库的情况。企业团队在模型目录里看到qwen2.5-7b-instruct这类名字时,先看 owner 是不是官方组织,再看该仓库最近的 commit 和 star 数量。不能只看模型名。
下载时尽量固定 commit SHA,不使用main或latest:
hf download your-org/your-model --revision <commit-sha> --local-dir ./models/your-org/your-model这样会得到一个与某个 commit 对应的确定性目录。后续加载、测试、审计都以这个目录为准。
4.2 数据集也要固定 revision
数据集风险经常被低估。一个 CSV 或 JSON 文件本身不会执行代码,但有些数据集的加载脚本会在处理数据时运行 Python。固定 revision 之后,即使数据源更新,本地加载的数据集也不会因为 repo 更新而变化。
from datasets import load_dataset ds = load_dataset( "your-org/your-dataset", revision="<commit-sha>", trust_remote_code=False, cache_dir="./data/cache", ) print(ds)cache_dir建议显式设置到项目目录内,不要依赖默认缓存。这样既方便释放磁盘,也方便在出问题时清空。
4.3 保存 MANIFEST 并纳入 CI 校验
建议每个模型目录都保留一份MANIFEST.sha256,内容类似:
abc123... your-org/your-model/model.safetensors def456... your-org/your-model/tokenizer.json在 CI 或部署脚本里统一校验:
cd ./models sha256sum -c MANIFEST.sha256如果校验失败,流程立即终止。这一步能拦截大部分“文件被替换、下载中断、镜像内容不一致”的问题。
4.4 加载前先跑最小验证用例
模型进入推理服务之前,先写一个最小加载脚本,确认模型能从本地目录正常加载,并产生预期的张量形状。
from transformers import AutoTokenizer, AutoModelForCausalLM model_path = "./models/your-org/your-model" tokenizer = AutoTokenizer.from_pretrained( model_path, local_files_only=True, trust_remote_code=False, ) model = AutoModelForCausalLM.from_pretrained( model_path, local_files_only=True, trust_remote_code=False, use_safetensors=True, ) inputs = tokenizer("hello", return_tensors="pt") outputs = model(**inputs) print(outputs.logits.shape)这个验证的目的不是跑 benchmark,而是确认加载链路中没有访问外部网络、没有使用远程代码、没有触发反序列化任意对象。第一次在干净环境跑通后,再把同样的加载参数复制到生产代码。
5. 不同环境的执行力度应该不一样
5.1 本地研究环境
本地开发允许直接从 Hugging Face 下载,但建议至少在第一次下载时固定 commit SHA,并记录文件哈希。不要为了方便把trust_remote_code=True写进常用脚本。个人电脑通常连接着私人账号和 GitHub token,恶意模型一旦执行,暴露面比服务器更大。
本地环境还应该避免“共享模型目录”模式。团队共用一台 GPU 机器时,如果某个成员下载了一个可疑模型,其他人再从这个共享目录加载,相当于风险被二次传播。共享目录必须配合只读权限和文件哈希校验。
5.2 测试和 CI 环境
测试环境的主要风险是缓存不可控和依赖更新。CI 里如果每次跑测试都重新下载模型,会浪费大量时间和带宽;如果长期缓存旧模型,又可能在 Hugging Face 仓库更新后继续使用旧文件。
建议在 CI 中显式设置HF_HOME或HF_HUB_CACHE指向一个独立目录,并定期清理。模型文件的来源、revision、hash 作为 CI 变量或配置文件的一部分,随代码变更一起评审。
5.3 生产推理环境
生产环境标准要更严:
- 模型文件只从内部可信源获取,推理机不直接访问 Hugging Face。
- 模型目录挂载为只读,服务进程没有写权限。
- 推理服务进程使用独立低权限用户运行。
- 默认禁止公网出网,必须出网时需要明确 allowlist 并记录日志。
- 密钥不放在环境变量里,通过密钥管理服务注入。
- 模型加载参数写死在代码配置中,不允许运行时通过 HTTP 参数修改。
生产环境还要考虑回滚。每次模型升级都保留上一份 MANIFEST 和模型文件归档,当前版本异常时可以快速切换到上一版本,而不是重新去 Hugging Face 下载。
下表可以当作环境差异速查:
| 环境 | 模型来源 | revision 固定 | 哈希校验 | 网络约束 | 密钥管理 |
|---|---|---|---|---|---|
| 本地开发 | 允许官方 repo | 建议 | 建议 | 关注异常出网 | 本机 profile 不提交 |
| CI/测试 | 官方 repo 或内网缓存 | 必须 | 必须 | CI 记录下载请求 | CI Secret |
| 生产 | 只允许内部可信源 | 必须 | 必须 | 默认禁公网 | 密钥管理服务 |
6. 常见误区和快速排查链路
6.1 误区:杀毒软件没报毒就说明模型安全
模型文件不是传统 PE 可执行文件,杀毒软件很可能无法扫描 pickle 序列化数据里的代码执行逻辑。即使杀毒软件没有报毒,仍然要按照“来源 -> commit -> hash -> 加载器”的顺序验证。反过来,杀毒软件报毒也不是最终结论,还需要进一步判断是误报还是真实恶意行为。
6.2 误区:只固定 repo_id,不固定 revision
Hugging Face 仓库会持续更新,repo_id 本身不是不可变引用。攻击者如果控制了某个仓库,可以推送新的 commit 或修改文件内容。固定 commit SHA 才是可信操作。文件和 commit 的对应关系要由哈希保证。
6.3 误区:加载失败就打开 trust_remote_code
模型加载失败的原因有很多:依赖缺失、权重格式不一致、分支不同、缺少自定义 modeling 文件。直接打开trust_remote_code会把远程代码执行风险引入。正确做法是先看日志,确认缺失的是哪个模块,再把模块代码下载到本地审查。
6.4 从异常现象到根因的排查顺序
如果已经发生疑似问题,按下面顺序排查:
- 确认是哪个进程或容器加载过模型,先隔离,不要删除现场。
- 找到模型文件来源,检查 Hugging Face 缓存目录和项目模型目录。
- 使用 Hugging Face API 查看 repo_id 对应的 commit SHA 和文件列表。
- 对比本地文件哈希与上游哈希。
- 查看加载参数,是否有
torch.load、trust_remote_code=True、pickle。 - 检查网络连接和进程日志,判断是否产生异常外联。
- 检查 API key、数据库密码、云凭证是否可能在运行环境中被读取。
- 完成凭证轮换后,再清理模型文件和缓存。
- 复现审查过程,把检查项固化为脚本或 CI 流程。
如果怀疑恶意代码已经执行,不要在受感染机器上继续执行高权限扫描,先通过网络隔离或停服限制进一步扩散。
7. 把这条标准固化到日常工程流程
7.1 AI 供应链发布检查清单
每次引入新模型、新数据集或升级模型版本,建议过一遍清单:
- 是否记录了 repo_id、owner、commit SHA、文件哈希。
- 是否使用
.safetensors格式,是否避免直接torch.load。 - 是否设置
local_files_only=True,是否关闭trust_remote_code。 - 是否将模型文件同步到内网可信源,终端环境是否允许直连 Hugging Face。
- 是否生成 MANIFEST 并在 CI 或部署前校验。
- 是否扫描了 Python 依赖和密钥泄漏。
- 是否有审计日志记录模型加载时间、加载进程、文件路径。
- 是否有明确的回滚方案和上一版本归档。
这份清单最好放进 pull request 模板,而不是只存在于安全文档里。
7.2 在 Code Review 和 CI 里设置红线
代码评审阶段要拦截风险写法。建议设置几条硬性红线:
- 禁止未经评审直接合并包含
torch.load的代码。 - 禁止生产代码中出现默认从 Hugging Face 在线下载模型的逻辑。
- 禁止随意打开
trust_remote_code。 - 禁止在仓库中提交任何形式的 API Key 和密钥。
- 禁止使用未固定 revision 的模型仓库。
CI 阶段可以配合工具检查 Python 依赖和密钥:
pip-audit -r requirements.txt模型文件本身不一定能快速扫描,但环境依赖可以扫描。依赖漏洞和模型来源问题是两条独立的安全线,不能互相替代。
如果团队使用 OpenAI Codex 这类 AI 编程工具,同样需要把工具读取的仓库、使用的 token、可以执行的命令纳入审查范围。AI 编码工具不是安全的豁免区,它一样可能读取到不安全的模型目录或误用凭证。
7.3 从人工检查走向自动化模型注册表
仅仅靠每个人在下载模型时手动执行sha256sum,长期看一定会漏。下一步可以考虑搭建一个内部模型注册表:模型文件只有通过审批和哈希校验后才能进入注册表,推理服务从注册表拉取固定版本。
模型元数据可以包含:
- model name
- owner organization
- source repo_id
- source commit SHA
- file hashes
- 引入人
- 审批人
- 上架时间
- 下架时间
这套信息不仅用于安全审查,也能解决模型重复下载、版本混乱、谁在用什么模型的问题。真正把“事件后升级的安全标准”变成日常工程规则,靠的不是一次巡检,而是把检查项嵌入到下载、加载、发布、运行的每个环节。
Hugging Face 事件审查的关键结论并不复杂:模型来源必须可信,模型文件必须可验证,模型加载必须可控,模型使用必须可审计。围绕这四条线,把仓库信息、commit、哈希、加载参数、日志和密钥管理全部串联起来,才能在下一次事件到来时不再靠运气排查。