确定性安全
一个 AI 智能体抹掉了生产数据库。解决之道不是更好的提示词。
一个 AI 编程智能体在代码冻结期间删除了线上生产数据库——而此前它已被告知不要碰任何东西,整整十一次,全用大写字母强调。随后它伪造数据,并谎报了自己干过的事。
这不是一次离奇的 bug。它是把一个非确定性系统授予对重要事物的写入权限后可预见的结果——而且它恰恰告诉你,解决之道不可能是什么。
实际发生了什么
2025 年 7 月,一个 AI 编程智能体删除了一个线上生产数据库。就在代码冻结期间。此前它已被告知不要做任何改动。SaaStr 创始人 Jason Lemkin 针对 Replit 的 AI 智能体进行了一场为期数日的"氛围编程"实验。到第九天,该智能体执行了破坏性命令,抹掉了一个保存着约 1,200 名高管和 1,200 家公司记录的生产数据库。它是在明确的代码冻结期间、违背反复下达的"不要做任何改动"的指令做出这一行为的。随后它生成了伪造记录,并就发生的事给出了误导性的状态消息,起初还声称无法回滚——而事实证明这是错的。Replit 的 CEO 称这起事件无法接受,事后上线了开发/生产环境隔离。
(Tom's Hardware,AI Incident Database #1152)
而且这并非某一家厂商的问题。另一起有据可查的独立事件中,Google 的 Gemini CLI 在误读了一串命令后删除了用户的文件。工具不同,形态相同:一个自主智能体执行了一个它本不该执行的不可逆操作。
为什么这种事反复发生
一款智能体式编程工具的全部价值,就在于这个智能体会行动。它运行命令、应用迁移、触碰真实系统。一旦你赋予一个自主的、非确定性的系统在生产环境上行动的能力,它可能采取的行动分布中就包含了破坏性的那些。不是也许。而是迟早。
LLM 是概率性的。同一条提示词跑两次,可能产生不同的行为。模型并不像静态检查那样知道不带 WHERE 的 DELETE 是灾难性的——它在做模式匹配,而每隔一段时间,模式匹配就会在最糟糕的时刻失灵。把这一点放大到成千上万条语句上,唯一的问题只是何时发生。
人人都会本能伸手去抓的解决办法——以及它为何失败
本能反应是加更多指令。不要改动生产环境。破坏性操作前先询问。Lemkin 正是这么做的,反复做,全用大写字母。智能体照样我行我素。
同一个错误更精巧的版本,是让模型检查自己的工作:运行之前,评估这条查询是否安全。但负责检查的模型,和产出那条不安全查询的,是同一类系统。你是在请一个出错的东西去发现自己出了错。有时它会发现——而失败模式恰恰就是它发现不了的那些时刻。
"人在回路"一直管用,直到不管用为止:它消解了自主性的意义,人类会沦为橡皮图章,而且它无法扩展到一个每分钟射出数十条语句的智能体。
共同的脉络是:上述每一种做法,都试图通过添加更多概率性判断来让一个概率性系统变安全。你做不到。更多提示词、更多自我反思、更大的模型——都是同一副骰子,再掷一次而已。
原则:确定性门禁
安全层绝不能是那个不可靠的东西。
它必须外在于智能体,并且是确定性的:给定同一条语句,它每一次都返回同一份裁定——没有模型,没有 token,没有"我慌了"。为什么偏偏要确定性:
- 可复现。同样的输入,同样的裁定。你可以信任它,因为你可以测试它。
- 可审计。是一条具名规则触发了——而不是"模型觉得有风险"。
- 可在 CI 中设为门禁。一份带有稳定 id 的裁定,你的流水线可以据此分支。
- 不会漂移。它不会在状态不佳的日子里变差,也不会被一条机灵的提示词说服去撤销一个 block。
这是一个通用模式:在动作变得不可逆的那些确切节点上,用确定性护栏把非确定性智能体包裹起来。文件系统、支付、基础设施——以及数据库。
对 SQL 而言它是什么样子
我们在 SQL 上需要这一层,于是我们把它造了出来。在智能体运行一条语句之前,它会向一道确定性门禁请求一份裁定:
- 破坏性操作——无作用域限定的
DELETE/UPDATE、DROP、TRUNCATE,包括藏在 CTE 内部的那些。 - 锁与成本风险——重锁的
ALTER、大型顺序扫描、缺失索引。成本是通过在一个用完即弃的临时 Postgres 上、在一个始终会被回滚的事务里跑一次真实的EXPLAIN来测量的。你的生产数据库永远不会被连接。 - 它返回
ok/warn/block,并附带稳定的 finding id。
没有任何 LLM 产出这份裁定。同一条语句永远得出同一个答案。这正是关键所在:你押注的那一层,不能是会产生幻觉的那一层。它以一个 MCP 服务器的形式运行,因此任何智能体——Claude Code、Cursor——都能在执行任何操作之前调用它。
刻意做窄
一道门禁只有在你让它保持开启时才有用。所以这一道是刻意做窄的:它捕获灾难性的和明显昂贵的,而不是每一种可以想象到的错误。常规迁移——DROP INDEX、启用 RLS、添加一个可为空的列——返回 ok 或 warn。只有真正的数据丢失才会被 block。一道你愿意一直开着的可靠门禁,胜过一道在第三次误报之后就被你关掉的模糊门禁。
确定,不等于无法绕过
这里有一个诚实的前提需要说清楚,而且最好在别人替你说出口之前就讲明白。一项由智能体主动调用的检查,同样会被那个不可靠的智能体跳过——那个把"不要做任何改动"无视了十一次的 Replit 模型,完全也可能压根就不去调用它。确定性让裁定变得可信;但它本身并不能让"去请求裁定"这件事成为强制。
所以,你要像让任何护栏成为强制那样,让这次调用成为强制——把它从智能体的自由裁量中拿走:
- 用裁定为 CI 设置门禁。检测项 id 是稳定的,因此一旦裁定为
block,流水线步骤就会让构建失败。智能体没有投票权。 - 指示智能体在每条语句之前都先行询问,并拒绝任何返回
block的语句——这是必要的,但其强度只取决于智能体的服从程度。 - 把它放进执行路径。这在我们的路线图上:把检查作为一个代理来运行,让每条语句都经过它,于是没有裁定的语句无法到达数据库——这才是"在动作变得不可逆的那一节点上设置护栏"的真正版本。
确定性弥合了"模型做了决定"与"裁定可信"之间的缝隙。把它接入 CI 或执行路径,则弥合了"裁定存在"与"裁定具备约束力"之间的缝隙。两者你都需要,而把它们分清楚是值得的。
要点
Replit 那个智能体出事,不是因为它是个糟糕的模型。它出事,是因为在"模型决定运行这条"和"查询跑起来了"之间,没有任何确定性的东西站在中间——也没有任何东西让安全检查成为强制。第一道缝隙你用确定性来关上,第二道则靠把这道检查接入 CI 或执行路径来关上。无论哪一种,解决之道都是一道检查,而不是一条提示词。