当沙箱反咬一口:三枚重写隔离边界规则的智能体 CVE

·阅读约12分钟·Evergreen Tools Team

2026 年关于 AI 智能体最值得读的文档不是模型卡,而是三份安全公告。它们分别来自 Cursor(CVE-2026-22708,CVSS v3.1 9.8,v4.0 7.2,2026 年 1 月 14 日公布)、OpenAI Codex CLI(CVE-2025-59532,CVSS v4.0 8.6)和 Flowise(CVE-2025-59528,CVSS v3.1 10.0),具体细节都能在 NVD 与各项目的 GitHub 安全公告里查到。三者表面无关,根因却相同:隔离逻辑要么信任模型产生的数据,要么自动批准了攻击者本来就需要的那些命令。本文只讲公告里写了什么,以及哪五道控制能把它堵上。

允许列表不该成为攻击助手

允许列表不该成为攻击助手

一、共同模式:把「数据」当成「策略」

智能体沙箱的本质是一条边界:哪些路径可写、哪些命令可跑、哪些主机可达。边界必须由操作者定义并强制。出问题的时候,边界的一部分却被交给了模型——要么是模型输出的路径被当作可写根目录,要么是「受信任命令」列表的语义被环境变量悄悄改写。这两种情况都不是提示词工程问题,而是权限模型问题,也正是 OWASP 在《State of Agentic AI Security and Governance》2.01 版里强调的转向:这一版不再罗列理论威胁,而是逐条挂上 CVE、厂商公告与事件报告。

# sandbox.py - the boundary must come from the operator, never the model
def writable_root(session_start_cwd: str, model_suggested_cwd: str) -> str:
    root = os.path.realpath(session_start_cwd)
    # Canonicalise and verify; refuse anything outside the operator's root.
    candidate = os.path.realpath(os.path.join(root, model_suggested_cwd))
    if not candidate.startswith(root + os.sep) and candidate != root:
        raise SandboxViolation(f"{candidate} escapes {root}")
    return candidate

# The CVE-2025-59532 lesson in one line: a model-generated cwd is data,
# not policy. The policy root is where the human started the session.
沙箱边界由操作者定义,不由模型定义

沙箱边界由操作者定义,不由模型定义

二、CVE-2026-22708:自动批准的允许列表

NVD 的记录写得很清楚:在 Cursor 2.3 之前,当 Cursor Agent 运行在 Auto-Run 模式且开启 Allowlist 模式时,某些 shell 内建命令(built-ins)可以在不出现在允许列表中、也不需要用户批准的情况下执行。攻击者借此通过直接或间接提示注入,设置、修改或删除影响「受信任命令」行为的变量,把环境本身投毒,于是像 git branch 这类被自动批准的命令就会执行任意载荷。修复版本是 2.3。这里的教训很具体:允许列表的安全前提是「这条命令的语义固定」;而 shell 内建命令恰恰能改写后续命令的运行环境,语义并不固定。

# env_guard.py - shell built-ins can rewrite the environment they run in
SHELL_BUILTINS = {"export", "set", "unset", "alias", "source", "cd", "eval",
                  "readonly", "declare", "typeset", "trap", "umask", "hash"}

def sanitise(command_argv: list[str]) -> list[str]:
    if not command_argv:
        raise PermissionError("empty command")
    head = os.path.basename(command_argv[0])
    if head in SHELL_BUILTINS:
        raise PermissionError(f"shell built-in not allowed: {head}")
    return command_argv

# CVE-2026-22708: an allowlist that auto-approved trusted commands, while
# built-ins quietly poisoned the environment those commands relied on.
出站通道是 trifecta 的第三条腿

出站通道是 trifecta 的第三条腿

三、CVE-2025-59532:模型自己写了沙箱边界

Codex CLI 在 0.2.0 至 0.38.0 版本之间,因沙箱配置逻辑的缺陷,会把模型生成的 cwd 当作沙箱的可写根目录,其中包含用户会话启动目录之外的路径。这条逻辑绕过了原本的工作区边界,从而允许任意文件写入与命令执行(网络受限的沙箱限制不受影响)。修复版本 0.39.0 的做法正是本文想强调的:它会对边界做规范化与校验,确保沙箱策略依据的是「用户启动会话的位置」,而不是模型生成的那个路径。被修复的不是提示词,是权限来源。

# allowlist.py - deny by default, then keep the list tiny
ALLOWED = {
    ("git", "status"), ("git", "diff"), ("git", "log"),
    ("pytest",), ("ruff", "check"), ("npm", "test"),
}

def is_allowed(argv: list[str]) -> bool:
    if not argv:
        return False
    for prefix in ALLOWED:
        if tuple(argv[:len(prefix)]) == prefix:
            # No shell, no pipes, no chaining, no redirection.
            return not any(tok in "|&;<>`$()" for tok in argv)
    return False

# An allowlist is a promise that these exact commands are safe.
# The moment it can rewrite its own environment, that promise is void.

四、爆炸半径比一个工具大得多

OWASP 的 State of AI Surveyor 追踪了 53 个智能体项目,其中 28 个属于编码智能体;安全公告数量最多的五个仓库全部是半自主框架或编码智能体:n8n(57)、Claude Code(22)、AutoGPT(15)、Dify(13)、Roo-Code(11)。同一份报告指出,提示注入被映射到《OWASP Top 10 for Agentic Applications》十个类别中的六个。供应链同样是软目标:CVE-2025-59528 就是 Flowise 3.0.5 中 CustomMCP 节点把用户提供的 MCP 配置直接交给 Function() 构造函数执行,最终拿到 CVSS 10.0 的远程代码执行。协议层、智能体层、技能与包层,都出现了同一类问题的不同变体。

# egress.py - the third leg of the lethal trifecta is the network
ALLOWED_HOSTS = {"api.internal.corp", "pypi.org", "registry.npmjs.org"}

def check_egress(url: str):
    host = urlparse(url).hostname or ""
    if host not in ALLOWED_HOSTS:
        raise PermissionError(f"egress blocked: {host}")
    if urlparse(url).scheme not in ("https",):
        raise PermissionError("https only")

# Private data + untrusted content + outbound channel = exfiltration.
# You can keep the first two and still be safe by breaking the third.

五、两句话记住风险模型

第一句来自 Simon Willison 的「致命三要素」(lethal trifecta):任何同时具备「访问私有数据」「接触不可信内容」「能对外通信」的智能体,都可以被一次注入改造成数据外泄工具。第二句来自 Meta 公布的「Agents Rule of Two」:把这三项当成预算,无人值守的智能体最多满足其中两项,三项全占就必须有人在环。Anthropic 在 2026 年 9 月 10 日发布的威胁情报报告提供了另一端证据:攻击者已经把智能体编码能力用在整个攻击链上,包括用子智能体做认证前后侦察与代码审查;同时也出现专门窃取 AI API 凭据、并用受害者密钥继续扩大攻击的案例。攻防两侧都在自动化,这已经不该当成理论风险。

{
  "event": "agent.tool_call",
  "agent": "claude-code",
  "argv": ["git", "branch", "--show-current"],
  "approved_by": "allowlist:v3",
  "env_diff": {"BASH_ENV": "<injected>"},
  "cwd": "/home/dev/repo",
  "sandbox_root": "/home/dev/repo",
  "decision": "deny",
  "reason": "env_diff modifies a trusted command's runtime",
  "host_reachable": ["api.internal.corp"]
}

// Log the environment diff, not just the command. Both CVEs below were
// invisible in command-only logs.

六、五道控制,按性价比排序

第一,让沙箱根来自操作者并对候选路径做规范化与越界校验(代码示例 1)。第二,拒绝 shell 内建命令,并监控环境变量差异(代码示例 2)。第三,允许列表按「命令 + 精确子命令元组」匹配,禁止管道、串联与重定向(代码示例 3)。第四,把出站主机做成白名单,直接断掉 trifecta 的第三条腿(代码示例 4)。第五,工具调用日志必须记录环境差异、cwd 与沙箱根,而不只是命令行(代码示例 5)——上面两枚 CVE 在只记录命令日志的体系里都是不可见的。把这五条做完,再谈提示词层的防护。

📌 常见问题 FAQ

CVE-2026-22708 到底影响了什么?

Cursor 2.3 之前,在 Auto-Run 与 Allowlist 模式下,部分 shell 内建命令可不经允许列表与用户批准执行,攻击者可通过提示注入投毒环境变量,使被自动批准的受信任命令执行任意载荷。已在 2.3 修复。

CVE-2025-59532 的根因是什么?

Codex CLI 0.2.0 至 0.38.0 的沙箱配置逻辑把模型生成的 cwd 当作可写根目录,从而越过工作区边界,导致任意文件写入与命令执行。0.39.0 改为按用户会话启动目录做规范化与校验。

提示注入只能是编码智能体的问题吗?

不是。OWASP 将提示注入映射到其智能体 Top 10 的六个类别,涵盖浏览器、企业助手与工作流平台。只要智能体消费不可信内容并拥有动作能力,风险就成立。

允许列表为什么反而放大了风险?

因为它把「自动批准」与「语义固定」绑在一起。当被批准的命令能改写自身运行环境时,允许列表就在替攻击者预批载荷的执行。

如果只能做一件事,该做哪件?

限制出站。断掉对外发送通道即可打破「致命三要素」的组合,成本最低、收益最直接;之后再补沙箱根部校验与命令策略。