Agentpath
闯关30 / 31 节 · 预计 35 分钟

闯关:失控的 agent

半夜的无人值守任务烧掉了大半个月预算,还改了不该改的目录。复盘并加固。

学完这节你能做到

  • 从用量与审计记录里还原失控过程
  • 找出四道本该拦住它的防线各自为什么没生效
  • 产出加固清单并验证

周五晚上,同事挂了一个无人值守任务:「把所有服务的镜像 tag 从 v1 升到 v2, 改完提交 PR」。周一早上:

  • 账单多了 312 美元
  • ~/.ssh/config 被改过
  • 三个仓库里出现了内容诡异的 PR
  • 任务本身没完成

四道防线,一道都没拦住。找出每一道为什么失效。

现场

ops@bastion-1目标 0/6
  1. 1.看清它是怎么被启动的
  2. 2.确认钱花在哪个时段、哪一类调用上
  3. 3.看它这一夜到底在干什么
  4. 4.找出越界的那几条命令
  5. 5.看清权限门为什么没生效
  6. 6.确认轮次上限有没有设
现场材料:账单导出、审计日志、当时的启动命令和权限门代码。目标有五个 —— 先还原过程,再逐道防线复查。输入 goals 看目标。
[ops@bastion-1 ~/postmortem]#
help 查看用法 · goals 看目标 · hint 要提示 · ↑↓ 翻历史

逐道防线复查

防线一:工具集白名单 —— 没有设

启动命令是 pi -p --approve "...",工具集是默认的全套 (read / bash / edit / write)。任务本身只需要改 YAML 和提 PR, bash 的全部能力是白给的。

该怎么做:--tools read,edit,write,bash 已经是最低要求, 更好的是把 git 操作包成受限工具,压根不给通用 bash

防线二:轮次与预算上限 —— 没有设

2094 轮,六个小时,没有任何东西让它停。-p 模式跑到 cron 的下一个周期才结束。

该怎么做:轮次上限、token 上限、墙钟上限、外加无进展检测。 从账单曲线看,21 点之后成本还在涨但产出为零 —— 一个「连续 20 轮没有成功的写操作就停」的检测,能把 312 美元变成 20 美元。

防线三:权限门 —— 逻辑写反了

if (!ctx.hasUI) return            // 无 UI 时直接 return = 放行

这一行是整个事故的核心。作者的本意大概是「没界面就没法问」, 但 return 在这里等于允许执行。正确写法:

if (!ctx.hasUI) {
  return { block: true, reason: `无人值守模式拒绝执行危险命令:${cmd}。请改用安全的替代方案。` }
}

而且匹配规则本身也太窄:--force 拦住了 git push --force, 但 ssh-keygen>> ~/.ssh/configcurl -X POST 这些根本不在规则里

该怎么做:白名单式分级(认不出来的按高危处理),而不是黑名单式匹配。

防线四:隔离 —— 完全没有

pi 直接跑在 bastion 上,宿主的 ~/.ssh~/.secrets、全局 git 配置都在手边。 defaultProjectTrust: "always" 还让它无条件加载任何项目里的本地扩展和提示词 —— 这等于把「三个仓库里的 .pi/ 目录」也变成了可执行输入。

该怎么做:容器里跑,只挂需要的仓库目录,凭据用短期 token, defaultProjectTrust 至少设成 "never"

为什么四道会同时失效

不是巧合。它们有共同的成因:

这个任务是从「我在旁边看着跑一次」直接搬到「无人值守跑一夜」的。

交互模式下这四道防线都不重要 —— 人就是防线。一旦人退场, 每一道都要从「提示」变成「强制」,而没有人做这次转换。

!从交互到无人值守,是一次完整的重新设计

不是加个 --approve 那么简单。要重新回答四个问题: 它需要哪些工具?什么时候必须停?拿不到人的确认时默认怎么办?它能碰到什么?

加固清单

# 1. 工具集收窄
pi -p --tools read,edit,write \
  # 2. 硬上限(由外层脚本或扩展实现)
  #    max_turns=60  max_usd=10  max_wall=30m  无进展 20 轮即停
  # 3. 权限门:无 UI 时默认拒绝 + 白名单分级
  # 4. 容器里跑,只挂 ~/work/repos,短期 token
  "..."
// settings.json
{ "defaultProjectTrust": "never" }

再加一条事后的:审计日志要能按「写操作」筛出来,并且每条都带回滚提示。 这次能还原过程,靠的就是那份 audit.jsonl

检查点单选

这次事故里,哪一处是「单点修复收益最大」的?

这一节的结论

  1. 「问不了就放行」是事故的共同起点 —— 无 UI 时必须默认拒绝。
  2. 黑名单匹配永远漏,用白名单分级 + 未知按高危。
  3. 无人值守必须有四类上限,外加无进展检测。
  4. 交互 → 无人值守是一次重新设计,不是加个参数。
  5. 事后能不能还原,取决于事前有没有审计 —— 写操作要带回滚提示。

延伸资料