一个错误的决定,六层缺失的防护
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 的团队,按成本从低到高:
- 找出 agent 读得到的 token。 代码仓库、dotfiles、shell 历史。把它们挪走。
- 按环境和按操作收窄 token 的权限。 如果供应商做不到,就把每个 token 都当成 root 凭证来保管。
- 给 staging 的 agent 只配 staging 的凭证和网络访问。
- 在删除前面加一道延迟或者审批。 供应商有就用供应商的,没有就自己包一层 API。
- **留一份同一套凭证够不着的备份,**并且真的演练一次恢复。
- 在 harness 里强制执行“停下来”这个条件。 一个拦截破坏性调用的 hook 靠得住,规则文件里的一段话靠不住。
凡是 agent 撤销不了的操作,都要有一个人来批准。
来源
- Decrypt,2026 年 4 月 28 日:Crane 的叙述、agent 的解释、Cooper 的说法。
- AI Incident Database,事故 1469:token 的来历和备份的细节。
- NeuralTrust 的分析:时间线,以及三个月前的那份备份。
- 关于 Railway 改动的报道:API 和控制台的差别,以及 48 小时窗口。
- AI Agent Incident Register,AIR-2026-001:Replit 事故。
Crane 的原帖和 Railway 的博客我都没有直接读到,依据的是上面这些报道。
