为什么静态策略对 Agent 不够用
静态出站策略假设目标集合在 Agent 运行之前就是可知的。对于人类工作流,这个假设大体成立:一个服务账号访问一组固定的主机,一个开发者访问已知的包仓库,一个 cron 任务与一个数据库通信。这份列表是有限的、可审查的,而且变化缓慢。
Agent 打破了这个假设,因为工具调用是在运行时组合出来的。目标往往是推导出来的,而不是事先声明的。一个 retrieval 步骤返回一个 URL,一个 summarisation 步骤从该 URL 中提取主机名,一个 fetch 步骤去调用它。没有工程师把这个主机名写进策略文件,因为在编写策略时没有工程师知道它。这次调用在构造上就是合法的,并且对预先批准的列表不可见。
The usual response is to widen the list until it stops blocking. That produces the opposite failure: an allowlist broad enough to admit every derived destination is effectively an allow-everything rule with extra steps. The policy still looks like a control, but it no longer discriminates. Between the two failure modes there is no static setting that works, because the policy is being asked to answer a question it cannot see.
更深层的问题在于,目标本身就是错误的判断单位。同一个端点可以是良性的,也可以是恶意的,取决于 payload 和产生它的步骤。对内部 metadata 服务的调用,在获取配置时是例行公事,在参数是凭据路径时则是严重问题。对合作伙伴 API 的请求,当 body 是状态查询时没问题,当 body 包含 Agent 本次会话读到的所有内容的拼接时就有问题。仅按主机过滤,丢掉了区分这些情况的信息。
运行时意图实际检查什么
运行时意图检查把决策从调用去哪里转移到调用想要达成什么。这需要看的不仅仅是目标。
参数,而不只是目标。 参数、payload 形态,以及这些值的来源。来源是最重要的部分,也是静态工具通常缺失的部分。如果一个参数可以追溯到用户消息,它是一种值。如果它可以追溯到一个读取了不可信内容的工具的输出,它是另一种值。在轨迹中追踪这种血缘关系,才能让检查区分用户提供的参数和注入文档提供的参数。

序列位置。 一次调用相对于产生它的轨迹是否可疑。在一次失败的身份验证尝试之后立即读取 secrets 存储,与在 provisioning 运行的第一步读取它,是不同的事件。同一次调用,处于不同位置,含义不同。这就是为什么意图检查需要把近期轨迹作为输入,而不只是单个工具调用。
频率与重复。 同一个良性调用连续四十次,与同一次调用只发生一次,是不同的事件。重复往往是区分枚举与单次查询的信号,而且它测量起来很廉价,因为计数器已经存在于编排层中。
一个把这些联系起来的具象机制:维护一个按运行划分的来源图,其中每个值都携带一个污点标签,记录哪些来源对它有贡献。当一次工具调用即将触发时,guard 从该调用的参数、这些参数上的污点标签、该调用在轨迹中的位置,以及到该目标的近期调用频率,组装出一个特征向量。一个小型分类器对该向量打分。策略依据的是分数,而不是主机名。污点传播是承重的部分;没有它,分类器只能从表面语法上猜测。
延迟预算
意图检查位于模型和工具之间,所以每一毫秒都按步骤付费。一个每个任务发起几十次工具调用的 Agent,要付几十次 guard 成本,而这个成本在关键路径上。一个每次调用都增加明显延迟的 guard 改变的是产品,而不只是安全态势。
实际的结构是分层的。廉价启发式先运行:目标类别、参数形态、污点标签是否存在、调用频率阈值。这些是表查找和比较,能在没有模型介入的情况下解决绝大多数调用。昂贵的分类只在模棱两可、廉价层无法决断时运行。升级规则必须公开,而不是隐式的,因为它定义了系统会捕捉什么、不会捕捉什么。如果规则是“当任何参数携带不可信污点标签且目标在内部类别之外时升级”,这是一个运营者可以推理、审查者可以质疑的陈述。
诚实的权衡是,分层用覆盖率换取延迟。廉价层放行的调用永远不会到达分类器,所以启发式看不见的任何东西都会被设计性地漏掉。收紧升级规则以捕捉更多,意味着更频繁地支付分类器成本。不存在既最快又最敏感的设置;这个选择必须被明确做出,并随着 Agent 行为的变化而重新审视。
The cost of getting it wrong is also asymmetric in a way that alert-based thinking obscures. Measure false positives in agent steps, not in alerts. A blocked step can invalidate a whole run: the agent loses a tool result, the trajectory derails, and the task either fails or completes with a degraded result the user cannot distinguish from a correct one. A guard that fires too often does not produce a queue of alerts to triage; it produces silent failures that look like model errors. That is why the false-positive budget for an agent guard is tighter than for a conventional network control, and why the guard's

运营这个模型
分类器不是一次性的产物。把意图模型与 Agent 一起做版本管理。策略变更就是一次部署:它随 Agent 版本一起发布,记录在运行轨迹中,并且可以用同样的方式回滚。把策略当作独立于 Agent 漂移的配置,正是 guard 最终执行没人记得写过的规则的原因。
保留一个 shadow 模式,记录本会发生的拦截但不强制执行,并每周审查差异。Shadow 模式是在 guard 开始消耗运行之前了解其误报率的唯一方法。每周审查是调整升级规则的地方,也是 Agent 工具使用模式漂移在变成事故之前显现出来的地方。
把已确认的事故作为带标签的轨迹反馈回去。分类器的训练集就是事故日志,这意味着事故审查流程和模型训练流程是同一个流程。每一个已确认的事故都成为一个附带其轨迹的正样本;每一个被审查推翻的 shadow 模式拦截都成为一个负样本。这闭合了循环,让模型保持最新,而不需要一个没人维护的独立数据收集工作。
这里的权衡是运营负担。一个从事故中学习的 guard 要求事故审查及时且一致,这是一项长期承诺,而不是一个项目。无法持续做到这一点的团队,用一个更简单、被充分理解的启发式层会更好,而不是用一个悄悄过期的分类器。
从中可以得出什么
静态允许列表没有错;它们是不拟合的。它们回答的是关于目标的问题,而风险存在于参数、来源和序列之中。运行时意图检查回答的是正确的问题,但前提是意图信号在热路径上足够廉价可评估。把检查分层,公开升级规则,用被作废的运行而不是告警来衡量误报,并把模型与 Agent 一起做版本管理。安全属性不是每一次调用都被预先批准。而是每一次调用都在上下文中被判断,且成本是 Agent 在每一步都负担得起的。
封面照片:Atlantic Ambience / Pexels。
