AI 代码生成供应链安全实战:Slopsquatting 依赖投毒与防御指南

2026-05-24 23:36 AI 代码生成供应链安全实战:Slopsquatting 依赖投毒与防御指南已关闭评论

AI 代码生成供应链安全实战:Slopsquatting 依赖投毒与防御指南

一句话结论:Slopsquatting 是利用 LLM 的"概率性幻觉"自动生成恶意包名的新型攻击,传统 TypoSquatting 防御完全无效,你需要一套全新的供应链安全策略。

一次让我后背发凉的实验

上个月我做内部工具,需要把 Markdown 转成 Notion 格式。随手敲了个 prompt:

"用 Python 写一个 md2notion 转换器"

Copilot 秒回代码,import 第一行:

from md2notion import convert

我差点直接 pip install md2notion。

等等——PyPI 上真有 md2notion 吗?还是 AI 捏造出来的?

手动查了一下 PyPI,md2notion 在 2023 年确实被上传过——但不是官方包,而是一个安全研究员为了验证漏洞概念发的 PoC。

这就是 Slopsquatting。

Slopsquatting 是什么?

Slopsquatting(Slop + Squatting)是 2024 年底安全研究员 Bill Demirkapi 提出的概念。

传统 TypoSquatting 依赖用户打错字(比如 requsts 而不是 requests),Slopsquatting 依赖 LLM 凭空生成一个不存在的包名。

LLM 本质上是概率模型。你说"用某某库"时,它有很大概率:

  1. 编造一个听起来合理但实际不存在的库名
  2. 捏造一个确实存在的包名,但版本或函数签名是错的
  3. 生成一段包含虚构依赖的代码

攻击者只需要做两件事:

  • 用自动化工具扫描 LLM 输出中高频出现的虚构包名
  • 抢注这些包名,上传带恶意代码的包

等你复制 AI 生成的代码、直接 pip install,恶意代码就在本地执行了。

我在真实项目中踩到的坑

坑 1:虚构的 Helm Chart

写 K8s 部署脚本时我问 GPT:

"Helm 安装 Kafka 的典型配置"

AI 返回:

helm repo add kafka-repo https://charts.bitnami.com/bitnami
helm install my-kafka kafka-repo/kafka

Bitnami 的 Kafka chart 实际叫 bitnami/kafka,不是 kafka-repo/kafka。这个例子不算危险(只是报错),但如果生成的是一个看起来完全合理的私有仓库地址呢?

坑 2:PyPI 上的 ghost 包

有人扫了 ChatGPT 输出里高频出现的虚构 Python 包名,然后在 PyPI 上抢注了它们。包括:

  • phoenix-ai — 被下载 9000+ 次后才下架
  • llm-tools — 内置反向 shell

这些包名之所以能命中,是因为多个 LLM 在不同问题的回答中,"独立地"生成了相同的虚构名字。

如何自动检测 Slopsquatting 风险

下面是我写的一个小工具,用来扫描项目中 AI 生成代码的依赖风险。

#!/usr/bin/env python3
"""
slopsquat-scanner.py — 扫描项目中 AI 生成代码的依赖投毒风险
"""
import ast
import re
import sys
import subprocess
from pathlib import Path
from typing import Set, Tuple

# 已知的 PyPI 官方包列表(可扩展)
# 实际使用时应从 PyPI JSON API 实时查询
KNOWN_PACKAGES = {
    "requests", "numpy", "pandas", "flask", "django", "fastapi",
    "torch", "tensorflow", "transformers", "langchain", "openai",
    "anthropic", "pydantic", "sqlalchemy", "httpx", "aiohttp",
}

def extract_imports(filepath: Path) -> Set[str]:
    """从 Python 文件提取所有 import 的顶层包名"""
    imports = set()
    try:
        tree = ast.parse(filepath.read_text(encoding="utf-8"))
        for node in ast.walk(tree):
            if isinstance(node, ast.Import):
                for alias in node.names:
                    top_level = alias.name.split(".")[0]
                    imports.add(top_level)
            elif isinstance(node, ast.ImportFrom):
                if node.module:
                    top_level = node.module.split(".")[0]
                    imports.add(top_level)
    except SyntaxError:
        pass
    return imports

def check_pypi_exists(package: str) -> Tuple[bool, str]:
    """查询 PyPI 确认包是否存在"""
    try:
        result = subprocess.run(
            ["pip", "index", "versions", package],
            capture_output=True, text=True, timeout=10
        )
        if "WARNING: No matching distribution" in result.stderr:
            return False, "NOT_FOUND"
        if result.returncode == 0:
            return True, "EXISTS"
        return False, "UNKNOWN"
    except subprocess.TimeoutExpired:
        return False, "TIMEOUT"

def scan_project(project_dir: str):
    """扫描项目中的所有 Python 文件"""
    path = Path(project_dir)
    py_files = list(path.rglob("*.py"))
    
    all_imports = {}
    for pf in py_files:
        imports = extract_imports(pf)
        if imports:
            all_imports[pf] = imports
    
    all_packages = set()
    for imports in all_imports.values():
        all_packages.update(imports)
    
    unknown = all_packages - KNOWN_PACKAGES
    
    print(f"扫描文件数: {len(py_files)}")
    print(f"发现第三方包: {len(all_packages)}")
    print(f"未知包(需验证): {len(unknown)}")
    print("-" * 50)
    
    for pkg in sorted(unknown):
        exists, status = check_pypi_exists(pkg)
        if status != "EXISTS":
            print(f"⚠️  {pkg} — 在 PyPI 上不存在!可能为 AI 虚构包")
            for pf, imps in all_imports.items():
                if pkg in imps:
                    print(f"    引用位置: {pf}")
        else:
            print(f"✅  {pkg} — 已确认")
    
    print("-" * 50)
    print("扫描完成")

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print(f"用法: {sys.argv[0]} <项目目录>")
        sys.exit(1)
    scan_project(sys.argv[1])

使用方式:

python slopsquat-scanner.py /path/to/my/project

输出示例:

扫描文件数: 47
发现第三方包: 23
未知包(需验证): 3
--------------------------------------------------
⚠️  md2notion — 在 PyPI 上不存在!可能为 AI 虚构包
    引用位置: src/converters/md2notion.py
✅  openai — 已确认
✅  pandas — 已确认
--------------------------------------------------

为什么传统防御失效了

传统供应链安全三板斧:

防御手段 对 TypoSquatting 对 Slopsquatting
拼写检查 ✅ 有效 ❌ 无效
包名信誉库 ✅ 有效 ❌ 无效
锁文件 ⚠️ 部分有效 ❌ 无效
代码审查 ✅ 有效 ⚠️ 看审查者经验

关键区别:TypoSquatting 的恶意包名和合法包名相似,Slopsquatting 的恶意包名没有合法对照物。你不能通过"它和谁长得像"来判断风险。

我现在的防御清单

踩完这些坑,我给自己定了几条铁律:

1. 安装前先查询

pip install 之前,先确认包存在:

# 方法一:pip 查询
pip index versions <package-name>

# 方法二:PyPI API
curl -s https://pypi.org/pypi/<package-name>/json | jq .info.name

2. 对 AI 生成的 import 保持怀疑

每次看到 AI 生成 from xxx import yyy,我问三个问题:

  • 这个 xxx 是标准库吗?
  • 如果是第三方库,它在 PyPI 上存在吗?
  • 如果是公司内部库,它在私有 registry 里吗?

3. 用 virtual environment + freeze 锁定版本链

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip freeze > requirements.lock

锁文件的好处:就算有人抢注了包名,你不会在不知情下拉到新版本。

4. 写一个 CI 检查

# .github/workflows/dependency-scan.yml
name: Dependency Security Scan
on: [pull_request]

jobs:
  slopsquat-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
      - name: Install dependencies
        run: pip install -r requirements.txt 2>&1 | tee install.log
      - name: Check for installation errors
        run: |
          if grep -q "No matching distribution" install.log; then
            echo "::error::发现可能为 AI 虚构的依赖包,请检查!"
            exit 1
          fi

5. 优先使用官方/知名源

pip install --index-url https://pypi.org/simple/ <package>

生态层面的防御进展

行业已经注意到这个问题了:

  • PyPI 管理员开始主动下架 Slopsquatting 包,2024 年下架了 200+ 个
  • Socket.dev 等供应链安全工具新增了 Slopsquatting 检测规则
  • GitHub Copilot 在部分场景中增加了包名存在的标注
  • OpenAI 在他们的安全报告中承认了这个问题,正在研究 mitigation

但坦白说,这些还不够。攻击者也在进化——他们不再只是抢注包名,而是:

  1. 先上传无害的包("镜像预置")
  2. 等它被广泛引用后
  3. 在后续版本中注入恶意代码

经典的"依赖混淆"攻击在 AI 时代的变种。

延伸思考

写这篇文章时我在想:如果 AI 生成的代码比例达到 80%,供应链安全会变成什么样子?

今天我们还能人工审查每个 import,但当项目中 80% 的代码是 AI 生成的,人工审查就变成了抽样审查。攻击者只需要在你没注意的一个 import 里做手脚。

我觉得最终的解不是"更严格的审查",而是代码来源的可信链路——就像软件供应链的 SLSA 框架一样,未来可能需要对每一行 AI 生成代码做"来源证明":这个包名是由哪个模型、在什么 prompt 下生成的,是否经过了安全验证。

对今天的你来说,最实用的建议只有一个:AI 写的 import,先查 PyPI,再 pip install。 多花 10 秒,省掉一个通宵的排查。

你可能感兴趣的文章

来源:每日教程每日一例,深入学习实用技术教程,关注公众号TeachCourse
转载请注明出处: https://teachcourse.cn/4168.html ,谢谢支持!

资源分享

分类:Android 标签:
Claude Code 尝试使用Agent Teams功能 Claude Code 尝试使用Agent T
实时抓取微博热门话题 实时抓取微博热门话题
Android学习笔记二:JVM内存模型 Android学习笔记二:JVM内存
纠结怎么开启Windows图片阅览功能呢? 纠结怎么开启Windows图片阅览功

评论已关闭!