7874 字
39 分钟
当 Agent 拿到 Shell:从路径围栏到纵深防御

写在前面:这篇文章是怎么来的#

这篇文章不是从一份安全规范开始的,而是从我实现自己的 Coding Agent 时产生的一点不安开始的。

在 PaiCLI 的 Policy 模块里,我先后实现了几项看起来很合理的安全能力:

  • 用 PathGuard 解析路径的真实位置,阻止文件工具越过当前工作区;
  • 用 CommandGuard 拦截删除根目录、格式化磁盘、关机重启等明显破坏性命令;
  • 对文件写入、Shell 和非只读 MCP 操作增加 HITL 人工审批;
  • 用脱敏 JSONL AuditLog 记录工具输入、执行结果、审批信息和工作目录。

做到这里,Agent 已经不像最初那样直接相信模型输出并执行了。它有路径边界、有命令检查、有人工确认,也留下了操作记录。

有一天和朋友聊天,说起我这个小demo,我说这是我要写在简历上去秋招的项目,然后简单介绍了一下。他说我来考考你。

如果危险命令没有出现在黑名单里怎么办?你又怎么能保证黑名单和危险命令就可以拦住所有危险呢?难不成自己一直去不断维护一直增加黑名单?即使操作在cwd项目目录,就能说明安全吗?实际上就随便问了两三个问题,我就答不上来了了。

回来之后,我就开始思考agent的安全策略。这些问题已经不是继续添加几条正则就能解决的了。于是我就和 GPT5.6 讨论,然后又试了试最新的Qwen3.8,在AI的帮助下,加深了对安全策略的一些理解并整理了笔记,最后再让 Codex 帮我润色修改,最终整理成了这篇博客。

因此,本文是从 PathGuard、CommandGuard、HITL 和 AuditLog 这一层防护的漏洞为起点,抛开代码本身从逻辑上去思考如何去做agent的安全策略。当 Agent 从“会回答问题”变成“能读文件、执行命令、访问网络和调用外部系统”之后,一套安全策略究竟还缺什么?

这套安全策略到底在防谁#

整理整套安全策略时,我一直有一个疑问:我们到底是在防模型,还是在防外部攻击者?

如果是在防外部攻击者,对方可能根本不知道我的 Agent 是怎么实现的,又怎么影响它?

答案是:大多数时候,模型不是攻击者,而是一个可能被误导、被借用的决策者;Tool Runtime 才是把决定变成真实副作用的执行者。

外部攻击者也不需要直接登录 Agent,更不一定需要知道内部代码。他只要控制 Agent 将来可能读取或执行的一段内容,就可能影响整条执行链:

外部攻击者
├── 在 README、Issue、网页、邮件或日志中放入恶意指令
├── 在仓库中放入 conftest.py、Makefile、Git hook 或安装脚本
└── 提供被污染的依赖、插件、MCP 工具描述或工具返回值
Agent 读取内容或运行项目
模型把恶意内容误当成可信指令
模型提出 Tool Call
Tool Runtime 使用开发者的文件、网络和身份权限执行
文件被修改、秘密被读取、数据被外传

这就是间接攻击。攻击者没有直接控制模型,而是控制了模型的输入、执行对象或工具供应链,再让 Agent 替他完成后续动作。

举个例子,假设 Coding Agent 收到任务:“修复这个项目的测试失败。”

它会读取 README、执行测试、根据报错安装依赖、修改文件并重新运行命令。对用户来说,这是一条正常的开发流程;对攻击者来说,这条流程却提供了很多可以提前布置的入口:

  • README 可以把恶意指令伪装成项目说明;
  • pytest 可以自动加载仓库里的 conftest.py
  • npm install 可以执行依赖包的安装脚本;
  • Shell 可以读取工作区之外的文件;
  • 网络可以把环境变量和源码发送出去;
  • 后台子进程可以在主命令结束后继续运行。

攻击者不一定要正面突破某条安全规则。他只要找到一条 Agent 会自然经过、但防守方没有建模的路径,就可以从旁边绕过去。

所以 Agent 安全实际上同时防三种情况:

  1. 防模型在没有恶意的情况下犯错;
  2. 防外部攻击者通过不可信内容、恶意仓库和供应链借用模型;
  3. 防某一层已经失守后,Tool Runtime 继续把影响扩大到宿主机、秘密和外部系统。

路径围栏、命令黑名单、人工审批和审计日志分别解决了其中一部分问题,却没有任何一层能够独自承担完整的安全责任。本文接下来就从进攻方寻找入口、防守方逐层收缩能力的博弈出发,讨论一套 Agent 安全策略应该如何形成。

一、真正的对手不是“恶意模型”#

讨论 Agent 安全时,很容易把问题简化成:“如果模型生成了危险命令怎么办?”

这个问题太窄了。真实环境里的威胁至少来自四个方向。

模型误操作#

模型没有恶意,但可能幻觉路径、拼错参数、误解用户意图,或者忽略变量为空、工作目录错误等运行条件。路径围栏、参数校验、危险模式检测和人工确认,主要是在处理这一类问题。

间接提示注入#

Agent 会读取 README、Issue、网页、日志、邮件、图片文字和工具返回值。这些内容原本应该作为数据,却可能夹带“忽略原任务并执行某个动作”的指令。

模型需要理解自然语言,攻击内容同样使用自然语言。防守方无法只靠一句“把外部内容当作数据”就保证模型永远不会被劫持。

恶意仓库和供应链#

仓库本身就是代码。测试配置、构建脚本、Git hook、依赖安装脚本、编译插件都可能执行程序。

当 Agent 运行 pytestmakenpm install 时,它不是在运行一个抽象的“安全开发动作”,而是在执行仓库作者和依赖作者提供的代码。

恶意或被入侵的工具#

MCP Server、插件和远程 API 也属于供应链。工具描述可以隐藏提示注入,工具可以错误声明自己是只读,也可能在升级后改变行为。

因此,Agent 安全要处理的不是“模型会不会变坏”,而是:

当模型、上下文、仓库或工具中的任意一环不可信时,它们最多能够造成多大影响?

二、攻防博弈的核心:攻击者寻找“假设裂缝”#

很多漏洞都可以还原成同一个结构:

防守方以为系统会怎样
实际运行时可能完全不同
两者之间的差,就是攻击入口

例如:

防守方的假设实际可能发生的情况
黑名单已经覆盖危险命令危险语义可以通过其他程序和参数无限变形
路径检查通过就能安全使用检查后文件可能被替换成符号链接
文件位于工作区就可以访问工作区内也有密钥、特殊文件和挂载目录
命令退出就代表任务结束子进程和后台任务可能继续运行
用户看到的就是实际执行的控制字符、Unicode 和解析差异可以制造两份内容
写了审计日志就能追踪Agent 可能拥有修改或删除日志的权限

攻击者的策略是寻找这些裂缝。防守方可以用三个基本动作应对。

翻默认#

如果坏情况数量无限,就不要追着补黑名单,而是默认拒绝,只放行能够明确描述的好情况。

默认允许 + 枚举禁止项
默认拒绝 + 枚举允许项

限能力#

如果无法可靠判断一个动作的真实意图,就不要把全部希望放在预测上,而要限制它实际拥有的文件、网络、进程、身份和凭据能力。

统一事实#

检查、审批、执行和审计必须围绕同一个规范化动作展开,不能让系统在不同阶段看到不同版本。

这三个动作不是三项具体产品,而是可以不断推导安全措施的认知框架。

三、第一轮博弈:PathGuard 对抗路径越界#

最常见的路径围栏实现大致如下:

class PathGuard:
def __init__(self, root: str | Path):
self.root = Path(root).resolve()
def validate(self, value: str | Path) -> Path:
candidate = Path(value)
if not candidate.is_absolute():
candidate = self.root / candidate
resolved = candidate.resolve()
resolved.relative_to(self.root)
return resolved

防守方先把工作区根目录解析成绝对路径,再把输入路径中的 ... 和符号链接解析掉,最后通过 relative_to() 判断真实路径是否仍位于工作区。

它可以有效阻止:

  • ../../secret.txt 之类的普通目录穿越;
  • 直接访问工作区外的绝对路径;
  • 检查时已经存在、并指向工作区外的符号链接;
  • /project/project-evil 这种字符串前缀误判。

这是必要的第一道防线,但攻击方不会停在字符串层。

攻方一:检查和使用之间调包#

PathGuard 检查路径后返回一个 Path,文件工具稍后再调用 open()。如果攻击者可以并发修改工作区,就可能在检查通过后、真正打开前,把文件或父目录替换成指向外部的符号链接。

这类问题被称为 TOCTOU:Time of Check to Time of Use。MITRE 在 CWE-367 中指出,只要检查和使用仍然通过文件名分两步完成,资源就可能在中间发生变化。

攻方二:路径在工作区,资源却不属于工作区#

路径层级无法完整描述底层对象:

  • 硬链接可能让工作区内外的两个名字指向同一个 inode;
  • bind mount 可能把宿主机目录挂载到工作区内部;
  • Windows junction 和 reparse point 有自己的解析语义;
  • FIFO、Unix Socket、设备节点并不是普通文件;
  • 工作区内部的 .env、私钥和 Git 配置,本来就不应该被随意访问。

因此,“位于工作区”只能回答位置问题,不能代替资源授权。

攻方三:绕过文件工具#

PathGuard 只有在工具主动调用它时才有效。新工具忘记接入、第三方插件直接打开文件、Shell 使用绝对路径或先 cd ..,都可以绕开这层保护。

cwd 只是进程的初始目录,并不是文件系统沙箱。

守方升级#

生产环境里的文件安全至少需要继续增加:

  1. 资源级授权:分别定义哪些目录允许读、允许写、允许创建和允许执行。
  2. 敏感区域保护:默认禁止 .env、密钥、.git、凭据目录和策略配置。
  3. 安全打开文件:在打开阶段禁止跟随符号链接,尽量让检查和使用围绕同一个文件描述符完成。
  4. 文件类型约束:拒绝设备、FIFO、Socket 等特殊文件。
  5. 写入配额:限制单文件大小、文件数量、总磁盘和写入速率。
  6. 操作系统隔离:让进程从根本上看不到未授权目录。

PathGuard 仍然值得保留,但它的正确定位是“低成本路径预检”,不是文件系统沙箱。

四、第二轮博弈:CommandGuard 对抗危险命令#

命令黑名单通常会先标准化空白,再检查自定义字符串和内置正则:

normalized = " ".join(command.strip().split())
for blocked in blacklist:
if blocked in normalized:
raise CommandPolicyError(...)
for pattern in destructive_patterns:
if pattern.search(normalized):
raise CommandPolicyError(...)

它适合拦截删除根目录、格式化磁盘、关机重启等明显破坏性命令,也能减少模型幻觉造成的低复杂度事故。

问题在于:防守方检查的是字符串,攻击方利用的是程序语义。

攻方一:改变表达,不改变效果#

同一个效果可以通过不同参数顺序、长短参数、解释器、脚本、重定向、构建工具或间接调用完成。

一个命令没有出现 rm,不代表它不能删除文件;没有出现 curl | sh,不代表它不能先下载脚本再执行;只有 pythonnodemake,也不代表行为安全。

OWASP 的 OS Command Injection Defense Cheat Sheet 将白名单、参数化和避免直接调用 Shell 作为主要防御,黑名单只能承担补充作用。

攻方二:利用 Shell 解释器#

如果执行器使用 shell=Truecreate_subprocess_shell(),整个字符串会交给 Shell 解释。管道、重定向、变量展开、命令替换、子 Shell 和后台任务都会改变行为。

Python subprocess 安全说明 明确提醒:一旦显式调用 Shell,调用方就要负责处理空白和元字符带来的注入风险。

在 Agent 场景里,问题比传统“固定命令拼接一个用户参数”更严重,因为模型可能控制整条命令。仅靠转义无法把任意 Shell 变成安全能力。

攻方三:使用合法命令完成危险动作#

危险并不等于“删除系统目录”。以下动作同样可能造成严重后果:

  • 读取环境变量并发送到外部地址;
  • 修改 Git hook、Shell 启动文件和定时任务;
  • 发布软件包、推送代码或部署生产环境;
  • 扫描内网和访问云元数据;
  • 启动后台进程;
  • 写满磁盘、耗尽内存或创建大量进程。

这些动作可能全部由常见开发工具完成。

守方升级#

防守方不应该试图写出一份“完美黑名单”,而应该逐步减少自由 Shell 的使用:

任意 Shell 字符串
解析后的程序与参数
固定程序 + 结构化参数
面向业务的专用 Tool

例如,不让模型生成任意 git 命令,而是提供 git_statusgit_diffgit_commit 等边界明确的工具。每个工具拥有独立的参数 Schema、风险等级和授权规则。

对于必须保留的 Shell:

  • 默认拒绝,明确允许少量程序和参数组合;
  • 使用参数数组执行,避免不必要的 Shell 解释;
  • 固定可信可执行文件路径;
  • 不继承未经清理的 PATH;
  • 在沙箱里运行;
  • 将未知命令升级为人工审批,而不是默认放行。

CommandGuard 也应该保留,但它只是已知风险探测器,未命中不能证明安全。

五、第三轮博弈:HITL 对抗自动化风险#

人工审批解决的是“高风险动作是否得到了人的授权”。它适合文件写入、Shell、删除、发布、外部通信和非只读 MCP 操作。

但是,攻击方也会攻击审批过程本身。

攻方一:让危险动作看起来合理#

间接提示注入可以让模型为危险命令生成一段非常可信的解释,例如“这是项目初始化要求”或“必须上传日志才能诊断问题”。

用户审批的往往不只是命令,还包括模型对命令的叙述。如果叙述被劫持,HITL 就可能从安全门禁变成社会工程入口。

攻方二:制造展示与执行差异#

控制字符、超长参数、Unicode 同形字符、零宽字符和不同层的转义规则,都可能让用户看到的内容与 Shell 实际解释的内容不同。

攻方三:利用审批疲劳#

当所有普通读操作都弹窗时,用户很快会形成机械点击习惯,最终选择“全部允许”。审批越多,不一定越安全。

守方升级#

可靠的 HITL 需要满足几个条件:

  • 按副作用和不可逆程度划分风险,不是所有动作一刀切;
  • 展示规范化后的程序、参数、目标文件、网络目的地和外部副作用;
  • 转义控制字符,并对异常 Unicode 提升风险;
  • 审批结果绑定工具名、完整参数、用户、会话和有效期;
  • 参数发生任何变化后,旧审批自动失效;
  • 高影响动作支持二次确认、回滚预览或更强身份验证;
  • 保留明确的拒绝、跳过和全局熔断能力。

OWASP AI Agent Security Cheat Sheet 同样强调高影响动作分类、执行前预览、明确审批、审计和中断回滚。

HITL 的职责是确认风险,不是替代底层隔离。

六、第四轮博弈:恶意仓库对抗执行环境#

到这里,防守方已经拥有路径检查、命令策略和人工审批。但攻击者仍然可以把恶意行为藏在“被允许的开发动作”里。

pytest → 加载 conftest.py 和插件
npm install → 执行生命周期脚本
make → 执行仓库定义的 Makefile
编译项目 → 执行编译插件和构建脚本

在防守方眼里,这是测试和安装;在操作系统眼里,这是任意代码执行。

这说明所有依赖“理解命令含义”的策略都会存在上限。真正的兜底必须进入能力层。

守方:把执行放进笼子#

一个面向 Coding Agent 的基础沙箱可以这样设计:

宿主机
├── LLM 调用与任务编排
├── Policy / HITL / Audit
└── 隔离的 Tool Runtime
├── 非 root 用户
├── 系统目录只读
├── 仅挂载当前工作区
├── 默认断网
├── 不挂载主目录、SSH Agent、Docker Socket
├── 不继承宿主敏感环境变量
└── 限制 CPU、内存、进程和磁盘

Linux 可以使用容器、bubblewrap 或 Landlock。Linux 内核的 Landlock 文档 将其定位为非特权进程可使用的访问控制机制,用于限制文件系统和网络等环境权限。

Docker 也提供只读根文件系统、进程数限制等运行参数;网络方面可以使用 --network none 完全隔离容器网络。

沙箱解决的不是“判断命令是否善良”,而是:

即使模型被提示注入、仓库带毒、命令策略判断失败,恶意代码仍然看不到宿主机重要资源,也没有足够能力扩大影响。

七、第五轮博弈:网络出口与数据外传#

只限制文件写入还不够。只要进程能够联网,它就可能把读到的数据送出去。

攻击方可以利用:

  • HTTP 请求上传源码和环境变量;
  • DNS 查询编码少量秘密;
  • Git、npm、pip 等合法协议建立外部连接;
  • URL 重定向访问内网;
  • DNS 重绑定改变目标地址;
  • 访问云元数据服务获取临时凭据。

所以“允许联网”本身就是一项高价值能力。

守方策略#

防守方应从默认断网开始,再按任务开放最小出口:

  1. 沙箱默认没有外部网络。
  2. 安装依赖时通过受控代理,只允许可信仓库域名。
  3. 每次 DNS 解析和 URL 重定向后重新检查最终地址。
  4. 拒绝回环、私网、链路本地和云元数据地址。
  5. 根据工具限制 HTTP 方法、请求体和上传大小。
  6. 网络工具不应同时持有工作区秘密和任意文件读取权限。
  7. 记录最终 URL、解析地址、重定向链和数据分类。

网络策略不能只看域名字符串,还要关注最终连接到哪里、发送了什么数据。

八、第六轮博弈:超时、子进程和资源耗尽#

很多实现会给命令设置超时,然后在超时时调用 proc.kill()。这并不等于任务已经停止。

如果被杀的是 Shell 父进程,它创建的子进程可能仍然存活;后台任务可能已经脱离;大量输出可能在截断前就占满内存;循环写入可能在超时前耗尽磁盘。

攻击者不一定追求控制宿主机。让系统不可用、持续消耗 API 和计算成本,同样是有效攻击。

守方策略#

  • 每个任务使用独立进程组或隔离 PID Namespace;
  • 超时后终止整个进程树,而不是只杀父进程;
  • 使用 cgroup 限制 CPU、内存、进程数和 I/O;
  • 限制文件数量、总写入量和临时目录大小;
  • 对 stdout/stderr 流式限流,不能等读完后再截断;
  • 禁止未托管的 nohup、终端复用器和任意后台驻留;
  • 必须运行的后台任务进入任务管理器,具备身份、状态、资源和终止接口;
  • 对 Agent 循环增加最大轮数、最大工具调用数、Token 和费用预算。

真正的运行时治理要保证所有副作用都“可见、可算、可杀”。

九、第七轮博弈:身份、环境变量与秘密#

如果 Tool Runtime 与开发者使用同一个系统身份,它自然拥有开发者的文件权限、网络权限和环境变量。

即使 sudo 被命令黑名单阻止,进程仍可能访问:

  • LLM API Key;
  • GitHub Token;
  • 数据库连接串;
  • SSH Agent;
  • 云厂商凭据;
  • Docker Socket;
  • 内部管理接口。

攻击者真正需要的不是 root,而是一把已经存在的高价值钥匙。

守方策略#

  • Agent 使用独立、低权限身份运行;
  • 每个用户、项目和任务之间建立隔离;
  • 默认构造干净环境,不直接复制宿主 os.environ
  • Tool 按任务领取短期、最小 Scope 的临时凭据;
  • 凭据与工具、资源、用户和有效期绑定;
  • 使用后立即撤销,不写入工作区和普通日志;
  • 把“可以读秘密”和“可以向外发送”拆成不能同时自动获得的能力。

最小权限的价值在于:即使前面的判断全部失败,攻击者拿到的也只是一个没有重要钥匙的低权限进程。

十、第八轮博弈:MCP 扩展与信任转移#

MCP 让 Agent 可以动态接入文件、数据库、浏览器、邮件和业务系统。它也把本地安全问题扩展成跨服务器的信任问题。

攻击方可以尝试:

  • 在工具描述和返回值里植入提示注入;
  • 把有副作用的工具标记为只读;
  • 用相似工具名遮蔽可信工具;
  • 在用户批准安装后改变工具定义;
  • 使用 MCP Server 自身的高权限替低权限用户执行动作;
  • 让多个 MCP Server 之间互相传递或外泄数据。

OWASP MCP Security Cheat Sheet 将工具投毒、定义变更、工具遮蔽、Confused Deputy、过宽权限和供应链攻击都列为关键风险。

守方策略#

  • MCP Server 和工具必须进入显式允许列表;
  • 工具描述、Schema 和版本经过审核并固定哈希;
  • 定义变化后重新授权,不能沿用旧审批;
  • 客户端独立判断风险,不能盲信 readOnlyHint
  • 每个 Server 使用独立的最小权限凭据;
  • 本地 MCP Server 运行在沙箱中;
  • 工具输入和输出都要验证、分类和审计;
  • 不同信任级别的 Server 隔离上下文和数据;
  • 高风险 MCP 操作仍然走参数绑定的 HITL。

MCP 的本质不是“安全工具协议”,而是一条新的能力接入通道。通道越通用,越需要独立授权和隔离。

十一、第九轮博弈:AuditLog 与反取证#

脱敏 JSONL AuditLog 是很好的起点。一行一个事件,方便追加、读取尾部和容忍单行损坏。

一条有价值的安全日志至少应该包含:

{
"timestamp": "2026-07-28T10:00:00Z",
"session_id": "session-123",
"tool_name": "execute_command",
"input": {"command": "..."},
"risk_level": "high",
"policy_version": "policy-v3",
"approval_id": "approval-456",
"approver": "user-789",
"outcome": "error",
"cwd": "/workspace/project"
}

但是审计面对两个主要对手。

攻方一:秘密进入日志#

仅按 tokenpasswordsecret 等字段名脱敏,会漏掉命令字符串、URL、文件内容和嵌套文本中的秘密。日志系统本身可能成为新的数据泄露源。

攻方二:删除或篡改证据#

如果日志文件位于 Agent 能访问的位置,恶意进程可以清空、修改或伪造记录。审计和执行使用同一系统身份时,可信度天然受限。

守方升级#

  • 日志写到 Agent 无权修改的外部系统;
  • 使用只追加存储、哈希链或签名提高篡改可见性;
  • 记录结构化动作,而不是只记录展示文本;
  • 对秘密进行数据分类和内容级脱敏;
  • 为调用、审批、执行结果和子进程建立统一关联 ID;
  • 记录策略版本和工具定义版本,保证事后可以还原决策;
  • 对异常频率、连续拒绝、权限提升和外传行为告警;
  • 提供全局熔断,能够停止一个用户、会话或全部 Agent Runtime;
  • 高风险写操作前创建快照,失败后能够恢复。

审计不是为了假装永远不会出事,而是为了出事后能够发现、止血、追踪和恢复。

十二、把所有防线连成一条执行链#

一套更完整的 Agent 安全执行链可以表示为:

用户输入 / 网页 / 仓库 / MCP 返回值
不可信上下文边界
LLM 生成行动提议
Tool Schema / 参数校验 / 规范化
默认拒绝 / 白名单 / 风险分类
参数绑定的 HITL 审批
隔离的 Tool Runtime / 沙箱
│ │ │
▼ ▼ ▼
文件策略 网络策略 进程与资源配额
│ │ │
└──────────┴──────────┘
低权限身份与临时最小凭据
实际副作用与 ToolResult
外置审计 / 监控 / 熔断 / 快照恢复

这条链可以按时间分成三个阶段。

阶段核心问题主要控制
执行前这件事是否应该开始?信任来源、结构化工具、默认拒绝、风险分类、HITL
执行中即使判断错了,后果能否受控?沙箱、文件边界、网络隔离、资源限制、最小权限
执行后能否发现、终止、追踪和恢复?外置审计、监控告警、进程治理、熔断、快照

纵深防御并不是不断增加相似规则,而是承认每一层都会失败,然后让下一层专门接住上一层漏过来的风险。

十三、PathGuard 和 CommandGuard 还有没有价值#

有,而且应该保留。

PathGuard 很适合挡住普通目录穿越和模型生成错误路径;CommandGuard 很适合拦截已知的明显破坏性命令。它们速度快、结果确定、容易测试,也容易向用户解释。

问题不在于它们“没用”,而在于不能把它们承担不了的责任交给它们。

控制正确定位不能证明什么
PathGuard路径规范化与越界预检路径对应的资源一定安全
CommandGuard已知危险模式探测未命中的命令一定无害
HITL人对高风险动作进行授权被批准的动作不会造成损失
AuditLog提供追踪和取证线索日志已经防止事件发生
Sandbox限制实际能力和爆炸半径上层策略可以不再校验

一套成熟的设计不是在这些控制中选一个,而是让它们分别承担最适合自己的职责。

十四、如何为一个新 Tool 推导安全策略#

以后新增数据库、浏览器、邮件或代码执行工具时,不需要先背一份安全清单。可以顺着七个问题推导:

  1. 定资产:它会接触哪些文件、秘密、网络、进程和外部系统?
  2. 定来源:哪些参数可能被用户、模型、网页、仓库或第三方工具影响?
  3. 找裂缝:当前设计依赖了哪些“我以为”?实际可能怎样偏离?
  4. 翻默认:哪些安全操作可以明确枚举?剩余情况是否应该拒绝或审批?
  5. 限能力:即使决策被劫持,它最多能读什么、写什么、连到哪里、消耗多少资源?
  6. 统一事实:校验、审批、执行和日志是否使用同一个规范化动作?
  7. 准备下一层:这一层失效后,怎样发现、终止、追踪和恢复?

以网页读取工具为例:

假设裂缝推导出的防御
以为网页是数据,实际可能包含恶意指令内容标记为不可信;读取器不持有写权限和秘密
以为访问公网 URL,实际可能重定向到内网默认拒绝私网和元数据地址;每次重定向重新校验
以为展示的是原 URL,实际连接到另一个地址记录最终 URL、重定向链和解析地址
以为读取不会外传,实际后续工具可能发送内容拆分读取与外发能力;网络出口独立授权

同样的方法可以迁移到数据库写入、邮件发送、代码发布和远程 MCP 操作。

十五、结语:安全不是预测模型,而是约束后果#

Agent 安全最容易陷入的误区,是不断研究“模型为什么会生成这条危险命令”,然后继续向黑名单添加规则。

这种工作有价值,却永远无法完成。Shell、脚本、仓库和外部工具能够表达的行为近乎无限,而防守方能够枚举的字符串始终有限。

更可靠的思路是:

先找出“我以为”和“实际可能”之间的裂缝。数不尽的坏,就翻转默认;猜不透的意图,就限制能力;担心出现两份现实,就让检查、审批、执行和审计共享同一份事实。一层无法消除风险,就让下一层继续接住。

PathGuard 和 CommandGuard 是这套体系的起点,不是终点。

真正的生产级安全策略,需要把结构化工具、默认拒绝、HITL、文件授权、执行沙箱、网络隔离、供应链治理、资源配额、最小权限、秘密管理、外置审计和恢复机制连接成一个闭环。

目标不是证明 Agent 永远不会犯错,也不是证明模型永远不会被劫持。

目标是:即使判断出错,系统仍然能够控制最坏后果。

参考资料#

当 Agent 拿到 Shell:从路径围栏到纵深防御
https://jiqingjiang.github.io/posts/tech/agent/当agent拿到shell从路径围栏到纵深防御/
作者
erode
发布于
2026-07-28
许可协议
CC BY-NC-SA 4.0