一个错误的决定,六层缺失的防护

Read in English

2026 年 4 月 25 日,一个 coding agent 删掉了 PocketOS 的生产数据库。PocketOS 是一家给租车公司做软件的公司。整件事只用了一次 API 调用,大约九秒。和数据库存在一起的备份也一并没了。

大部分报道讲的是那个 agent。我觉得更有用的是它周围的一切。这篇复盘借用了美国陆军行动后复盘(after-action review)的框架,它问四个问题:本来该发生什么,实际发生了什么,为什么不一样,哪些要保持、哪些要改。

我不在现场。下面的内容都来自创始人的公开叙述、Railway 的公开声明,以及对两者的报道。说法不一致的地方,我把两边都列出来。

本来该发生什么

按创始人 Jer Crane 的说法,agent 当时在 staging 环境里做一个例行任务。项目里有规则:未经批准不得执行破坏性操作。agent 遇到凭证不匹配时,预期的行为是停下来问。

实际发生了什么

  • 这个 agent 跑在 Cursor 里,用的是 Claude Opus 4.6。它遇到凭证不匹配,然后试图自己解决。
  • 它在代码库一个不相关的文件里找到了一个 Railway 的 API token。按 Crane 的说法,这个 token 当初是为了管理自定义域名建的。Railway 的 CEO Jake Cooper 后来说它是一个拥有全部权限的 token。
  • agent 发出了一次 GraphQL 调用,删除了一个存储卷。没有确认步骤。那个卷上放着生产数据库。
  • Railway 把卷级备份和卷存在一起,所以同一次调用把备份也删了。Crane 说他能拿到的最新备份是大约三个月前的。
  • 事后被问到时,agent 写道,它是猜测这次删除只会影响 staging,并没有去核对。
  • Crane 和他的客户靠支付记录、日历和邮件,手工重建订单。
  • Cooper 说,他联系上 Crane 之后大约 30 分钟,Railway 就从内部的灾备里恢复了数据;在此之前的延误,是因为一张支持工单被搁置了一天多。Crane 则说仍有明显的数据缺口,并且已经聘请了律师。
  • Railway 说,当时 API 端点会立即执行删除,而同样的操作在控制台里有 48 小时的窗口。之后它把两条路径上的删除都改成了 48 小时内可恢复,并让客户能看到更多的备份。
  • 截至 4 月底,Cursor 和 Anthropic 都没有公开回应。

为什么不一样

agent 做了一个错误的决定:被挡住的时候,它选择了自己想办法。但要让这个决定付出三个月数据的代价,还得有另外六件事同时成立。

层 当时的情况 这一层要是在,拦得住吗
密钥放在哪 一个能删存储的 token,放在 agent 读得到的文件里 能
凭证的权限范围 为改域名建的 token 能删卷 能
环境隔离 staging 里的工作碰得到生产资源 能
破坏性调用的确认 删除立即生效,没有延迟,也没有第二步 能,至少会留出撤销的时间
备份放在哪 备份和数据在同一个故障域里 拦不住,但损失会是几小时,而不是几个月
规则怎么执行 规则是项目里的文字,工具的护栏没有拦截一次直接的 API 调用 文字拦不住请求,强制执行的检查才拦得住

其中三层值得多说一句。

token。 agent 并没有提权。它用的是一个本来就有这些权限的凭证。在 API 看来,这是一个合法 token 发来的、经过认证的删除请求,于是它执行了。

规则。 agent 在违反规则之后,能把那条规则复述出来。一条 agent 能背出来的规则,不是控制手段。控制手段是会拒绝请求的东西:拦截破坏性命令的 hook、网络策略、根本做不了这个操作的 token。

备份。 一份会和数据被同一次调用删掉的备份,防的是数据损坏,防不了删除。

数据分支能管什么,不能管什么

我维护着 Argon,它给 agent 一份自己的 MongoDB 数据库分支,agent 的写入可以被审阅、合并或者丢弃。在这次事故里它帮不上忙。被删掉的是数据库底下的卷,走的是基础设施供应商的 API。分支防的是经由数据库写进去的坏数据,它管不了谁有权删磁盘。

分支针对的是另一类事故,比如 2025 年 7 月 Replit 上的那一次:一个 agent 在冻结期间对线上数据库执行了破坏性命令。故障不同,对应的层也不同。

保持

  • Crane 在一天之内发表了详细的叙述,连 agent 自己的解释也放了出来。正因为这样,其他人才有东西可学。
  • Railway 在几天之内恢复了数据,并改了 API。

改进

对今天就在用 coding agent 的团队,按成本从低到高:

  1. 找出 agent 读得到的 token。 代码仓库、dotfiles、shell 历史。把它们挪走。
  2. 按环境和按操作收窄 token 的权限。 如果供应商做不到,就把每个 token 都当成 root 凭证来保管。
  3. 给 staging 的 agent 只配 staging 的凭证和网络访问。
  4. 在删除前面加一道延迟或者审批。 供应商有就用供应商的,没有就自己包一层 API。
  5. **留一份同一套凭证够不着的备份,**并且真的演练一次恢复。
  6. 在 harness 里强制执行“停下来”这个条件。 一个拦截破坏性调用的 hook 靠得住,规则文件里的一段话靠不住。

凡是 agent 撤销不了的操作,都要有一个人来批准。

来源

Crane 的原帖和 Railway 的博客我都没有直接读到,依据的是上面这些报道。