:为什么“我同意“不等于“它应该发生“)
人按下确认不代表人真的理解了将要发生什么。先讲一件真事。2024 年 5 月 3 日一位加密巨鲸要转出 1,155 枚 WBTC约合6800 万美元。他像平时一样从钱包的转账历史里复制了收款地址核对了开头和结尾几位确认无误签名发送。钱没了。他并没有被盗私钥也没有点进什么钓鱼网站。他中的是一种叫地址投毒Address Poisoning*的攻击攻击者用靓号生成器造出一个*开头六位、结尾六位都和他真实收款地址一模一样的假地址然后提前给他的钱包转了一笔几乎为零的灰尘交易把这个假地址塞进了他的转账历史。等他下次转账时从历史记录里顺手复制的就是那个假地址。他真心想转账他真心核对过他真心按下了确认。每一步都是他本人、本意、本愿。但最终执行的对象不是他以为的那一个。这个案子后来罕见地有了转折——攻击者在舆论压力下于一周后把钱退了回来但绝大多数受害者没有这种运气。这件事把本篇的主题摆得明明白白我同意只能证明一个人完成了确认动作它不能证明这个人理解了将要发生什么更不能证明最终发生的就是他原本想要的那件事。在很多数字系统里人的一次点击被赋予了近乎决定性的意义。页面弹出确认框用户点同意审批请求送达管理员点通过硬件设备显示提示持有人按下确认键。只要动作出自合法用户系统就倾向于把它解释成一份完整、清晰、真实的授权——用户知道自己在做什么并愿意承担结果。这个假设正在变得越来越不可靠。一、确认按钮记录了动作却没有证明理解我们习惯把确认按钮理解成一种数字签字点同意意味着已阅读并接受点批准意味着已理解风险点签名意味着认可这笔交易。但按钮真正能证明的东西其实很有限。它最多能证明在某个时间一个拥有相应身份或设备的人完成了一个确认动作。至于他有没有看完全部信息、有没有理解每个参数、有没有注意到关键变化、有没有被界面误导、是不是在错误的上下文里做的决定——仅凭点击本身无法证明。这不是用户不负责任而是人面对复杂系统时本来就不会去读底层执行内容。他依赖的是界面是系统替他组织、筛选、解释之后的信息。页面说这是向供应商付款他不会逐字检查底层支付指令系统说这是常规权限变更他不会逐项分析访问范围AI 说这是低风险操作他很可能不会重新核对模型没展示的字段。所以用户同意的往往不是完整动作而是系统对这个动作的表达。一旦表达和执行之间出现差异我同意就不再等于它应该发生。二、人确认的是界面系统执行的是参数数字系统里其实并存着两套语言。一套是给人看的语言简洁、概括、好理解——向合作方支付合同尾款导出本季度客户数据为新成员开通项目权限修复异常服务并重启。一套是给机器执行的语言必须极其具体——精确的账户、金额、币种、目标地址、数据字段、时间范围、权限集合、服务器节点、命令参数、执行顺序。这两套语言之间必然有一次转换翻译。用户没法每次都去读原始请求于是界面把复杂执行内容压缩成一个摘要。但任何压缩都会丢信息而被省略的部分恰恰可能决定这个动作安不安全支付合同尾款——没说最终收款账户是不是还属于原供应商地址投毒就发生在这里导出客户数据——没说导出字段里有没有身份证号、手机号这类敏感项开通项目权限——没说这个成员是不是同时拿到了生产环境访问权重启异常服务——没说操作的是测试环境还是生产集群。标题可能没错描述可能基本合理但风险常常藏在没被展示出来的参数里。所以执行安全不能只问用户是否同意还必须问用户同意的内容和系统即将执行的参数是不是仍然是同一件事三、UI 不只是展示工具它是执行链条的一部分很多人把界面看成系统外面的一层包装底层逻辑负责真正的业务界面只是把结果显示得好看点。但在高风险执行里界面绝不是可有可无的装饰——它承担着把机器动作翻译成人类意图的关键职责。如果翻译错了哪怕底层程序严格按参数执行最终结果也会违背人的真实意愿。界面可能因为设计不佳而无意中隐藏风险把危险字段折叠到用户几乎不会展开的位置、用熟悉的名称掩盖已经变化的目标也可能因为系统被攻击而主动制造误导。最严重的情况是——用户看到的内容和系统真正提交的内容根本不一致页面显示地址 A提交时用的是地址 B确认框显示金额 X底层请求里是金额 Y审批页显示低风险操作执行阶段却被追加了额外参数。这种时候用户既没拒绝也不是粗心他只是确认了系统让他看到的东西而真正执行的内容在他视野之外被换掉了。安全领域对此有一条根本性的设计原则叫WYSIWYSWhat You See Is What You Sign所见即所签一个安全的确认必须保证用户签下的就是用户看到的那一个。上一篇提到的 Bybit签名人看到普通转账、实际签下delegatecall违背的正是这条原则。所以UI 欺骗不是产品体验问题而是执行安全问题。一个系统不能仅凭用户点过确认就认定动作有效它还必须能证明用户确认时所看到的内容与最后进入执行边界的内容是一致的。四、信息完整不代表信息真正可理解就算系统没刻意隐藏任何东西还会出现另一种问题信息在形式上完整但对人并不真正可理解。有些签名设备会展示一长串地址或原始哈希审批系统可能列出几十个参数合约交互可能展示完整的 calldata运维平台可能显示一整段执行脚本。从技术上看系统似乎尽到了展示义务——数据都在那儿你可以自己查。但数据都在和人能看懂是两回事。地址投毒的可怕就在这里那串地址完整地、原封不动地显示在了屏幕上首尾六位还都对得上可人类的眼睛在有限时间里就是分辨不出中间被换掉的字符。让审批人读一段 calldata不能保证他理解其业务含义让管理员在几百行脚本里确认没有危险操作更是把机器都难做的判断硬压回给了人。表面上的完整展示有时只是把机器无法完成的判断重新甩给人类却没有真正帮人理解。有效的确认不是把信息全倒在屏幕上而是要让关键风险能被看见、能被比较、能和最初意图对上号。人真正需要知道的不只是系统准备执行什么而是这个动作和最初申请相比变了哪些哪个参数决定主要风险目标对象和历史记录一致吗金额、范围、权限有没有异常放大当前执行环境和审批时是同一个吗如果系统只是把一堆原始数据摊在屏幕上然后要求用户点我已阅读那更像是责任转移而不是执行控制。五、AI 摘要让信息更好读也可能让风险更难被看见AI 正在快速进入审批和确认界面。面对复杂的合同、工单、交易、权限变更它可以自动生成摘要、提取关键字段、解释风险还给出建议通过/拒绝。这解决了一个真实痛点人没时间读全部原文。但它也引入了一道新的缝隙。过去人是在原始材料和真实动作之间做判断现在中间多了一个负责解释的模型。审批人看到的可能不再是请求本身而是AI 对请求的理解。AI 漏掉一个关键字段摘要依然可以写得流畅自然模型把异常账户误认成常用对象依然可能给出低风险如果恶意内容污染了 AI 对任务的理解回想上一篇的 EchoLeak它生成的解释甚至可能主动说服人继续批准。AI 最危险的地方不是输出明显荒谬的答案——那种错误容易被发现。真正值得警惕的是它能把不完整、甚至错误的判断包装成一段专业、简洁、自信的说明。举个特别典型的例子审批人看到一句系统未发现明显异常很容易理解成已经做过全面检查了。但这句话真正的含义可能只是——模型根据它能看到的信息没识别出异常。两者差着十万八千里。所以AI 可以帮人理解执行请求却不能成为最终执行事实的唯一解释者。系统不能因为 AI 摘要写得够清楚就假设人的确认已经覆盖了所有真实参数。六、同意可能是真的但同意的对象可能已经变了执行风险里最危险的一类不是伪造人的同意而是保留真实的同意却替换掉同意所指向的对象。用户确实想付款管理员确实想改配置审批人确实同意导出数据持有人也确实按下了签名键——这些意愿全是真的。问题在于从意图产生到最终执行之间动作的对象、参数或上下文可能已经变了用户同意向甲公司付款执行时账户被换成了另一个地址地址投毒、剪贴板劫持/clipper 木马都干这个管理员批准的是测试环境变更最终指令却指向了生产环境审批人同意导出一个部门的数据实际范围却扩到了全体客户持有人同意签一笔普通交易底层调用却附带了更宽的授权。这类攻击不需要伪造签名也不一定需要绕过审批。它只需要让你对 A 表达真实同意然后让系统去执行 B。从日志上看整个流程甚至无比完整身份验证通过、用户完成确认、审批状态有效、签名真实、执行成功——但真正缺失的一环是系统无法证明用户确认的那个 A和最终执行的那个 B是同一个动作。所以可靠的执行系统不能只保存用户同意了这个事实还必须把人的确认和具体执行内容牢牢绑在一起。用伪代码表达这个绑定大致是# 把确认和确认的对象锁死而不是只记一个 approvedTrue approval { approved_by: user_id, intent_hash: hash(to, amount, chain, params, env), # 确认那一刻的完整动作指纹 shown_to_user: rendered_summary, # 用户当时看到的界面内容 ts: now(), } # 执行前重新算一遍即将执行的动作指纹 def can_execute(action, approval): if hash(action.to, action.amount, action.chain, action.params, action.env) ! approval[intent_hash]: return DENY # ← 只要关键参数变了原来的同意立即失效 return ALLOW核心思想只有一句任何关键参数发生变化都应该让原有的同意失效而不是让新动作继续沿用旧授权。七、再次确认不一定能解决问题面对确认风险最常见的设计是加更多确认第一次点提交第二次弹风险提示第三次输密码第四次再确认一遍。这在某些场景有效但确认次数增加不等于理解增加。当人长期面对重复弹窗会形成固定反应看到按钮就点看到提示就关输密码只是为了让流程往下走。安全行业把这叫警告疲劳warning fatigue/ 习惯化habituation——提示越多、越频繁、越相似人越倾向于忽略。这是有大量人因研究支撑的真实现象不是态度问题是认知机制。AI Agent 时代这会更严重自动化能生成海量待审批请求企业为控风险又不断加确认节点最后审批人面对的是一长串长得都差不多的任务工作退化成把队列里的请求逐个点成通过。这时候人并没有真正构成一道门闩只是给自动化流程提供了一个形式上的许可来源——上一篇说的橡皮图章化。真正有效的确认不该只是增加点击次数而应该在关键时刻把差异和后果清晰地摆出来。不是干巴巴地问是否继续而是明确指出收款账户与历史记录不同本次金额是过去平均值的十倍执行目标从测试环境变成了生产环境导出范围比审批申请多了三个部门最终参数与最初审批内容不一致。这种确认才有意义——它帮人理解为什么这一次值得停下来而不是机械地要求人再表个态。八、人的确认应该是治理输入而不是最终安全证明这不是说人的同意没价值。恰恰相反人的判断依然是组织治理里极重要的一环需要有人表达业务意愿、承担决策责任、判断例外、在关键动作前给出明确授权。问题只在于——不能把人的一次点击当作全部安全条件已满足的证明。更准确的定位是人的确认是治理输入之一governance input不是最终执行许可execution authorization。它说明某个参与者在特定信息条件下表达了同意但系统仍需独立验证一串条件确认人身份是否有效、他看到的内容是否完整、确认内容是否与真实执行参数一致、确认后关键字段有没有变、动作是否符合本地约束、当前环境是否仍满足执行条件、是否还有其他必须共同满足的治理要求。只有这些条件同时成立人的同意才有机会转化为有效的执行许可。换句话说人的确认不应直接等于执行命令它应该成为执行判断的一部分而不是替代执行判断。九、执行前必须重新绑定人看到的和机器要做的要弥合我同意和它应该发生之间的裂缝关键不是取消确认而是在执行前重新绑定三件事人的真实意图人实际看到并确认的内容机器最后准备执行的参数。这三者必须一致。用户确认后金额变了确认就该失效审批完成后地址被替换动作就该重新进治理流程AI 摘要和底层参数不一致系统不该只信摘要执行上下文发生重要变化时间窗口、目标环境、风险等级旧许可也不该继续沿用。这种绑定不能只活在界面的文字里还必须进入可验证的执行结构——系统要能证明最终执行的正是当初被提出、被展示、被治理、被确认的那一个动作。这正是 Havenlon 强调从 Intent 到最终执行的连续约束的原因。这里的 Intent 不是一句模糊的用户同意了而是对执行对象、金额、参数、上下文和治理条件的具体绑定可以理解为第六节那个intent_hash的完整版。它让任何关键变化都变得可见并阻止一个已经变化的动作继续借用原来的授权。十、我同意不等于它应该发生在传统软件里确认按钮常被当成责任链的终点用户点了、审批人通过了、设备签名了于是系统认为自己已经拿到了充分授权。但 AI Agent、自动化执行和复杂业务系统正在把这个逻辑的缺口暴露得越来越清楚人可能没看到完整内容界面可能展示了错误信息AI 摘要可能漏掉关键风险确认之后参数可能被替换审批时的上下文也可能已经改变。在这些情况下人的同意可以完全真实最终动作却仍然不应该发生。所以成熟的执行安全不能只问有没有人点击同意它必须继续追问这个人同意的究竟是什么他看到的内容足以支持这个决定吗最终执行的动作和确认内容完全一致吗在执行发生之前还有没有一道独立边界能发现两者已经偏离我同意是一种意愿表达不是现实动作天然正确的证明。真正可靠的系统不会用人的点击为所有后续执行背书而是确保人的意图、界面展示、机器动作始终指向同一件事。因为最危险的情况未必是系统未经同意就执行。更可能是人确实同意了但最终发生的并不是他以为自己同意的那件事。那位差点损失 6800 万美元的巨鲸同意了。他只是不知道自己同意的那个地址早已不是原来那一个。这是《Havenlon安全讲人话》系列的第六篇。下一篇我们换一个更轻松的视角讲讲海狸筑坝的智慧为什么好的安全系统不追每一滴水而是只守住关键路径上的那一处——聊聊 Havenlon 为什么不试图控制所有风险。参考来源本文引用的真实事件与技术概念Exploiter Steals $68M Worth of Crypto Through Address Poisoning — CoinDeskTrader loses $68M in address poisoning scam — CointelegraphAnatomy of an Address Poisoning Scam — ChainalysisBlockchain Address Poisoning (technical study) — arXiv 2501.16681