AI代理供应链安全2026:llms.txt投毒防御实战

·阅读约15分钟·Evergreen Tools Team
Agent Security

💡 工具推荐审计配置与策略文件时,试试 Evergreen Tools 的 JSON验证工具YAML验证工具Hash生成工具,全部免费!

2026年8月27日,Ars Technica 披露了一起针对 AI 代理的供应链攻击:100 多个网站的文档文件(llms.txt 与 llms-full.txt)引用了危险的可执行内容,编码代理访问这些网站时会自动安装。研究人员扫描了 6,214 个活跃域名、8,265 个 llms.txt 文件,发现其中 120 个指向未注册的代码包;注册这些包名后,一小时内就有 Fortune 500 公司回连。研究者 Alon Hertz 一针见血:「信任模型已经坏了——代理把供应商文档当作 ground truth,监督它们的人类也不质疑。」本文给出四层防御的完整代码实现。

1. 攻击原理:代理把文档当 ground truth

llms.txt 是网站为 AI 提供机器可读摘要的新兴约定,相当于给 AI 的 robots.txt。攻击者利用的正是代理对文档的盲目信任:恶意文件里写着「Installation: pip install [未注册包名]」或「npm install [未注册包名]」。因为包名尚未注册,攻击者可以先注册它,然后托管勒索软件或任何恶意载荷。当有 shell 权限的编码代理把这些文件当作权威安装文档时,就会下载并执行。

// Layer 1: Parse and validate every llms.txt before trusting it.
// Researchers found 120 poisoned files across 100+ corporate sites;
// packages were unregistered, so attackers could register them and
// ship ransomware through "pip install" instructions.
import { fetch } from "undici";

interface LlmsTxtEntry {
  link: string;
  title: string;
  installHints: string[];
}

export async function auditLlmsTxt(url: string): Promise<LlmsTxtEntry[]> {
  const res = await fetch(url);
  const text = await res.text();
  const entries: LlmsTxtEntry[] = [];

  for (const line of text.split("\n")) {
    if (!line.startsWith("- ")) continue;
    const [link, ...rest] = line.slice(2).split(": ");
    entries.push({
      link,
      title: rest.join(": "),
      installHints: collectInstallHints(line),
    });
  }
  return entries;
}

function collectInstallHints(line: string): string[] {
  return (line.match(/(?:pip|npm|brew|apt) install[^\n]*/g) ?? []);
}
Supply Chain

2. 第一层防御:解析并审计 llms.txt

第一条防线是永远不要自动信任 llms.txt 里的安装指令。写一个审计器:抓取文件、解析条目、提取所有 pip/npm/brew/apt install 提示,然后对每个包名向公共注册表做注册状态检查。扫描成本很低——几千个 HTTP 请求加少量注册表查询,只占代理实际工作的一小部分时间。如果你的代理要访问外部文档,让审计器先跑一遍,把可疑条目标记出来而不是直接执行。把审计器做成代理启动序列的一部分,而不是事后补丁:每个新项目、每个陌生依赖、每份出现在工单里的厂商文档都走同一流程。时间久了你会积累一份已知安全包名基线,审计会越来越快,第四层的允许列表也会更有用。

// Layer 2: Pin every dependency with a hash. Never "install latest".
// The poisoned files pointed to unregistered PyPI/npm names; an attacker
// registers the name and hosts anything they want.
// pip install with hash pinning:
# requirements.txt
requests==2.32.3 --hash=sha256:8d2c0d1f9a...
pydantic==2.9.2 --hash=sha256:1a3f5c2e9b...
# pip install --require-hashes -r requirements.txt

// npm with lockfile verification:
// 1. Commit package-lock.json
// 2. Run CI check:
//    npm ci --ignore-scripts --audit
// 3. Reject lockfile drift in code review.

3. 第二层防御:哈希固定依赖

被投毒的文件指向未注册的包名,而「install latest」语义正是这类攻击的温床。pip 的 --require-hashes 模式、npm 的 lockfile 加 npm ci 是行业标准答案:锁死版本和哈希,任何未固定依赖都直接失败。关键是把 lockfile 的变更纳入代码审查,拒绝静默漂移。

// Layer 3: Never let an agent run arbitrary shell commands.
// Run agent tool calls inside a sandbox with no network + no persistence.
// Example: Docker with seccomp + no network + read-only FS.
services:
  agent-sandbox:
    image: node:22-slim
    network_mode: "none"
    read_only: true
    tmpfs:
      - /tmp:size=100m
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    volumes:
      - ./workspace:/workspace:rw
    command: ["node", "/workspace/agent.js"]

# And gate every install command through a policy proxy:
{
  "policy": {
    "allow_install": ["npm ci", "pip install --require-hashes -r requirements.txt"],
    "block_install": ["npm install", "pip install", "curl | sh"],
    "allow_network": ["api.github.com", "registry.npmjs.org"],
    "block_network": ["*"]
  }
}

4. 第三层防御:沙箱化 shell 执行

编码代理需要 shell,但 shell 不该裸奔。用 Docker 加 seccomp、无网络、只读文件系统、丢弃全部 capabilities,把代理限制在临时工作区里。这套打法安全行业对不可信代码用了十年——唯一的新变量是,不可信代码现在通过聊天界面进来。更彻底的做法是在工具层加策略代理:拦截每条安装命令,命中允许列表才放行,其余全部阻断并告警。别忘了报告里关于父进程链的细节:研究者的信标精确追踪到是哪个代理发起了每次安装。你的审计日志也应该能回答同样的问题——哪个代理、哪个会话、哪条命令——而不需要做取证调查。

// Layer 4: Allowlist the registries and packages agents may touch.
// Agents treat vendor docs as ground truth — so the trust model
// has to be enforced in code, not in the prompt.
const ALLOWED_PACKAGES = new Set([
  "[email protected]",
  "[email protected]",
  "[email protected]",
  "[email protected]",
]);

async function beforeInstall(cmd: string, cwd: string) {
  const parsed = parseInstall(cmd);
  if (!parsed) return { allow: true };

  const key = `${parsed.name}@${parsed.version}`;
  if (!ALLOWED_PACKAGES.has(key)) {
    await notifySecurity(`Blocked install: ${key}`);
    return { allow: false, reason: "not-on-allowlist" };
  }
  return { allow: true };
}

// Attach to the agent's tool layer:
const safeTool = wrapWithPolicy(agent.tools.shell, {
  before: beforeInstall,
  auditLog: (entry) => appendAudit(entry),
});
Security Layers

5. 第四层防御:注册表与包允许列表

允许列表是唯一能真正对抗「包名被抢注」的机制。维护一个团队级允许列表(包名+精确版本),代理执行任何安装前先查表:不在表里就阻断、通知安全团队并记录审计日志。把策略挂在代理的工具层上,而不是写在提示词里——提示词可以被文档内容污染,代码策略不会。

// Agent configuration: the hygiene baseline every team should adopt.
// From the Ars Technica report: a Fortune 500 phoned home within an hour
// of a researcher registering a poisoned package name.
agent:
  shell:
    enabled: true
    sandbox: docker
    policy_file: ./agent-policy.json
  installs:
    mode: allowlist-only
    require_hashes: true
  network:
    egress: registry-only
  docs:
    llms_txt:
      enabled: false        # never auto-follow install hints
      audit: true           # but log what the site asked for
  audit:
    log_all_shell: true
    alert_on:
      - "pip install"
      - "npm install"
      - "curl.*\|.*sh"
      - "chmod +x"
  human_approval:
    required_for: [install, network_egress, filesystem_write_outside_workspace]

6. 团队落地清单

至少做到:禁止代理自动跟随 llms.txt 安装指令(改为审计模式);安装一律 require-hashes;shell 走沙箱;网络出口限制为注册表;所有安装类命令需要人工审批;审计日志全量记录 shell 与网络事件。从零开始的话,按顺序落地四层——审计、固定、沙箱、允许列表——把每一层当成独立的可部署里程碑,而不是一次性大工程。报告中最触目惊心的数字——注册恶意包名后一小时内 Fortune 500 回连——说明这不是理论风险,是当下正在发生的攻击。而修复方案大多是朴素的工程:校验、固定、沙箱化,加上一条纪律——绝不让代理逐字执行文档。

📌 常见问题 FAQ

llms.txt 是什么?

llms.txt 是网站为 AI 代理提供机器可读摘要的新兴约定,相当于给 AI 的 robots.txt,列出站点的内容结构和链接。Cloudflare 等大厂也在维护自己的 llms.txt。

为什么代理会执行 llms.txt 里的安装指令?

编码代理有 shell 权限时,会把文档当作权威设置说明。投毒文件里写「pip install [未注册包名]」,代理就照做。研究者 Alon Hertz 称之为「信任模型坏了」。

攻击者怎么利用未注册的包名?

文件里引用的包名没有在 PyPI/npm 注册,攻击者可以抢先注册,然后在包里托管勒索软件或任何恶意载荷。凡是执行安装的代理都会中招。

npm 的 lockfile 够安全吗?

够用但要看怎么用。必须用 npm ci(不是 npm install)、提交 lockfile、在 CI 里跑 --ignore-scripts 和 --audit,并把 lockfile 变更纳入代码审查。

小团队没有专门安全团队怎么办?

从最低限度开始:关闭代理自动安装(改为审计模式)、安装 require-hashes、shell 走 Docker 沙箱、网络出口限制为注册表。这四条不需要安全团队也能落地。