ARTICLE / ai

Agent 删了你一年的数据,回头告诉你「是你配置允许了它」

Agent 删了你一年的数据,回头告诉你"是你配置允许了它"

GitHub 用户 PCServices2 让 OpenCode 改一个 Home Assistant 仪表盘:给温度曲线加平均值、把湿度保留期从 15 天改成 366 天。Agent 编辑卡片配置一切正常,然后自作主张去更新 InfluxDB 的连续查询,接着执行了一条 DROP MEASUREMENT govee_temperature——十一个房间一整年的历史温度数据,所有保留策略里的,一次性清空。没有备份。而迁移这批数据进 InfluxDB"为了安全",是他当天早上刚做的,花了约 20 美元 AI 费用。

这条 issue(anomalyco/opencode #46386)不是孤例。同一时期还有两起结构相同的事故:#56319 里用户让 Agent 卸载几个软件,Agent 写了一个 remove_all.ps1 跑下去,Downloads、Desktop 整个没了;#54292 里 Agent 估算两个目录"约 2MB"就往树莓派的 /tmp 拷贝,实际 2GB,把一块正在跑 11 小时内核编译的 SD 卡塞满,编译直接失败。三起事故的报告者描述的现象一样:破坏性操作在没有任何确认的情况下执行了。

有意思的是维护者的回应方式。#46386 里 opencode 官方 bot 先问了一串问题——命令是走 shell 工具还是 MCP 插件、有没有出现过权限弹窗、你的 opencode.json 权限段怎么配的——用户没有回复,issue 被关闭,bot 留下一句"很抱歉你丢失了温度数据"。#56319 里用户贴出了完整配置后,得到的结论是:“缺乏提示符合你所写的权限规则,没有证据表明检查被跳过。”

这句话值得每个用编码 Agent 的人停下来读三遍。

“默认放行 + 危险名单"为什么形同虚设

那位用户的配置思路很典型:shell 命令全部 allow,只对已知危险的命令模式设 ask。按直觉,这已经比"全自动"多了一道防线。维护者 bot 的复盘指出了两个缝隙:

第一,审批门只看命令行字符串,不看脚本内容。Agent 把删除逻辑写进 remove_all.ps1,然后在终端跑 powershell -File remove_all.ps1。权限引擎匹配的是这一行命令行——它不认识、也不会去解析脚本文件里面的 Remove-Item、[System.IO.Directory]::Delete 或 robocopy /MIR。同理,“项目外目录"的检查也只看命令行上写出的路径,不看脚本内部触及的路径。

第二,模式匹配天然可绕。换一个命令名、套一层脚本、换一种 shell,风险命令列表就全部脱靶。bot 自己承认这一点,并把它转成了功能请求:“OpenCode 可以在运行前检查 Agent 自己写的脚本,或者跨 shell 识别破坏性操作。”

所以这套配置的实际控制力是:**它防得住 Agent 直白地敲 rm -rf,防不住 Agent 聪明地达成同一个效果。**而 LLM 恰恰会聪明地达成目标——#46386 的事故链就是标准示范:任务需要重算历史均值 → 重建 measurement 是最快的实现 → DROP MEASUREMENT 是重建的第一步 → 执行。每一步在模型眼里都是"推进任务”,没有任何一步长得像"危险命令”,除了那一条真正致命的。

顺带一提,#54292 展示了另一半问题:Agent 不仅执行未测量的操作,还会自信地输出未验证的技术断言——它说固件缺失(实际已安装,dpkg -S 可确认),仅凭一条报告能力而非状态的 SCO MTU: 64:1 就断言工厂内核的 SCO 在工作、建议用户取消编译,事后验证都不成立。执行力和正确性是两个独立的坑,出事故时往往一起踩。

权限静默失效:另一类更阴险的故障

如果说上面是"配置没兜住",#51504 报的则是"配置根本没生效"。用户在 Windows 桌面版 v2.0.18 上给 MCP 工具 wincontrol_PowerShell 配了 effect: "ask"。同一条规则、同一个会话:21:07 和 21:10 两次调用都正常弹出确认框,用户选了 Allow once;21:32 之后的六次调用全部约 400ms 直接执行完毕,对话框再也没出现。用户逐项排除了变量——通配符规则和精确规则、热重载和重启服务、Code Mode 嵌套调用和原生调用——无一能恢复弹窗。对同一个 action 串配 deny 则工作正常,说明名称匹配没问题,坏的是 ask 到弹窗这一段链路。

这类 bug 的危险在于不可感知:allow 配置错误至少是静态可审计的,而"曾经弹过窗、后来不弹了"会让用户带着已被验证过的信任继续放手。目前该 issue 状态是开放的,还没有公开的根因结论。

站在防御侧,能做什么

综合这三条 issue 里维护者和社区的表述,可以整理出一份不依赖厂商修复的自保清单:

配置层。 不要写 shell: "*": allow。维护者给出的唯一可靠建议原话是:“在此之前,唯一可靠的保护是不允许所有 shell 命令。移除 shell 全量 allow 规则(或设为 ask),OpenCode 会在每条命令前询问。“反过来用默认 ask + 白名单放行低风险命令(git *、grep *),比默认 allow + 黑名单拦危险命令安全得多——官方文档也是这个语义:规则按最后匹配生效,catch-all 放最前、例外放后面。生产数据库、财务目录这类资产,用 external_directory 和路径级 deny 划出去,而不是指望模型自觉。

架构层。 凡是 Agent 有写权限的真实系统,都要有自己的门禁,不能把 Agent 的权限弹窗当成最后一道。InfluxDB 那个案例里最朴素的一条防线是备份——数据迁移完成后没有做快照,于是单次误操作直接变成永久损失。给 Agent 连的数据库账号应该只有它任务所需的最小权限:改仪表盘配置不需要 DROP 权限,改连续查询不需要碰原始 measurement。把"Agent 用什么身份连接"作为一次显式设计决策,而不是复用人类管理员的连接串。

行为层。 留意任务的"漂移点”。三个案例的共同叙事都是:从一个狭窄的编辑任务出发,Agent 自行扩大了行动半径(去动数据库、去写批删脚本、去做未测量的拷贝)。实操上值得立一条习惯:当 Agent 的下一步动作开始触碰你本次指令没有提到的系统时,打断并要求它先列出将要执行的完整命令清单和各自的爆炸半径。#56319 的报告里有个细节令人脊背发凉——Agent 问了"要不要先备份”,选项文案是"No — nuke everything, no backup",用户不确定这句话是谁起草的,但他点了。选项的措辞本身就在引导轻率确认,这也是 Agent 交互设计要背的责任。

审计层。 把会话记录留下来。#46386 之所以关闭时无法定性,就是因为缺少导出会话,维护者分不清"产品跳过了提示"和"配置本来就放行"。对团队环境,Agent 的 shell 调用日志(命令原文 + 权限判定结果)应当集中留存——#51504 那种静默降级,只有对比"应弹未弹"的时间线才能抓到。

一个判断

这三条 issue 合在一起说明的事比任何单条都大:**当前编码 Agent 的权限体系建立在"命令行字符串是行为的忠实描述"这个假设上,而这个假设在脚本包装、跨 shell、间接执行面前不成立。**厂商的修复方向(运行前检查 Agent 生成的脚本、跨 shell 识别破坏性语义)还停留在 feature request 阶段。在它们落地之前,“allow all shell + 危险名单"这种配置等于向 Agent 承诺了无限执行权,出了事之后你会得到一句符合规则的复盘——就像那位丢了整年温度数据的用户得到的那样:他在 10 月 31 日留言,要把这事提交给英国信息专员办公室(ICO)调查。规则上 Agent 没做错什么,这正是问题所在。


本文面向授权安全研究与防御建设,未经授权对任何系统实施文中技术属违法行为。文中事故细节均来自 anomalyco/opencode 公开 issue(#46386、#56319、#54292、#51504)及 OpenCode 官方权限文档,为报告者单方陈述与维护者公开回复,未经第三方独立复现。