策略可以给出判断,但它不能替现实承担后果。

摘要

在今天的软件系统里,Policy 正在被赋予越来越高的地位。

权限系统依赖 Policy,风控系统依赖 Policy,AI Agent 的工具调用依赖 Policy,企业审批、支付放行、数据导出、证书轮换、基础设施变更,也都依赖 Policy。只要策略引擎返回 Allow,系统就继续向前。

久而久之,我们很容易产生一种错觉:

只要策略判断通过,执行就是安全的。

但 Policy 从来不是安全本身。

它只是一个判断系统,基于某些输入、某些规则、某个时间点的状态,得出一次允许、限制或拒绝的结论。它可能判断正确,也可能判断错误;可能看到完整上下文,也可能只看到被截取、包装甚至污染后的信息;可能在决策时仍然有效,也可能在真实执行发生前,环境已经改变。

真正危险的,不是系统使用 Policy。真正危险的是,系统把 Policy 的输出,当成了安全事实本身。

这是「Policy 不是神谕」系列的第一篇。整个系列想拆掉的,不是「Policy 有没有用」这个问题,而是一种非常普遍、也非常危险的幻觉:只要策略判断为允许,执行就是安全的。 本篇先把地基打好——说清楚 Policy 到底是什么,以及为什么「判断通过」和「执行安全」是两件完全不同的事。


一、现代系统越来越依赖 Policy

Policy 并不是一个陌生概念。它可能表现为:

  • 一条权限规则;

  • 一个审批条件;

  • 一组额度限制;

  • 一个风险评分阈值;

  • 一套身份与角色映射;

  • 一条「满足条件即可执行」的自动化逻辑;

  • 一个由 AI 或风控模型给出的允许结论。

在最简单的系统里,Policy 可能只是一行判断:

如果用户是管理员,则允许执行。

用代码表达,大概是这样一段没有任何人会怀疑的逻辑:

def can_execute(user, action):
    if user.role == "admin":
        return Decision.ALLOW
    return Decision.DENY

在复杂系统里,它会迅速膨胀成一组条件的合取:

如果身份可信、设备可信、金额未超限、审批已完成、
风险评分低于阈值、当前时间处于允许窗口,
则允许执行。

对应的策略引擎往往写成这样:

def evaluate(ctx) -> Decision:
    if not ctx.identity.trusted:            return Decision.DENY
    if not ctx.device.trusted:              return Decision.DENY
    if ctx.amount > ctx.policy.limit:       return Decision.DENY
    if not ctx.approval.completed:          return Decision.DENY
    if ctx.risk_score >= ctx.policy.threshold: return Decision.DENY
    if not in_allowed_window(ctx.now):      return Decision.DENY
    return Decision.ALLOW

随着系统规模扩大,Policy 也越来越复杂。云端有云端的 Policy,本地有本地的 Policy,设备有设备的 Policy,业务系统有业务系统的 Policy,审批平台还有自己的 Policy。看起来,判断越来越完整,安全也应该越来越强。

但问题恰恰出现在这里。

当判断链条越来越复杂时,人们往往会开始把「判断通过」与「执行安全」混为一谈。系统不再追问:

  • 这个判断依据的输入是真的吗?

  • 这些规则现在仍然适用吗?

  • 审批时看到的对象,和最终执行的对象是同一个吗?

  • 策略系统是否被绕过或污染?

  • 当前执行环境是否仍然处于可接受状态?

它只看见了一个结果:

ALLOW

然后,真实执行发生了。

注意上面那段 evaluate 函数:它的每一个分支,都在读取 ctx 里的某个字段。而这些字段——identitydeviceamountapprovalrisk_score——没有一个是「事实」,它们全部是别人告诉策略引擎的信息。策略引擎从头到尾没有能力验证这些信息是否为真,它只是在这些信息之上做布尔运算。这一点,是理解本篇后续所有论证的关键。


二、Policy 是判断,不是事实

Policy 的本质,是在有限信息下做判断。它永远依赖三个基本要素:

  1. 输入(Input):告诉策略系统「现在发生了什么」;

  2. 规则(Rule):告诉策略系统「应该如何判断」;

  3. 状态(State):告诉策略系统「当前环境是否满足执行条件」。

只要这三者全部可靠,策略输出才有可能可靠。但现实系统从来没有这么理想。我们逐一来看这三个要素是如何塌陷的。

输入可能来自业务系统,而业务系统已经被攻击。 策略引擎读到的 amount = 98000payee = "供应商A",是业务系统序列化后传过来的。如果业务系统被注入,或者上游接口被中间人改写,策略引擎读到的就是攻击者想让它读到的值。它不会怀疑,因为它没有能力怀疑——输入对它来说就是公理。

规则可能来自 SaaS,而 SaaS 账户已经被接管。 很多系统把策略规则放在云端管理后台,方便运营随时调整阈值。这意味着「规则」本身是可写的,而写入权限的边界就是一个云端账户的登录态。一旦这个账户被钓鱼、被会话劫持,攻击者不需要绕过任何策略——他直接修改策略,让原本会被拒绝的操作变成合规。

状态可能来自缓存,而缓存没有及时更新。 「这个设备是否可信」「这个证书是否已吊销」「这个账户是否已被冻结」,这些状态判断常常读的是缓存或异步同步过来的副本。撤销一个权限,和这次撤销真正传播到所有执行点之间,存在一个时间窗口。在这个窗口里,策略引擎会用一个已经过期的「真」,放行一个本该被拒绝的请求。

除此之外还有几种更隐蔽的失效:

  • 审批结果可能是真的,但审批对象在提交之后发生了变化;

  • 风险评分可能很低,但模型没有识别出新的攻击方式;

  • 权限可能完全合法,但这个合法账户已经被诱导执行错误操作。

因此,Policy 输出的 Allow,真正表达的并不是:

这次执行一定安全。

它只能表达:

根据当前收到的信息,按照当前加载的规则,在当前可见状态下,这次请求被判断为允许。

这两句话之间,存在巨大差异。前一句像是一种保证,是对未来的承诺;后一句只是一次条件判断,是对过去某个瞬间的、基于二手信息的描述。

Policy 最容易被误用的地方,就是系统把后一句,当成了前一句。用一句话概括:Policy 输出的是「在我看到的世界里,这条规则没有被违反」,而不是「真实世界里,这件事可以安全发生」。


三、通过 Policy,不等于风险已经消失

抽象的论证容易被反驳,我们来看一个足够具体、也足够常见的例子。

假设一个企业内部系统规定:

金额低于 10 万元,并且经过两人审批,即可自动付款。

某次付款请求完全满足条件:

  • 金额为 9.8 万元;

  • 两名审批人都点击了同意;

  • 付款账户属于白名单;

  • 风险系统没有报警。

从 Policy 的角度看,这是一笔无可挑剔的合规请求。策略引擎会返回 ALLOW,日志里会留下一条干净、完整、可审计的通过记录。

但真实世界里,仍然可能存在很多问题:

  • 收款账户虽然在白名单里,但账户控制权已经发生变化——白名单校验的是账号,不是账号背后的人;

  • 审批页面展示的是供应商名称,实际请求中绑定的是另一个账户——人看到的系统执行的不是同一份数据;

  • 两位审批人看到的是同一份被伪造的业务背景,所谓「双人审批」在信息层面其实只是一个人被骗了两次;

  • 金额被故意拆分成多笔,单笔都低于 10 万阈值——规则约束的是单笔,攻击面在总额;

  • 攻击者使用了合法账户和合法流程,没有任何一步「越权」;

  • 审批完成后,执行参数在下游被替换——批准的是 A,执行的是 B;

  • 系统只验证了「是否审批」,没有验证「审批的究竟是什么」。

在这些情况下,Policy 没有被绕过,审批也没有被跳过,权限系统没有报错,日志里甚至可以看到一条非常完整的合法路径。但执行仍然是错的。

这里出现的两类问题,在安全工程里都有专门的名字,值得点出来:

其一是 TOCTOU(Time-of-Check to Time-of-Use,检查时刻与使用时刻之间的时间差)。审批(check)和执行(use)之间存在时间间隔,只要在这个间隔里对象或参数被替换,检查的结论就与执行的现实脱钩。策略引擎检查的是过去那一刻的世界,执行发生在现在这一刻的世界,两者之间没有人负责重新对齐。

其二是混淆代理问题(Confused Deputy)。合法账户、合法权限、合法流程,被用来完成一个本不该发生的操作。系统信任的是「谁在操作」和「流程是否走完」,而不是「这次操作本身是否正当」。当一个拥有正当权限的主体被诱导去做错误的事,所有基于身份和流程的策略都会齐刷刷地放行。

这说明一个重要事实:

安全风险并不会因为 Policy 返回允许而自动消失。

Policy 只是在规则覆盖范围内,没有发现拒绝理由。而「没有发现拒绝理由」,和「已经证明执行安全」,从来不是同一件事。前者是规则的沉默,后者是现实的保证;把规则的沉默读成现实的保证,是几乎所有「合规却出事」事故的共同根源。


四、Policy 无法替代执行边界

很多系统把 Policy 当作最后一道防线。策略引擎一旦判断通过,后面的执行层就不再重新检查,只负责机械地完成动作。

这种设计背后有一个隐含假设:

判断系统既然已经做出了决定,执行系统就没有必要再次怀疑。

但对高风险执行来说,这个假设并不成立。因为 Policy 与执行之间,始终存在距离。这段距离可能包含:

Policy 判断
   │
   ▼
参数序列化 → 接口调用 → 消息队列 → 服务转发
   → 数据转换 → 权限映射 → 下游系统解释
   → 最终设备或账户执行

在这条路径上,任何一个环节都可能改变原本的意图。审批时看到的是一个对象,执行时可能变成另一个对象;策略判断时确认的是一个金额,最终提交时可能被拆分或重新编码;Policy 判断通过的是「允许调用某工具」,但工具真正执行的具体参数,可能已经超出用户原本的理解。

在 AI Agent 时代,这段距离尤其致命。用户批准的可能是一个自然语言意图——「帮我把这份报告发给团队」,而 Agent 最终执行的是一串具体的工具调用:send_email(to=[...], attachments=[...])。从「意图」到「参数」的翻译过程,恰恰是提示注入、上下文污染最容易介入的地方。策略引擎如果只在意图层判断「发邮件这个动作允许吗」,它就永远看不到 toattachments 里被塞进了什么。

如果执行层只是相信上游已经判断过,而不再验证真实对象,那么 Policy 实际上只保护了一个中间状态。它保护的不是最终动作。

这也是为什么:

Policy 可以参与决策,但不能代替独立的执行控制。

执行边界必须面对真实即将发生的动作。它需要知道:

  • 最终执行对象是谁;

  • 最终金额是多少;

  • 最终权限范围是什么;

  • 最终参数是否与已批准意图一致;

  • 当前状态是否仍然满足约束;

  • 是否存在任何无法确认的关键条件。

Policy 可以告诉执行边界「上游认为它应该被允许」。但执行边界仍然需要独立回答那个更硬的问题:它现在是否真的可以发生。 前者是转述,后者是决断——一个成熟的系统不会把转述当成决断。


五、策略越复杂,越容易让人产生安全感

复杂系统有一种常见错觉:

规则越多,系统越安全。

于是团队不断增加:更多权限条件、更多审批层级、更多风险评分、更多黑白名单、更多上下文变量、更多自动判断逻辑。

这些机制当然有价值。但规则数量增加,并不一定意味着执行风险下降。有时它只意味着系统更难被人理解。

当 Policy 复杂到没有人能够完整解释时,团队往往开始依赖策略引擎本身的权威。只要系统显示:

Policy Passed

人们就默认所有问题都已经处理。这是一种典型的权威转移:因为没有人能推演清楚这几百条规则的全部组合,所以大家干脆不再推演,把「引擎说通过」当成「一定没问题」。规则的复杂度,反而成了停止思考的借口。

而复杂 Policy 本身也会带来新的风险:

1. 规则之间可能冲突。 一条规则允许,另一条规则限制,最终由谁决定,往往隐藏在实现细节里——比如规则的加载顺序、匹配优先级、或者某个「最后一条命中生效」的默认约定。安全边界不该由这种偶然决定。

2. 规则可能产生意外组合。 每条规则单独看都合理,但组合之后可能出现从未预料的放行路径。这类似于权限系统里的特权升级链:A 角色能做的、加上 B 角色能做的,组合起来变成了任何人都不该拥有的能力。

3. 规则可能长期未更新。 业务已经变化,攻击方式已经变化,但策略仍然停留在过去。一条三年前写的白名单,今天可能正在为一个早已易主的账户放行。

4. 规则可能依赖同一个错误来源。 多个策略看似独立,实际上都相信同一份身份、同一套业务数据或同一个云端状态。这一点在系列后续会专门展开——它是「多策略≠多源治理」的核心陷阱:三层判断读的是同一个数据库字段,本质上只是把同一个错误复制了三次。

5. 规则可能被业务压力削弱。 当策略频繁阻碍业务时,团队最容易做的不是修复系统,而是放宽阈值。「先把阈值从 10 万调到 50 万,回头再优化」——这句话往往就是安全边界开始崩塌的起点。

所以,Policy 的复杂度不应该成为安全性的证明。真正重要的是:它的输入是否可信、它的边界是否明确、它是否可以被独立验证、它是否拥有过大的执行权力,以及它失效时,系统会不会继续向前

最后一点尤其关键。一个安全的系统,应该在它的判断系统失效时变得更保守,而不是继续以为一切正常


六、策略系统最危险的能力,是把判断变成权力

Policy 原本只是判断工具。但在很多系统里,它逐渐变成了权力本身。

当 Policy 返回 Allow 时:

  • 支付自动发出;

  • 数据自动导出;

  • 账号自动创建;

  • 权限自动提升;

  • 服务自动重启;

  • 证书自动轮换;

  • AI Agent 自动调用外部工具;

  • 机器人自动操作真实设备。

此时,Policy 不再只是表达意见。它已经直接推动现实发生变化。

问题并不在于自动化。自动化是现代系统的必需品。问题在于一个更本质的结构问题:

判断系统是否有能力独自导致高风险执行。

如果一个 Policy 来源被污染,就能直接放行真实执行,那么这个策略系统实际上成为了单点权力:

  • 如果一个 SaaS 账户被接管,就能修改规则并立即生效,那么 SaaS Policy 就成了最终权力;

  • 如果一个本地管理员可以临时关闭限制,那么本地 Policy 就成了最终权力;

  • 如果审批平台只要显示「已通过」,执行层就无条件服从,那么审批 Policy 就成了最终权力。

这违背了一个基本的安全原则:

任何单一层,都不应该能够独自导致灾难性执行。

这条原则在其他工程领域早有成熟形态。核武器发射需要双人规则(two-person rule),没有任何一个人能单独触发;金融系统讲职责分离(separation of duties),发起和批准不能是同一方;分布式系统讲故障域隔离,一个域的崩溃不能拖垮全局。它们的共同内核只有一句话:不要让任何单点拥有制造灾难的能力。

Policy 应该能够提出建议、施加限制、收缩范围、要求确认。但它不应该因为一次判断,就天然获得改变现实的最终权力。判断和权力,本该是两样东西——把它们焊在一起,是很多系统在架构层面就埋下的隐患。


七、策略安全的第一步,是承认 Policy 可能错

很多安全设计从一开始就假设 Policy 是正确的。系统只考虑:

  • 如何防止未授权用户绕过 Policy;

  • 如何防止攻击者直接调用接口;

  • 如何保证策略引擎稳定运行;

  • 如何记录策略判断日志。

这些都重要。但它们仍然没有回答一个更根本的问题:

如果 Policy 本身判断错了,系统怎么办?

Policy 可能因为错误数据判断错,可能因为规则缺陷判断错,可能因为环境变化判断错,也可能因为有人合法修改了规则,却做出了错误决定。注意最后这一种:它不是「被绕过」,而是「被正常使用」——所有基于「防绕过」的防御在这种情况下完全失效,因为根本没有人绕过任何东西。

一个成熟的执行系统,不能只防止「没有经过 Policy 的请求」。它还必须防止:

已经经过 Policy、形式上完全合法、但实际上不应该发生的请求。

这要求系统从「信任 Policy」转向「约束 Policy」。这里有三个层层递进的转变:

  • 不是否定策略,而是限制策略的权力

  • 不是假设策略永远正确,而是假设任何策略都有可能失效

  • 也不是寻找一个绝对正确的策略中心,而是建立多个相互约束的边界

第三点是整个系列的转折。人们本能地想找一个「最聪明、最权威的裁判」来一锤定音,但安全恰恰不来自更强的中心,而来自更多互相不完全信任的边界。这一点,我们会在后续几篇里逐层拆开。


八、Policy 应该是输入,而不是最终裁决

在一个更安全的系统里,Policy 的位置应该被重新定义。它不是最终答案,它是执行控制系统的一项输入

例如,一次高风险执行可以由多个独立来源共同参与判断:

SaaS Policy   → 提供组织级规则
Hub Policy    → 提供本地治理状态
设备 Policy    → 提供物理边界约束
审批结果       → 提供人的授权意见
风险系统       → 提供动态风险信号
执行层         → 重新绑定真实参数与最终对象
              ─────────────────────────────
              → 收敛出一个受控的执行边界

这些判断共同参与决策。但任何一个来源,都不能单独说:

我已经允许,所以必须执行。

真正的安全结果,不应来自某个中心策略的绝对判断,而应该来自多个有限判断之间的约束与收敛

  • 当不同来源一致时,系统可以在明确边界内执行;

  • 当不同来源冲突时,系统应该缩小权限,而不是选择最方便业务的一方;

  • 当关键状态无法验证时,系统应该暂停,而不是假设一切正常;

  • 当执行对象发生变化时,系统应该重新绑定意图,而不是沿用旧的批准结果。

这里有一个必须讲清楚的前提:这些来源必须真正独立。 如果 SaaS Policy、Hub Policy、审批系统读的都是同一个被污染的身份数据,那所谓「多源」只是一个来源的三次回声,冲突永远不会发生,收敛也就失去了意义。多源治理的价值,建立在「不共享同一个信任根」之上。

这才是 Policy 在执行系统中更合理的位置:它参与定义边界,但它不独自拥有跨越边界的权力。


九、安全不是「允许」,而是受控地发生

传统策略系统常常把安全简化为一个二元结果:

ALLOW / DENY

允许,意味着安全;拒绝,意味着风险。

但真实执行并不只有「执行」和「不执行」两种状态。在这两个极端之间,还有一整片被忽视的连续空间。一个系统还可以:

  • 限制金额;

  • 限制频率;

  • 限制目标;

  • 缩短有效期;

  • 要求二次确认;

  • 延迟执行;

  • 进入只读状态;

  • 降低权限;

  • 切换到 Safe Mode;

  • 要求本地物理介入。

这说明真正的安全,不是简单判断「允许还是拒绝」,而是回答一个更精细的问题:

在当前不确定性下,这个动作最多能够以什么范围、什么权限、什么速度、什么条件发生?

把决策从「是否允许」升级为「允许到什么程度」,是一次根本性的思维转变。二元判断只有一个开关,而受控执行有一整块可调的边界——金额、频率、目标、时效、确认强度、执行速度,每一个维度都可以在不确定性上升时独立收紧。

用一个比喻:Policy 不应该只是打开一扇门。它更应该参与定义——门能开多大、可以持续多久、谁可以通过、能携带什么、出现异常时是否自动关闭、是否需要另一道独立边界共同确认。

所以,策略安全的目标,不是尽可能多地得出 Allow,而是确保:

即使判断存在偏差,真实执行仍然被限制在可承受范围内。

这句话,几乎是整个 Havenlon 体系的一句总纲:当系统无法证明当前状态仍然安全时,它不应该扩大执行能力,而应该收缩。


十、结语:策略不是安全本身

Policy 很重要。没有 Policy,复杂系统无法进行规模化治理;没有权限规则、额度限制、身份判断、审批条件与风险约束,系统也无法建立基本秩序。

但 Policy 的重要性,不应该被误解成绝对权威。

  • Policy 不是事实;

  • Policy 不是现实;

  • Policy 不是执行;

  • Policy 更不是安全本身。

它只是某个来源,在某个时间点,根据某组规则与某些可见信息,做出的一次判断。它可以成为安全体系的一部分。但只有当它被明确限制、被其他边界约束、被真实执行重新验证时,它才不会演变成一个危险的单点权力。

真正成熟的系统,不会因为 Policy 返回 Allow 就停止怀疑。它会继续追问:

  • 这个判断基于什么?

  • 当前状态是否仍然成立?

  • 最终执行对象是否一致?

  • 是否有其他边界给出不同结论?

  • 一旦判断错误,系统最多会失控到什么程度?

因为真正的安全,从来不来自一句「策略已通过」。它来自一个更困难、也更可靠的结构:

即使 Policy 判断错了,系统仍然不能轻易把错误变成现实。

Policy 不是神谕。它可以提出判断,但不能独自决定世界接下来发生什么。


下一篇预告:《Policy 不是神谕(二)》将进入更底层的追问——为什么所有策略判断都天然带有「状态依赖」和「信息边界」,以及这两个属性如何决定了 Policy 永远只能是「当前怎么看」,而不是「最终能不能发生」。

本文是「Policy 不是神谕」系列第一篇。该系列与前序的「执行缝隙」「执行控制」两个系列共同构成一条完整链路:执行缝隙解释判断与真实执行为什么不是一回事,Policy 不是神谕解释为什么策略判断不能直接跨越这道缝隙,执行控制回答谁应该在真实执行前拥有最终拒绝权。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐