第 23 章 安全审查

这本书从第 7 章起就在讲安全,但那些内容分散在七八个章节里,每一条都是就地解决一个具体问题。

这一章不引入任何新机制。它只做一件事:把这些防线横着排一遍,看中间有没有缝。


一、换一个视角提问

前面各章的安全内容大致是这样分布的:

表 23-1 安全职责的跨章回顾

在哪一章 讲了什么
第 7 章 材料分区、注入治理、判空与出口、输出白名单
第 9 章 权限最小化、回执与事实主张的对应
第 15 章 不变量、安全降级、拒绝的持续约束
第 17、18 章 渲染契约、发出前验证、逐字否决
第 20 章 探针与追踪的安全边界
第 21 章 对抗用例、越权测试

每一条都对。但安全不是各条正确的总和——攻击者不按你的章节顺序走,他找的是那些没有任何一章负责的地方。

所以这一章换一个提问方式:

不问“我做了哪些防护”,而问“如果我要攻破这套系统,我会从哪里进”。

这两个问题的答案经常不一样。前者会得到一份令人安心的清单,后者会得到一份让人不安的清单。

如果把本章作为安全复查入口,只需要先掌握下面六个最小概念。这里给的是审查口径,详细建模仍以对应章节为准:

表 23-2 安全审查的六个最小概念

概念 本章中的最小定义 审查时要问什么
不可信输入 任何能够被外部影响写入的内容 它能不能被当成系统指令执行?
证据 对外部事实的一次可追溯观察 它证明了什么,又不能证明什么?
作用域 证据、状态和权限适用的对象范围 它属于哪个用户、需求、订单或产品?
闸门 只判断当前允许做哪些动作的唯一许可点 关键动作是否由一个权威位置批准?
不变量 无论走哪条路径都不能被破坏的底线 模型被诱导后,下游是否仍会拦截?
验证器 文本或动作提交前的最后否决层 越界内容能否在发出前被整条拒绝?

安全评审不必先掌握每个组件的实现细节,但必须能沿着这六个问题追到责任层。追不到的位置,就是本章要找的缝。

二、入口清单:什么算不可信输入

第一步是把所有能进入上下文的东西列出来,逐项标注可信度。

用户消息                 不可信
被分析的文档、邮件、附件   不可信
检索片段                 不可信(来源本身可能被人写入)
工具回执                 不可信(外部系统返回的文本)
其他会话的历史            不可信
上游注入的业务字段         结构可信,值不一定(第 7 章)
系统提示词与规则           可信
受控代码与校验规则的执行机制  可信;其结果仍受输入、配置与作用域约束

这里的“可信”要分清两层。执行机制可信,不等于它产出的每个业务值都天然是真实事实。 代码可以稳定执行错误的规则,也可以基于脏输入算出错误状态;它的优势是执行路径受控、结果可复现,而不是自动获得事实真实性。

对“输入内容是否应被当作指令”的判据则和“它来自哪个系统”无关:

能被外部影响写入的内容,就按不可信输入处理;受控执行机制的可信度,要与其处理的数据可信度分开判断。

第 7 章那次事故正好说明了这一点:那个“当前产品”字段来自公司自己的内部系统,值却是脏的。“内部”不等于“可信”——内部系统里同样存着用户填的备注、运营录入的文案、历史遗留的测试数据。

清单里最容易被漏掉的是工具回执。很多系统对用户输入很警惕,对接口返回的内容却毫无戒心,而那段文本可能来自另一个能被写入的地方:商品描述、工单正文、用户提交的表单内容。它经过一次接口调用之后,看起来就像是“系统数据”了。

这一节的自查动作很简单:把这张表填一遍,每一项写清楚它走的是哪一道隔离。 填不出来的那一项,就是缺口。

三、“最高优先级”为什么是弱约束

第 1 章那条规则被标注了三次“最高优先级”,仍然被违反。当时讨论的是可靠性;放在安全语境里,这件事的含义更重:

声明式约束的强度取决于模型愿不愿意遵守它——而攻击者的全部工作,就是让它不愿意。

把常见的做法按强度排一下:

弱  提示词里写"必须"                同层文本可以与它竞争
    提示词里写"最高优先级""绝对禁止"  还是声明,只是措辞更重
    材料分区 + "不执行材料内指令"     有效,但仍然是同一层的一条规则
强  代码层面不给这个权限              模型被说服了也没用

第 7 章那句话在这里是全章的支点:不要让模型的服从性成为唯一防线。

由此得到审查时最该做的一个动作:

对每一条安全规则,假设它失效,然后看后果。

如果答案是“什么也不会发生,下面还有拦截”,那这条规则是加固;如果答案是“那就出事了”,那它是一个单点——而这个单点恰好建立在一段可以被文本影响的判断上。

四、三层防线横向复查:缝在哪里

按第 7 章那个分层逐层查。每一层都写清楚它该拦什么,以及实践中最常见的缝

表 23-3 三层防线与常见缝隙

该拦什么 常见的缝
输入层 分区隔离、注入治理、判空与出口 新接入的数据源没走同一套隔离
权限层 能做什么由代码决定,不由模型自述 权限做了,作用域没做(下一节展开)
输出层 发出前验证、事实与输出白名单 只验证了字段值,没验证“这一轮该不该说这件事”
治理层 对抗用例进回归、定期演练 对抗集只在出事后追加,从不定期重跑;换模型时也不重跑

四条缝里有一个共同的形态:

防线是逐层建起来的,但缝几乎都出现在“新增的东西没走老路”上。

每接一个新知识库、新工具、新渠道、新的上游字段,都要重新问一遍:它走了这四层没有。这也是为什么下一节那份清单需要在每次接入时用,而不是只在上线前用一次。

五、被忽略的那一层:作用域

上一节那个“权限做了、作用域没做”值得单独展开,因为它是本章最实际的一个缺口。

权限最小化通常做成这样:这个角色能不能调用“查询订单”这个工具。但真正危险的往往不是能不能调,而是对谁调

能查订单              ← 权限
只能查当前用户的订单    ← 作用域

如果模型可以自由决定工具参数里的对象标识——订单号、用户 ID、会话编号——那么权限控制基本是形同虚设的:它确实有权调用这个工具,而调用的对象由一段可以被影响的文本决定。

正确的做法:

对象标识由会话上下文决定,不由模型输出决定。

模型可以提出“查询订单”这个动作,但不该由它给出“订单号 12345”——除非这个号码是经过证据与归并链进来的,并且属于当前会话的作用域(第 10、11、12 章)。执行层在真正调用之前,还要再校验一次这个对象是否落在本次会话被授权的范围内。

这一条把第 9 章的权限和第 10 章的作用域接了起来:权限回答“能不能做这类事”,作用域回答“能对谁做”。 两个都要有。

六、隐私:最有效的保护是不注入

隐私要从两个方向查。

输入侧:不该进上下文的,不要进。 常见的过度注入有三类:

  • 把用户档案整份塞进去,理由是“以防用得上”;
  • 带上其他会话的历史;
  • 注入内部字段——成本价、客户等级、风控标记、内部备注。

第三类最危险,因为它有个隐含前提:“模型只是拿来判断,不会说出来。”但模型看得见的东西,就存在被说出去的可能——被诱导、被误解、或者只是在总结时顺手带上。

判据可以直接用第 6 章那个消融动作:这一轮的判断,真的需要它吗? 需要不了就别放。

输出侧:不该说的,说不出去。 输出白名单(第 7 章)、事实快照与完成语约束(第 17、18 章)都在这一层。有一条规则要补:内部字段一旦进了上下文,输出层就必须有对应的禁止断言,并且这条断言要有对抗用例守着。

两侧的强度不一样,所以有一条排序:

最有效的隐私保护是不注入,其次才是不输出。 不注入是确定性的,不输出依赖判断。

七、追踪不该成为新的泄露面

第 20 章讲追踪时留了一条边界,这里收口。

追踪要完整,但完整的是因果元数据,不是原文。围绕它的审查项有五条:

谁能看追踪?         访问控制与审计
原文存多久?         保留期与自动清理
敏感字段怎么存?      脱敏、掩码或散列
探针能回显什么?      只允许经批准的非敏感诊断字段(第 7、20 章)
错误信息会外泄吗?    给用户的提示不应包含内部结构、字段名或原因码原文

最后一条最容易被漏。把原因码直译给用户,等于把内部规则清单告诉攻击者——他会知道系统在等哪个条件、哪一步被拦住了、绕过它需要满足什么。

回头看,第 17 章那条“渲染不许直译原因码”当时是作为体验和契约问题提出来的,它同时也是一条安全规则。横向复查的价值就在这里:同一条约束,在不同章节里的理由可能完全不同。

追踪的价值在于事后可解释,它不应该变成一个新的泄露面。

八、清单怎么用才不会退化

一份审查清单如果只在上线前用一次,几轮之后很容易退化成走过场。附录 D 提供了本章框架的可打印版本;填写时每个“通过”都要附证据位置。三个使用时机更实际:

  • 每次接入新的数据源、工具或渠道时——对照第四节那四层逐一确认;
  • 每次换模型时——重跑对抗集(第 21 章);
  • 定期复查——重填入口清单,重跑对抗集,检查保留期与访问控制。

还有一条来自上一章:清单条目本身也要有来源。 每一条为什么在清单里、防的是哪一类问题,要写清楚。没有来源的条目,积累一段时间后就没人敢删,也没人真的执行——它会变成又一种形态的机制膨胀(第 22 章)。

适用边界 本章是一次横向复查,不是完整的威胁建模,也不能替代专业的安全评估。它覆盖的是提示词系统特有的那部分风险——不可信文本进入决策链、模型被说服、内部信息经由自然语言泄露。至于账号体系、传输加密、依赖漏洞、密钥管理这些通用安全问题,本书完全没有涉及,它们同样重要,而且有成熟得多的方法论。

另外,这一章给的是审查框架。真实攻击手法在快速演化,具体的攻击样本和公开案例应当持续从安全社区获取并补进你自己的对抗集,而不是照抄任何一本书里的清单。

九、拆开讲完了,该合起来了

安全复查时,优先守住三条线:

能被外部影响写入的内容,就按不可信输入处理——不看它来自哪个系统;受控机制可信,不代表输入值天然可信。

对每条安全规则,假设它失效,看后果:后果是“出事”,它就是单点。

最有效的隐私保护是不注入,其次才是不输出。

第五篇至此完成:改动可以被证伪、失败可以被定位、变化可以被验证、增长可以被治理、防线可以被复查。

从第 3 章开始,本书一直把这些机制拆开讲——契约、上下文、证据、状态、闸门、分工、验证、评测、治理,一章一件事。

真正做一个系统的时候,它们是同时出现的。

所以最后一篇要把它们合起来,回答两个最实际的问题:从零开始做一个新项目,第一步该干什么?以及——手上已经有一份接近两千行的提示词,从哪里下刀? 后一种情况在第 25 章中对应较早的 1925 行审计快照。