第 20 章 控制流隔离:把"谁做决定"和"谁执行"分开
同一个模型既读又干,边界就没了
第 19 章框定了威胁模型:私有数据 + 不可信内容 + 对外通信,三要素凑齐就危险。那第一层防御怎么立?
核心思路是控制流隔离:把"谁读不可信内容"和"谁下高权限命令"分开。如果同一个模型既读不受信任的数据、又控制高权限工具,那么一条提示注入路径就能把良性上下文变成特权操作。这种耦合把信任边界抹平了,你很难推理"危险行为到底从哪来的"。
这一章讲三个模式,从模型层到工具层到通道层:
- Dual LLM Pattern(双 LLM 模式):一个模型读,一个模型干,中间传符号变量。
- Tool Capability Compartmentalization(工具能力分区):把"读 / 处理 / 写"拆成不同信任区。
- Authenticated Authority Channel(认证权威通道):区分"影响推理的信息"和"授予权限的输入"。
模式一:双 LLM 模式(Dual LLM Pattern)
问题
当同一个模型既读不可信内容、又控制高权限工具时,一条提示注入路径就能把良性上下文变成特权操作。这种耦合把信任边界抹平了,让"危险行为从哪来"变得难以推理。
方案
拆分角色:
- 特权 LLM(Privileged LLM):负责规划和调用工具,但永远不碰原始不可信数据。
- 隔离 LLM(Quarantined LLM):负责读不可信数据,但零工具权限。
- 数据以符号变量或验证过的原语传递,特权侧只操作引用。
两个模型之间用显式契约:隔离模型只能输出类型化值(或是不透明句柄),特权模型只能操作批准的 schema 和工具。这保留了能力,又防止原始不可信文本进入高权威的推理路径。
var1 = QuarantineLLM("提取邮箱", text) # 返回 $VAR1,原始文本没暴露
PrivLLM.plan("把 $VAR1 发给老板") # 特权侧只碰引用
execute(plan, subst={ "$VAR1": var1 }) # 执行时才替换注意关键点:特权模型做计划时看到的只有 $VAR1 这个占位符,恶意文本没有机会在它的上下文里"说话"。隔离模型读了恶意内容也无所谓,因为它根本没有工具可以用。
证据
- 证据等级:新兴(
emerging),来自 Simon Willison(2023),被 Beurer-Kellner 等人(2025)采用为安全设计模式。
怎么用
- 邮件/日历助手、订票 Agent、API 驱动的聊天机器人,以及任何"处理不可信用户输入 + 有特权动作(数据库写、外部 API 调用、文件系统操作)"的系统。
- 隔离模型的输出格式要严格(类型化值或句柄),别让它输出自由文本给特权模型。
取舍
- 好处:信任边界清晰;兼容静态分析(数据流可追踪)。
- 代价:复杂度增加;跨两个模型调试,出错要两边找。
模式二:工具能力分区(Tool Capability Compartmentalization)
问题
MCP 和各类 Agent 框架经常把三类能力混进一个工具里:私有数据读取器(邮件、文件系统)、网页抓取器(HTTP 客户端)、写入器(API 修改器)。这就造出了"致命三要素":恶意输入可以触发一条链,在一个操作里读敏感数据、外传、改系统。
方案
在工具层做能力分区:
- 把单体工具拆成 读 / 处理 / 写 三个微工具。
- 跨能力类组合工具时,要求每次调用都显式获得用户同意。
- 每个类在隔离的子进程里跑,配作用域受限的 API key 和文件权限。
把每个能力类当成独立的信任区,有自己的运行时身份和策略检查。跨区组合要求显式策略评估和短期委托 token,让 Agent 无法悄无声息地把"读 + 抓取 + 写"链成一条高风险路径。
# tool-manifest.yml
email_reader:
capabilities: [private_data, untrusted_input]
permissions:
fs: read-only:/mail
net: none
issue_creator:
capabilities: [external_comm]
permissions:
net: allowlist:github.com调用时验证工具链,拒绝三能力类齐全的链:
// 跨区验证
function validateToolChain(tools: string[]): boolean {
const classes = new Set(tools.map(t => getCapabilityClass(t)));
if (classes.has("PRIVATE_DATA") &&
classes.has("UNTRUSTED_INPUT") &&
classes.has("EXTERNAL_COMM")) {
return false; // 检测到致命三要素
}
return true;
}证据
- 证据等级:新兴(
emerging),来自 Simon Willison 对 MCP 的批评:他警告"某个 MCP 把三种模式混进了单一工具"。 - Clawdbot(生产验证)用基于 profile 的策略做了参考实现;NVIDIA NeMo Guardrails 做基于策略的强制。
怎么用
- 从 CI 自动生成 manifest,别手写。
- Agent 运行器构造行动计划前,先查 manifest。
- 标记任何会重新造出致命三要素的工具链组合。
- 按能力类分组工具(fs、web、runtime、memory),分配 profile(minimal、coding、messaging),防止混用。
- 调用时验证工具链:三类能力齐全就拒绝。
取舍
- 好处:细粒度;和模块化架构配合好。
- 代价:工具开销更多;权限随时间蔓延的风险(permission creep)。
模式三:认证权威通道(Authenticated Authority Channel)
问题
用工具的 Agent 经常把操作者指令、常设策略、检索到的文档、网页、消息、工具输出、摘要、委派报告全放进同一个模型上下文。不可信内容里于是可以出现看起来像合法命令的祈使语言。如果运行时没有保留"每条指令样语句来自哪",来源内容就可能被当成授权、被持久化成策略、或被用来重定向一个有后果的动作。
这个问题比"判断一段内容是否恶意"更广。一段良性文档可以描述一个真实流程,却不授权 Agent 执行它。 缺失的区别是:哪些是"可以影响推理的信息",哪些是"可以授予权限的输入"。
方案
在整个工作流里维护一条可区分的"权威通道":
- **控制状态(control state)**包含:认证过的当前操作者意图、显式采纳的常设策略、运行时强制的权限。
- **归因内容(attributed content)**包含:外部来源、检索文件、分析中的消息、工具输出、模型生成的摘要、委派报告。
- 归因内容可以提供事实和候选参数,但不能扩大范围、授予权限、修改持久策略、或授权执行。
- 一个特权执行点在执行前,独立地把提议的操作、目标、目的地、权限、参数对照控制状态验证。
- 摘要、压缩、委派、持久化必须保留这个区分。来源丢了,就降级为只读工作,或者在有后果的执行前重新请求新鲜的认证批准。
control = load_authenticated_intent_and_policy() # 认证过的意图和策略
content = retrieve_with_provenance() # 带来源的检索内容
proposal = reason_over(control.task, content) # 基于任务+内容推理
if proposal.exceeds(control.authority):
request_authenticated_approval(proposal) # 超出权限就请求认证批准
else:
executor.validate_and_run(proposal, control, content.provenance) # 独立验证再执行标签和提示词分隔符能帮模型理解上下文,但它们不是安全边界。要拿作用域受限的凭据、能力控制、出口限制、确定性验证或与后果相称的隔离来支撑这个模式。
和目录里其它更窄的控制互补:Action Selector 把不可信反馈从有限控制循环里拿掉;策略门控代理和沙箱授权在工具边界强制权限;上下文最小化在不再需要时移除污染材料;致命三要素减少危险能力组合。认证权威通道做的则是在不可信材料必须通过检索、摘要、压缩、委派、持久化、执行一直可用时,端到端保住"权威 vs 内容"的区分。
证据
- 证据等级:混合(
mixed)。 - 有价值发现:OWASP 记录了通过外部内容的间接提示注入,建议隔离并明确标识不可信内容;CaMeL 为一个更强、更窄的架构(把可信控制流和不可信数据分离,在工具调用时强制能力策略)提供了有效性证据。
- 未验证:被引用的研究没有直接验证这个横跨检索、摘要、压缩、委派、持久化的更宽端到端模式;单靠提示词标签从未被证明能可靠防护,所以除非运行时也强制边界,否则不能把行为版本描述成"抗提示注入"。
怎么用
- 定义哪个认证通道能提供当前操作者意图。别仅仅因为能访问文件、收件箱、仓库或工具结果,就推断出权威。
- 外部材料进模型上下文前,附加来源类型和溯源。在摘要和交接里保留这些标签。
- 检索到的祈使句保持归因。别自动把它们提升进记忆、循环任务、配置、策略或身份。
- 候选动作从认证任务里推导,把来源内容当证据和提议参数。
- 执行时,独立验证操作、接收者/目标、目的地、参数、范围、有效权限,别依赖自由形式的源文本。
- 压缩或委派后如果运行时重建不了权威边界,停止有后果的执行,继续只读分析,或获取新批准。
- 用配对用例测试:良性内容、合法任务相关流程、以及一个声称批准或重定向有后果参数的对抗变体。
取舍
- 好处:减少意外"权威洗白"(authority laundering);支持在不可信内容上灵活推理;创造清晰的强制和审计点;适用于严格有限动作选择器不切实际的场景。
- 代价:需要认证意图、感知溯源的上下文构造、独立的执行检查;标签占上下文,可能被不支持的运行时丢掉;批准回退会增加摩擦。
- 局限:溯源建立的是归因,不是真实性或授权。 控制状态可能过期、被攻破、或绑定到错误主体。这个模式减少权威洗白,但不让 LLM 变得可信、不净化恶意数据、不证明来源真实性、也不替代技术隔离。
三个模式怎么选
| 场景 | 推荐模式 |
|---|---|
| 读不可信数据 + 有特权动作(邮件助手等) | 双 LLM 模式 |
| 单体工具把读/抓取/写混在一起 | 工具能力分区 |
| 检索内容必须一路可用,又要分清楚权威 | 认证权威通道 |
三个模式是控制流隔离的三个层面:双 LLM 在模型层分开"读"和"干",工具分区在工具层拆开"读 / 处理 / 写",权威通道在数据流层区分"信息"和"授权"。可以组合:双 LLM 管模型角色,工具分区管工具信任区,权威通道管数据流动。
实践清单
- 读不可信数据的模型,别给它工具权限;有高权限工具的模型,别让它看原始不可信文本
- 模型间传符号变量 / 类型化值,特权侧只操作引用,别传原始文本
- 单体工具拆成读/处理/写微工具,跨能力类组合要显式同意
- 工具 manifest 从 CI 自动生成,运行时按能力类验证工具链
- 三类能力(私有数据 / 不可信输入 / 对外通信)齐全的工具链,调用时拒绝
- 区分"控制状态"和"归因内容":内容能供事实,不能扩权、不能授权执行
- 执行前用独立执行点验证操作/目标/权限/参数,别信自由文本
- 摘要、压缩、委派、持久化都保留来源标签,丢了就降级只读
- 溯源不等于真实性:控制状态可能过期或被攻破,不能替代技术隔离
本章小结
- 双 LLM:隔离模型读、特权模型干,中间传符号变量,信任边界清晰。
- 工具能力分区:读/处理/写拆成独立信任区,三类齐全的工具链直接拒绝。
- 认证权威通道:控制状态给权威,归因内容给事实,执行前独立验证。
- 控制流隔离的核心:让"读的人"和"干的人"不是同一个,提示注入就少一条路。
下一章讲权限与审批:隔离做好了,剩下的是"能干什么、谁点头"。人在回路审批、精确动作授权绑定、策略门控工具代理、沙箱授权。