过去一段时间,AI 开发者在日常工作中越来越依赖从 Hugging Face、GitHub 等平台拉取开源模型和数据集。模型托管平台带来了便利,同时也把供应链安全风险带进了企业代码库。近期 OpenAI 完成了一次与 Hugging Face 平台相关的事件审查,并同步升级了内部安全标准。这件事给所有使用 AI 模型和开源组件的团队提了一个醒:模型下载不是简单的pip install,而是一次完整的外部代码引入行为,必须纳入安全审查体系。
这篇文章不会去复述传闻或猜测事件细节,而是围绕“AI 模型供应链安全审查”这个核心主题,从技术角度拆解:
- Hugging Face 等平台存在哪些真实的安全风险;
- 下载、加载、运行模型前应该做哪些安全检查;
- 企业内部如何将事件复盘转化为可落地的安全标准;
- 哪些工具和命令可以直接用在日常开发中;
- 遇到恶意权重、依赖混淆、token 泄露等典型问题如何排查。
如果你是后端开发者、AI 应用开发者,或者负责公司内部模型资产管理,这篇文章的内容会比较贴近实际工作场景。
1. 模型托管平台为什么需要安全审查
1.1 从一次“下载模型”说起
很多团队的模型接入流程是:在 Hugging Face 上搜索一个效果不错的模型 →git clone或直接用transformers加载 → 立刻跑通业务。
这个流程非常高效,但隐藏的风险点也很多。一个模型仓库里不只有权重文件,还包含配置文件、分词器、推理脚本、甚至 README 里嵌入的可执行命令。其中任何一个环节被恶意注入,都可能在开发机或生产环境中执行任意代码。
从供应链角度来看,模型托管平台和 npm、PyPI、Maven 仓库没有本质区别:**你引入的不只是模型,而是整个仓库里所有文件的可信度问题。**PyPI 上曾经出现过恶意包窃取环境变量的案例,Hugging Face 上的模型权重同样可以通过反序列化触发代码执行。
1.2 事件审查带来的安全标准升级思路
当一个团队完成安全事件审查后,通常会输出三样东西:
- 根因分析:确定问题出在哪个环节,是流程缺失、权限过大,还是依赖不可控;
- 影响范围确认:识别哪些资产、哪些环境受到潜在影响;
- 安全标准升级:把临时修复变成长期防控制度。
这套方法论不只适用于 OpenAI 这类大公司,任何使用 AI 模型的中小团队都可以照搬。下面的内容会围绕一个可落地的“模型引入安全审查流程”展开,你可以直接作为内部规范草案来用。
2. 环境准备与工具链
2.1 基础环境说明
本文的示例以常见环境为例,重点是演示配置思路,具体版本需要根据你的项目实际情况调整:
| 组件 | 建议版本范围 | 用途 |
|---|---|---|
| Python | 3.9 及以上 | 运行 transformers 等框架 |
| huggingface_hub | 0.23 及以上 | 下载模型与仓库元数据 |
| transformers | 4.40 及以上 | 加载 Hugging Face 模型 |
| pip-audit | 任意较新版本 | 检查 Python 依赖漏洞 |
| bandit | 任意较新版本 | 对 Python 脚本做基础安全扫描 |
| grype / trivy | 任意较新版本 | 容器和目录级漏洞扫描 |
| syft | 任意较新版本 | 生成 SBOM(软件物料清单) |
如果你的环境没有安装,可以按下面的命令准备:
pip install --upgrade huggingface_hub transformers pip install pip-audit bandit # syft 和 grype 推荐用二进制安装 # macOS 可执行: brew install anchore/syft/syft anchore/grype/grype2.2 隔离环境是安全审查的前提
强烈建议在专门的隔离环境或容器中完成模型的下载、审查和测试运行,不要直接在开发主环境或生产环境中操作。
python -m venv .venv-model-audit source .venv-model-audit/bin/activate # 或者使用 Docker 做一次性审查容器 docker run -it --rm -v $(pwd):/work python:3.11-slim bash这样做的原因有两点:
- 恶意代码一旦在审查阶段执行,只会影响临时环境;
- 容器可以随时销毁,避免污染开发机。
3. 核心安全风险拆解
3.1 恶意权重文件与任意代码执行
Hugging Face 生态里最典型的攻击面是 PyTorch 的权重加载机制。PyTorch 官方长期使用 pickle 协议保存模型权重,而 pickle 在反序列化时允许执行任意代码。一个经过恶意构造的.pt或.bin文件,可以在torch.load()被调用时执行攻击者植入的代码片段。
即使你使用transformers的from_pretrained()方法,底层依然可能触发权重加载。虽然官方在推动safetensors格式来规避风险,但不能假设所有模型都已经切换到安全格式。
典型攻击链路:
用户搜索模型 -> 访问恶意仓库 -> 执行 from_pretrained() -> 触发恶意 pickle 载荷 -> 攻击者获取执行权限3.2 依赖混淆与脚本注入
有些模型仓库会在推理脚本里写os.system()或subprocess.Popen()调用,有些会通过requirements.txt引入恶意命名的依赖包。更隐蔽的方式是仓库中的配置类文件(如config.json、tokenizer_config.json)包含自定义处理逻辑,加载框架会根据字段执行额外操作。
3.3 访问令牌泄露
Hugging Face 支持通过 Access Token 访问私有模型和数据集。开发者很容易把 token 直接写在代码中、提交到 Git 仓库,或者暴露在模型下载脚本的环境变量里。一旦 token 被恶意模型或第三方工具获取,攻击者就能读取你企业内部的私有模型资产。
3.4 元数据与文档陷阱
README 文件本身不会直接导致代码执行,但攻击者可以在 README 中植入带有误导性的安装命令、伪造的 curl 地址或指向恶意域名的链接。自动化流程如果把这些命令当作标准安装步骤执行,就会引入风险。
4. 实战案例:Hugging Face 模型引入安全审查流程
下面用一个完整的示例来演示,团队在下载 Hugging Face 模型之前应该执行哪些检查。示例以bert-base-uncased作为展示对象,你也可以替换成任意线上模型,步骤完全一致。
4.1 创建项目结构
model-audit-project/ ├── download.py # 模型下载与基本信息打印 ├── inspect_repo.py # 仓库文件与依赖检查 ├── scan_deps.py # Python 依赖漏洞扫描 ├── sbom_generate.sh # 生成 SBOM ├── safe_load_test.py # 隔离环境加载验证 └── requirements.txt4.2 下载模型并核对仓库元数据
先用huggingface_hub获取仓库信息,包括作者、许可证、下载量、文件列表,而不要直接git clone或from_pretrained()。
# 文件路径:download.py from huggingface_hub import HfApi api = HfApi() model_id = "bert-base-uncased" # 获取模型基本信息 info = api.model_info(model_id, files_metadata=True) print("模型 ID:", info.id) print("作者:", info.author) print("许可证:", info.card_data.license if info.card_data else "未知") print("最后更新时间:", info.last_modified) print("\n文件清单:") for sib in info.siblings: size_mb = sib.size / 1024 / 1024 if sib.size else 0 print(f"{sib.rfilename} | {size_mb:.2f} MB")运行输出示例:
模型 ID: bert-base-uncased 作者: google 许可证: apache-2.0 最后更新时间: 2023-08-31 22:28:37 文件清单: config.json | 0.59 MB flax_model.msgpack | 0.52 MB model.safetensors | 0.52 MB pytorch_model.bin | 0.53 MB ...在这个阶段要重点确认:
- 作者是否可信,是不是官方组织或知名团队;
- 许可证是否允许你的业务场景使用;
- 文件列表里有几种权重格式;
- 是否同时存在
.bin和.safetensors文件。
如果仓库里同时存在安全格式和非安全格式,优先下载safetensors。如果没有safetensors,则必须在后续隔离环境中做加载验证。
4.3 检查仓库文件与依赖声明
接下来检查仓库里是否包含可疑脚本、可疑依赖、可疑网络请求。
# 文件路径:inspect_repo.py from huggingface_hub import snapshot_download import os import re model_id = "bert-base-uncased" # 只下载仓库文件,不做任何加载 local_dir = snapshot_download( repo_id=model_id, allow_patterns=["*.py", "*.txt", "*.json", "*.yaml", "*.yml"], local_dir="./downloaded_model", local_dir_use_symlinks=False, ) suspicious_keywords = [ "os.system", "subprocess", "eval(", "exec(", "pickle.loads", "socket", "requests.get", "urllib.request", "base64.b64decode", "curl", "wget", "reverse_shell" ] for root, dirs, files in os.walk(local_dir): for fname in files: if not fname.endswith((".py", ".txt", ".json", ".yaml", ".yml")): continue file_path = os.path.join(root, fname) try: with open(file_path, "r", encoding="utf-8", errors="ignore") as f: content = f.read() except Exception: continue for i, line in enumerate(content.splitlines(), 1): lower_line = line.strip().lower() for kw in suspicious_keywords: if kw.lower() in lower_line: print(f"[可疑] {file_path}:{i} 包含关键词 {kw}") print(f" {line.strip()[:100]}")运行后如果没有任何输出,说明仓库内没有明显的高危关键字。如果出现输出,不要直接运行该模型,先人工确认这些代码片段在什么逻辑中被调用。
4.4 依赖漏洞扫描
模型推理往往依赖transformers、torch、tokenizers等包。这些包自身也可能存在漏洞。可以在项目环境里统一扫描:
pip-audit输出示例:
No known vulnerabilities found如果发现漏洞,优先通过升级到修复版本解决。需要使用旧版本时,应通过内部安全评估后加入白名单。
4.5 在隔离环境中安全加载模型
完成静态检查后,进入隔离环境做一次真实加载验证。这里先使用safetensors格式,并明确关闭 pickle 权重加载。
# 文件路径:safe_load_test.py import os os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1" os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1" from transformers import AutoTokenizer, AutoModelForSequenceClassification model_id = "bert-base-uncased" try: tokenizer = AutoTokenizer.from_pretrained(model_id, use_fast=True) model = AutoModelForSequenceClassification.from_pretrained( model_id, torch_dtype="auto", ) print("模型加载完成") except Exception as e: print("加载失败:", e)如果你的模型仓库存在非safetensors格式,并且必须使用,应将from_pretrained()放到一个没有重要权限的容器中运行,并观察进程的网络连接和文件系统变化。
# 在容器内观察网络连接 apt-get update && apt-get install -y net-tools strace -f -e trace=network -e trace=file python3 safe_load_test.py 2> trace.log # 分析 trace.log 中是否有可疑的外部域名连接这一步的核心目的不是阻止代码执行,而是让恶意行为暴露在可监控的范围内。
4.6 生成软件物料清单
安全审查需要留下可追溯记录。推荐使用 syft 生成 SBOM:
syft dir:./downloaded_model -o spdx-json > sbom_downloaded_model.json cat sbom_downloaded_model.json | head -50SBOM 可以让你随时回答一些问题:
- 这个模型仓库里有哪些文件?
- 依赖了哪些 Python 包和系统库?
- 版本分别是什么?
当新漏洞披露时,你可以快速比对 SBOM,确认是否受影响。
4.7 审查通过后注册内部模型资产
审查通过的模型不只是在代码里直接填 ID,建议在企业内部建一个模型登记表,字段可以包括:
| 字段 | 必填 | 说明 |
|---|---|---|
| 模型 ID | 是 | Hugging Face 或内部仓库 ID |
| 版本/Commit | 是 | 锁定的精确版本 |
| 维护者 | 是 | 业务侧负责人 |
| 审查人 | 是 | 安全侧负责人 |
| 许可证 | 是 | 商业应用可行性 |
| 审查日期 | 是 | 确保定期复审 |
| 已知风险 | 否 | 例如“无 safetensors 格式” |
| 使用场景 | 否 | 控制在安全边界内 |
5. 安全标准升级:从审查到制度
5.1 分级控制模型下载
基于审查结果建立模型风险分级:
| 级别 | 定义 | 管理方式 |
|---|---|---|
| L1 | 官方组织发布,safetensors,低风险 | 登记后可直接使用 |
| L2 | 第三方发布,格式安全,依赖简单 | 安全人员复审后使用 |
| L3 | 第三方发布,含 pickle 权重或高风险依赖 | 隔离环境运行,禁止访问生产网络 |
| L4 | 未通过审查 / 来源不明 | 禁止使用,名单拉黑 |
企业在内部内网搭建 Hugging Face 镜像或代理时,可以在这个层级上做访问控制。最常见的做法是把 L1 和 L2 模型预下载到内部模型仓库,业务侧不允许直接从公网拉取。
5.2 最小权限原则
业务代码在加载模型时,应遵循最小权限:
- 不要使用拥有仓库写权限的 token 读取模型;
- 读 token 应单独创建,只授权需要访问的仓库;
- 生产环境使用环境变量注入 token,不要写在配置中心明文里;
- 定期轮换 token,并在出现疑似泄露时立即撤销。
# 创建只读 token 后放入环境变量 export HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxx export HF_HUB_ENABLE_HF_TRANSFER=1在 Python 中读取 token 的方式:
import os hf_token = os.getenv("HF_TOKEN") if not hf_token: raise RuntimeError("缺少 HF_TOKEN")不要使用类似HF_API_KEY = "hf_xxx"这样的硬编码写法。代码仓库扫描工具会自动发现这类泄露。
5.3 引入锁文件和依赖固定
模型依赖不是“装最新版”就行。在内部项目里,requirements.txt建议固定到精确版本,并记录校验和:
pip freeze > requirements.lock pip-audit --requirement requirements.lock对于容器化的模型推理服务,在镜像构建阶段就加入 SBOM 生成和漏洞扫描:
FROM nvidia/cuda:12.1.1-runtime-ubuntu22.04 WORKDIR /app COPY requirements.lock . RUN pip install --no-cache-dir -r requirements.lock && \ pip-audit --requirement requirements.lock COPY . . # 构建结束后生成 SBOM RUN syft dir:./ > /app/sbom.json5.4 告警与监控
安全标准升级不能只停留在“审批”和“扫描”,还需要可观测性。在模型推理容器中,建议开启运行时监控:
- 容器进程的网络连接监控;
- 文件系统写入监控;
- 异常进程启动告警;
- 模型请求日志审计。
当某个模型首次上线时,给监控告警设置较为敏感的规则,例如:
- 容器启动后出现外部 IP 连接;
- 工作目录下被写入非预期文件;
- 出现新的 shell 进程。
这样可以更快发现模型仓库投毒或恶意权重触发的问题。
6. 常见问题与排查思路
6.1 从 Hugging Face 下载模型时速度特别慢
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 下载一直停留在等待状态 | 网络到 Hugging Face 不稳定 | 启用 hf_transfer 并配置环境变量 |
| 大文件下载中断 | 网络波动或代理不稳定 | 使用 huggingface_hub 的断点续传能力,不要手动 curl |
| 内网无法访问 | 企业防火墙限制 | 使用内部镜像或预下载模型资产,安全标准中不要依赖公网直连 |
使用hf_transfer加速下载的配置方式:
pip install hf_transfer export HF_HUB_ENABLE_HF_TRANSFER=16.2 模型加载时报错“Some weights are not used”
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 权重文件不匹配 | 模型结构与预训练权重不一致 | 检查config.json中的num_labels等字段 |
| 框架版本不兼容 | transformers 版本过旧 | 升级 transformers 到项目锁定版本 |
| 多格式权重混用 | 仓库同时存在 .bin 和 .safetensors | 优先使用 safetensors,删除多余格式 |
6.3 安全扫描工具误报如何处理
静态扫描的误报很常见,例如模型仓库中的config.json包含了字段名exec,这不一定代表代码注入。误报处理流程:
- 先人工查看上下文中该字段是否被动态拼接进
eval()或exec(); - 如果属于无执行语义的普通字段,在扫描白名单中备注原因;
- 如果存在执行语义,立即升级风险级别。
不要因为误报就直接关闭扫描规则,扫描规则的价值在于发现可疑点,而不仅是发现恶意代码。
6.4 如何确认 token 已泄露
如果怀疑 token 泄露,可以这样做:
- 登录 Hugging Face 后台,查看 token 最近调用记录;
- 确认是否有未知 IP 的访问;
- 发现异常后,第一时间删除旧 token 并签发新 token;
- 在团队内部排查是否有人把 token 提交到公开代码仓库。
# 在代码仓库中扫描疑似 token grep -r "hf_" --include="*.py" --include="*.env" --include="*.txt" . | head -20更好的方式是用 gitleaks 等工具做仓库历史扫描:
gitleaks detect --source . --report-format json --report-path gitleaks-report.json7. 模型安全审查清单
为了方便团队落地,下面是一份精简的审查清单,可以打印贴在墙上,也可以直接写进 CI/CD 的检查项。
7.1 下载前检查项
- [ ] 模型仓库的作者是否明确且可信?
- [ ] 许可证是否允许业务场景使用?
- [ ] 是否已有同类型内部已审查模型可复用?
- [ ] 是否确认不直接使用公网模型 ID?
7.2 下载与静态检查项
- [ ] 是否在隔离环境中下载?
- [ ] 是否检查了仓库所有文件清单?
- [ ] 是否扫描了 Python 脚本中的危险调用?
- [ ] 是否检查 requirements.txt 中每个依赖?
- [ ] 是否执行 pip-audit 确认无已知漏洞?
- [ ] 是否优先下载 safetensors 格式?
7.3 动态加载验证项
- [ ] 是否在容器中执行加载?
- [ ] 是否监控了网络连接和文件系统变化?
- [ ] 是否记录了加载日志?
- [ ] 是否生成并保存了 SBOM?
7.4 上线与维护项
- [ ] 是否在内部模型登记表中注册?
- [ ] 是否锁定了模型版本和 commit?
- [ ] 是否配置了只读 token?
- [ ] 是否存在轮换 token 的计划?
- [ ] 是否配置了运行时告警?
8. 安全标准升级的长期建议
事件驱动的安全升级有一个常见误区:只盯住最近出问题的那个平台,忽略了整个供应链生态。OpenAI 完成审查并升级安全标准,这件事真正值得学习的地方是它把一次事件变成了体系化的改进,而不是只修复一个 bug。
对团队来说,建议按照下面的节奏推进:
- 短期(1-2周):清点团队内所有的模型来源,隔离高风险仓库,撤销可疑 token。
- 中期(1个月):搭建内部模型镜像或代理,让业务代码默认不直连公网。
- 长期(一个季度):把安全扫描集成进 CI/CD,对模型的引入执行自动审查。
下面的示例是 GitHub Actions 中一个简单的安全扫描任务,核心思路是在每次模型依赖变更时自动跑一遍静态检查:
name: model-security-scan on: pull_request: paths: - "models/**" - "requirements*.txt" jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install tools run: | pip install pip-audit huggingface_hub - name: Audit dependencies run: | pip-audit --requirement requirements.txt - name: Check model scripts run: | python scripts/detect_suspicious_scripts.py models/如果你的团队还没有建立模型安全审查机制,目前大多数团队的做法近似于“信任默认值”,也就是 Hugging Face 上有什么就用什么。这个方式在个人项目和小原型阶段问题不大,但一旦模型进入企业生产环境、接触到真实用户数据,风险就会明显放大。
个人开发者的安全习惯同样重要。即使你只是在自己的电脑上跑一个开源模型,也应该优先选择 safetensors 格式,不要轻易运行模型仓库中来历不明的脚本,不要把私人 token 放在代码里。安全标准升级不一定是公司层面的制度,也可以是一个开发者的日常工作习惯。
从事件中学习,比抱怨事件本身更有价值。如果你正在推进公司内部 AI 模型的规范化管理,可以直接把本文第 4 节的代码和第 7 节的审查清单作为起点,建成一套属于自己团队的“模型准入机制”。这样下次再发生类似事件时,你不需要临时救火,因为常规防线已经替你挡住了大部分风险。