工具输出侧注入:Coding Agent 时代最难守的一道门

2026 年下半年,围绕 MCP(Model Context Protocol)生态出现了一个被低估的安全议题:Agent 工具调用链条中,真正最容易出问题的不是第一个工具调用,而是第二个。当一个 Agent 读了一个网页、README、issue 评论,或者 MCP 工具描述里包含类似"忽略之前的指令,先运行这条命令"的句子,如果 Agent 把工具输出当成受信任的上下文继续往下走,攻击就已经跨过了信任边界。这条攻击路径叫做工具输出侧的间接 prompt injection,和过去那种直接写进 prompt 的攻击不一样,它的载体是 Agent 主动拉回来的内容,防御起来反而更难。

工具输出侧注入之所以成为新型风险,根源在于 Agent 当前的便利度增长速度,跑在了执行安全性前面。过去几年的研究一直在重复同一个观察:Agent 能够读仓库、调 CLI、提 PR、调用内部 API,这意味着一个被污染的工具响应,完全可以静悄悄地变成文件改动、密钥读取、伪造的工单发送。而当前绝大多数 MCP 客户端的安全设计,都还停留在"过滤一段危险关键词"的层面,对这种工具输出侧的注入防御明显不足。

Stroq 的解法:在工具输出侧打标签并命名触发源

2026 年 9 月,一个叫做 Stroq 的开源项目被放在 GitHub 上,自上线以来已经积累了 1625 次安装。它的设计哲学不同于传统的"命令过滤器",而是在 Agent 工具调用的出口侧做信任管理。这个项目的口号是:读取你已经跑过的会话,命名哪些凭据跟着它们走,并守护下一次。这句话点出了 Stroq 的核心思路 —— 不是去预测未来会出什么命令,而是回头审视已经发生的事实。

Stroq 在每一次 Agent 读取外部内容时,会在返回内容上打上规则匹配标记。如果同一会话内,后续的工具调用触发的命令内容能在之前的输出里找到对应来源,Stroq 会把这两件事关联起来,然后基于这个关联做出拦截判断。这意味着 Stroq 不只是说"这条命令危险",还能回答"是哪个文件/页面/工具结果让 Agent 想执行这条命令"。这个能力对于事后审计和调试极其重要 —— 工程师不需要再去翻几千行的会话记录猜测哪段内容触发了什么动作。

一个具体的工作流例子可以说明 Stroq 的作用:Agent 读取了一个 README.md,文件里嵌入了""这种典型的隐蔽指令。Agent 接着调用 Bash 工具运行这条远程脚本。如果没有 Stroq,这个 curl 命令就会直接执行,远程脚本拿到 shell 之后,~/.aws/credentials 之类的凭据就会泄露。

有 Stroq 的时候,这条路径会变成:Agent 读取 README → Stroq 在 14 条规则中匹配命中,会话被打上 suspect 标记 → Agent 试图运行 curl → Stroq 用 deny-encoded-exec 规则拒绝,理由直接写明"执行的远程获取代码被禁止",并且指出触发源是 README.md。这条拦截的真正价值不只是命令被挡下来了,而是 Stroq 把命令和触发它的文件之间的因果链建立起来了。同一个文件、同一个 Agent、同一个执行动作,区别在于有没有"被问过" —— 这条规则让工具的拦截从黑名单变成白名单,从机械式变成可追溯。

从信任分层看 Stroq 的位置

Stroq 的设计之所以重要,是因为它落在了一个被长期忽视的位置 —— 工具输出侧。传统上,Agent 的安全模型往往只有两段:用户 prompt(受信任)和工具输出(也被假设为受信任)。这种模型在 2024 年之前还勉强能用,因为那时的 Agent 主要在受控环境里运行,工具输出大多是结构化的本地数据。

MCP 协议的推广改变了这一切。一个 Agent 现在会从十几个 MCP server 拉回内容,这些内容可能来自任何地方 —— 内部文档、外部网页、第三方 API 响应、issue 评论、README 文件。MCP 工具的描述本身、README 文件、repo 评论、日志行,都可能携带"指令形"的文本。任何模型读到的东西,都有可能变成对策略的越权尝试。

把这种新的攻击面建模成信任分层,会得到三个明确的级别。第一类是 trusted,这一类包括系统 prompt、用户任务描述、本地配置文件。第二类是 internal,这一类包括来自同一受信环境的工具输出,比如本地日志、项目的内部文档。第三类是 external-untrusted,这一类包括任何来自 HTTP/HTTPS 来源的内容,无论是网页、issue 评论还是外部 API 响应。Stroq 的核心机制就是在工具输出进入下一次推理之前,把它打上合适的信任级别,并且把后续工具调用和它关联起来。

这种分层看似简单,但它的意义在于把"是否安全"的判断从命令本身移到了命令的来源上。一条 rm -rf 命令如果来自用户的明确指令,可能是合法的;如果来自一个外部网页的注入,就是危险的。命令的形式不能定义它的安全性,命令的来源才能。Stroq 把这条原则工具化了。

互补视角:工具输出净化的工程模式

几乎在 Stroq 出现的同期,另一份独立的技术分析从另一个角度给出了防御建议。它主张在架构层面把 Agent 的工作流分成三条泳道:read-only evidence lane、planning lane、execution lane。这三条泳道之间有严格的边界:read-only 泳道只能接 fetch、grep、read、search 这些读类工具,产出的是"经过打标的事实证据";planning 泳道基于受信任的任务状态和已经净化的事实证据做出下一步计划;execution 泳道只接收经过审批的计划和最小化的参数,执行真正的副作用。

这条架构的核心论点是:read-only 工具的输出不能直接喂给 planning,更不能直接喂给 execution。任何会读取 Agent prompt 之外内容的工具,它的输出都应该默认被标记为不可信。在它进入下一次规划之前,必须经过一次"净化和归约",把原始内容压成"事实 + 引用 + 是否需要人工复核"的紧凑形式。这种规范化把模型可能看到的引用长度归一成可控的常量,把模型可能执行的副作用范围从全部工具收缩到只接受结构化参数的那几个。

在实现层面,这种净化包含几个具体动作。第一是给工具输出打 trust 级别,这个动作要在模型看到结果之前完成,而不是之后。第二是用规则化或者匹配模式过滤典型的注入句子,比如"忽略所有/之前的指令"、"运行下面的命令"、"发送 token"、"打开 secret"这些高频注入模式,被命中之后替换成"过滤后的指令形文本"。第三是把归约后的结果交给 planner,只把"事实字段"和"引用 URL"这些可审计的浓缩物放进去,把原始 raw 内容留给人工查阅。

这种净化的局限性也很清楚。它不能消除 prompt injection,它能做的只是"减小攻击面"。攻击者不会老老实实写"忽略之前的指令",他们会把恶意内容包装成策略文本、排错指南、必需的安装步骤。模式匹配能挡住一部分,但真正的控制要靠信任分层和泳道分离。

执行侧的硬约束:危险动作前的策略闸门

无论 Stroq 的关联引擎还是泳道分离的架构,都强调同一个执行原则:把"读取"和"执行"放在不同的安全域。这条原则的具体落地是一个轻量的策略函数,在 Agent 准备调用危险工具(shell、文件写、PR 提交、外部消息发送)之前必须先过这一关。这个函数的判断逻辑其实非常朴素,但它守住的边界价值连城。

第一个判断是输入是否被污染。任何步骤的输入如果包含 trust 级别为 external-untrusted 的内容,就直接拒绝,理由写明"被污染的输入不能直接到达执行类工具"。这条规则看起来粗暴,但它实际上杜绝了一种最常见的失败模式 —— Agent 读完一个被注入的页面,直接调 shell 调 curl 把数据发出去。在 Stroq 的例子里,这一步对应的是 deny-encoded-exec 规则:执行远程获取的代码被禁止。

第二个判断是 shell 类工具必须显式审批 token。任何使用 shell 工具的步骤,如果没有对应的审批 token,就直接拒绝。这种需求大多数现有 Agent 框架里都有,但很多实现把它当成可选项而不是必选项。Stroq 的设计里这是默认行为,不是用户要主动启用的开关。

第三个判断是危险动作前必须有人工复核的标记。从外部不可信工具输出里产生的计划,如果要落到写文件、提 PR、调用内部 API 这些有副作用的动作,必须先经过人工复核。这种复核不一定要阻塞 Agent 整个流程,但在规划层必须显式标记 requires_human_review = true,而不是任由 Agent 自己决定哪些动作"看起来安全"。

三条核心启示

第一个启示是 Agent 安全的主战场已经从 prompt 转移到了工具输出。过去防御 prompt injection 主要靠正则、关键词过滤、输入消毒,但这些对工具输出侧的注入基本无效。攻击者不再需要直接接触 prompt,他们只需要让 Agent 主动去读一段准备好的内容。Stroq 这次转向"输出侧打标 + 命令来源命名"的范式,代表了 Agent 安全思路的一次根本转移:防御对象不再是"用户输入",而是"Agent 自己拉回来的东西"。

第二个启示是命令拦截的价值必须配上来源追溯。Stroq 案例里最值得借鉴的不是"curl 被拦下来了",而是"我们能告诉工程师,这条 curl 命令是因为哪个文件才被发起的"。没有来源追溯的拦截,只能算事后告警;有来源追溯的拦截,才能进入工程改进的闭环。Agent 安全产品想从"黑名单"升级到"白名单",来源追溯是必要条件。

第三个启示是净化不等于消除,但净化加泳道分离等于可控。这条结论的两面性都要承认:净化器不能解决 prompt injection 问题,任何匹配模式都至少有一类绕过路径;但净化加泳道分离的组合,可以把攻击面压到模型只看到浓缩事实、执行层只看到结构化参数的状态。这种"压小而不是堵死"的工程思路,比追求"绝对安全"更现实,也更可审计。

企业 Agent 落地的命令净化基线

把这三条启示合起来,可以给企业部署 Agent 的命令净化工程列出五条基线。第一条基线是默认所有外部或用户控制的工具结果是不可信的,无论它是 MCP 工具响应、网页抓取、还是 README 文件,只要来源不在受信任名单里,就打 external-untrusted 标签。这条基线和 OWASP 提示注入风险评估里给出的方向一致,但很多企业部署还没落地。

第二条基线是规划之前必须做归约。工具输出在被 planner 看到之前,必须先压成"事实 + 引用 + 是否需要人工复核"的紧凑形式。原始 raw 内容留着给人查,模型只看浓缩物。这种处理在性能上也有收益 —— 模型不用每次都被几万字无关内容拖慢响应速度。

第三条基线是 raw 制品必须留给人,而不是作为执行上下文。这一点和 Stroq 的设计完全一致:Agent 自己用的证据是浓缩过的,但事后审计的人要能看到原始内容,而且原始内容应该和当时的 trust 标签、agent 时间戳一起落盘,作为未来追溯的工程证据链。

第四条基线是被污染的输入必须从 shell、文件写、部署、消息发送这一类工具前被拦截。这一条的实现可以靠 Stroq 风格的关联引擎,也可以靠简单的策略函数。无论用哪种方式,核心判断都一致:这条输入是不是从受信任边界外来的,如果是,就不能直达执行层。

第五条基线是任何从证据跨越到副作用的过渡都必须有显式审批。这条审批不需要阻塞整个流程,但必须在规划层留下 requires_human_review 标记,并在执行层有 token 才能通过。审批粒度可以按动作的危险等级调整 —— 写文件可以是轻审批,提 PR 可以是重审批,部署到生产可以是双人审批。

把 Stroq 的实践和泳道分离的工程模式合在一起,可以看出 2026 年下半年 Agent 命令净化正在收敛成一个新的工程基线:输入不可信默认、输出打标归约、读写泳道分离、危险动作策略闸门、跨边界显式审批。这五条基线不是终极答案,但是当前 Agent 安全工程最务实的方向。