AI写操作的人类确认:为什么"yes"不能只是布尔值

人类对AI写操作的"同意"不能是布尔值,必须绑定五要素才能成为可审计的凭证。
这篇文章讨论AI系统执行写操作时,人机确认机制的工程设计缺陷。核心论点是:把用户的"同意"简化为布尔值,会导致授权在执行瞬间被系统其余部分悄悄重新解读,而用户对此毫无感知。作者提出应将每次确认存为数据库记录,并绑定五要素:精确参数(逐字符匹配)、目标系统(防止写入目的地被偷换)、可写字段域(按字段声明权限边界)、有效期窗口(发放时写入,不依赖可变配置)、操作者与决定时刻。此外,拒绝操作应与批准共用同一绑定路径,避免两条路径随时间漂移产生不对称漏洞。文章还给出了一个自检方法:找出所有在发放时和执行时各算一次的字段,任何这样的字段都是潜在的漂移点。
当一个AI系统想要执行写操作时,流程看起来很简单:它发起请求,用户看到确认框,点击"同意",操作就执行了。但这条链条中最容易被做得过薄的一环,恰恰是系统在用户点击"yes"之后究竟保留了什么。
这篇文章聚焦的正是这个单点问题:确认不应该是一个布尔值。如果把人类的"同意"简化为 confirmed = true,整个授权链都会在后续环节悄悄为它打补丁。

布尔值的两个问题
把"yes"折叠成一个布尔值,会带来两个层次的问题。
较小的那个是复用风险:同一个"yes"能不能批准第二件事?这不是新问题,把确认标记为一次性(one-shot)就能解决。
较大的那个更隐蔽:布尔值是一句话,不是一张凭证。 "yes"这个词无法回答下面任何一个问题:
- 被批准的是哪个请求?
- 批准写什么?
- 批准写到哪里?
- 批准改动多少?
- 有效期到什么时候?
如果确认本身回答不了这些,那么答案就会在执行的那一刻被重新决定。而执行的时刻与用户点头的时刻之间存在距离,中间发生的任何变化,用户都察觉不到。
作者给出的解法是:把"yes"变成数据库里的一行记录,绑定五样东西。下面逐一说明——以及不绑定它会坏在哪里。
绑定"写什么":精确参数
一次确认绑定的是那一次调用的参数,逐字符精确。不是"参数要落在某个范围内",而是"参数就是这些"。当时是什么就必须执行什么,差一个字符就拒绝。
这比值域约束(value domain)严格得多,代价也要讲清楚:整组参数要么全部接受,要么全部拒绝。
它无法表达"这次批准里 status 可以改,但 owner 不能改",因为参数是作为一个封装好的整体被绑定的。要表达那种粒度,需要另一个机制(见下文的可写字段域)。
绑定"写到哪":目标系统
这是作者坦言由读者指出的一环。最初的版本记录了工具名和参数,看起来已经足够——要写的内容被锁死了,还有什么能动?
答案是:写到哪里。
工具名和参数都是"我们这一侧"的东西。而写操作最终落到哪个外部系统,是另一回事:代理工具的配置可以被重新指向,外部服务可以被重新注册。批准记录静静躺在数据库里,每个参数都原封不动,可它要去的那个地方已经被换成了另一个。记录里没有任何东西能揭穿这一点。
修复办法是把目标也绑定:发放时记录目标,执行时重新解析目标,两者不一致就拒绝,绝不默默跟随。
一句值得单独记住的话:"已批准"和"批准写到哪"是两件不同的事。
这类攻击在安全领域有一个对应的模式:混淆代理人问题(confused deputy problem)。代理人(这里是AI系统)持有合法授权,但被诱导将该授权用于授权颁发时未曾指向的目标。攻击者不需要伪造授权本身,只需要在授权颁发之后、执行之前,悄悄替换目标。由于授权记录里没有锁定目标,代理人无从分辨这一次执行与原始批准是否一致。在更广泛的系统安全语境中,这也与TOCTOU(time-of-check to time-of-use)竞争条件有结构上的相似性:检查发生在一个时刻,使用发生在另一个时刻,两者之间的状态变化不受任何约束。把目标系统在发放时写入记录,并在执行时重新比对,本质上是在把"检查"和"使用"之间的可变量消除掉。
绑定"改多少":可写字段域
既然精确参数已经绑定了,为什么还需要这一条?
因为精确参数只能对整组做接受或拒绝,而确认通常表达的是按字段的意思:这次可以改 status,但不能改 owner。只有把可写字段单独声明出来,才能说清这件事。
它与精确参数并列而非替代:一个回答"这些值对不对",另一个回答"这类字段到底能不能写"。约定与系统其余部分一致——没声明就是不约束;超出声明集合的直接拒绝,而不是截断。截断会被读成"改了一部分",而实际发生的是"一个本该被拒绝的写入被悄悄收窄后放行了"。
绑定"何时":有效期窗口
确认会过期不是新闻。容易被忽略的是:过期信息存在哪里。
作者犯过的错误是:窗口只活在 token 上。token 一旦被消费就没了。于是三个月后再看那一行记录,只能说"它被批准过",却说不出当时的窗口是多少。
而这恰恰是最需要它的时候。有人问为什么某次写入在那个时间被允许,你去查记录,却发现窗口依赖的是发放时的配置,而配置此后已经变了。
修复很直接:发放时就把窗口写进那一行,之后读那一行上的值,不要从当前配置重新计算。
这里触及一个更普遍的审计设计原则:记录应当自描述(self-describing),即一行记录在任何未来时刻都应能独立回答"当时发生了什么",而不依赖外部的、可变的上下文。依赖"发放时的配置"来还原当时的窗口,实际上把审计记录的完整性外包给了配置管理系统——而配置几乎必然会随时间变化。这与数据库中的**事件溯源(event sourcing)**思想一脉相承:事件本身应携带足够的信息让它在任何时刻都可被重播或验证,而不是让验证者去拼凑当时的环境状态。把窗口在发放时写入那一行,正是让确认记录变得自描述的最小步骤。
绑定"谁"以及"哪一次决定"
最后两样:操作者,以及做出决定的那一刻。
还有一条刻意的设计:拒绝走和批准完全相同的绑定路径。 如果"no"是一条带自己开关的独立路径,两者就会逐渐漂移——今天给批准加的绑定,明天在拒绝路径上就被忘了。共享一条记录、一个入口,让这种漂移不可能发生。
一个你可以自己跑的检查
不要上来就问"我的确认够不够严",而要问一个更具体的问题:
你的确认里,哪些字段是在发放时算一次、执行时又算一次的?
任何这样的字段都可能漂移。三个测试:
- 把同一个确认指向另一个目标系统,它会成功吗?
- 改掉一个参数,它会成功吗?
- 三个月后,你能说出当时的窗口或策略版本是什么吗?
任何一个"会"或"说不出",都标记出一处未被绑定的地方。
为什么不干脆为每次批准发一张凭证
作者坦诚地列出了两件没做的事,以及一个主动放弃的方向。
字段域能表达某类字段能不能写,但无法表达跨字段规则,比如"B 只有在 A 改变时才能改"。窗口列是后来加的,比它更早的旧记录不返回这个字段——它们的窗口不可知,作者也不为此编造默认值。
至于"为什么不为每次批准签发一张凭证",作者算过账:确认已经是一次性的,凭证本就是短命且限定单一目标系统的,而目标现在也已经被绑定。这些加在一起已经覆盖了担忧。再加一层只会买来重复的复杂度,而不是新的保证。所以这是一个权衡,不是等待填补的缺口。
这里提到的"凭证"对应安全领域的 capability token 或 macaroon 等机制——将授权的全部约束内嵌到一个可验证、可传递的令牌中,持有者即可在不查询中心状态的情况下完成鉴权。这类方案的优势在于去中心化验证,劣势在于撤销困难:令牌一旦签发,除非引入额外的撤销列表,否则在有效期内无法收回。作者选择继续依赖数据库记录而非令牌,意味着撤销只需将那一行标记为无效,执行侧查库时即可立即感知——代价是每次执行都需要一次数据库读取,但换来了撤销的即时性。这个权衡在AI自主写操作场景下尤为重要:用户发现批准有误后,应当能够在操作实际执行前将其撤回,而不是等令牌自然过期。
回到开头
整条链上的每一个承诺,最终都落在这一环上。审计记录谁批准了什么,撤销要还原被批准的那次写入。如果这一环做得薄,后面每一环都在默默替它遮掩。
要绑定的五样东西值得再列一遍:精确参数、目标系统、可写字段域、有效期窗口、操作者与决定时刻。
这是一套关于 AI 自主写操作如何做人机确认的工程实践,核心洞察朴素却深刻:人类的"同意"必须被绑定成可审计、不可漂移的凭证,而不是一个会在执行瞬间被重新解读的布尔值。
相关推荐

零成本搭建AI超级员工系统:智能体自动工作流全解析
深度拆解B站演示的AI超级员工系统:Agent智能体编排、数字人口播、地图与企业获客、24小时智能客服及法务模块全覆盖。分析零成本开户背后的商业逻辑与合规风险,帮助创业者判断这套AI数字员工系统的真实投产比。

Chatbot与AI Agent的本质区别:从对话到执行的跨越
Chatbot和AI Agent有什么区别?本文基于Learn Tech频道的MCP系列讲解,用订机票、笔记摘要等实例说明:Chatbot只给答案,Agent通过思考-行动-观察循环真正完成任务,并拆解模型、工具、记忆、目标四大部件及MCP的桥梁作用,同时讨论Agent的适用边界与权限风险。

免费GitHub仓库:342小时AI课程,从数学到MCP服务器
一个免费GitHub仓库提供342小时AI课程,包含523节课、20个阶段,覆盖数学基础到Agent构建和MCP服务器。支持目标驱动学习,每节课产出可留存的prompt、skill或agent,已有近10万人开始学习。