一位开发者给 Codex 开启完全放行权限后无人值守运行,回来发现 C 盘和 D 盘被清空。事故由四个环节叠加而成:Windows 跨层传参丢失引号、WSL 自动挂载 Windows 盘符、find -delete 无刹车、full access 无人拦截。文章拆解每一环的成因,给出隔离、权限收紧、删除刹车、备份四层可落地的防护方案与检查清单。
事故经过:一条清理临时目录的命令
一位开发者为了在额度重置前把用量跑满,出门前给编程 Agent 下了一条指令:自己去验证整个项目的脚本和文档是否正确,并开启了完全放行模式。几小时后回来,C 盘和 D 盘已被清空,涉及两三百 GB、上百万个文件,只有少数被进程占用的文件留了下来,最后靠云备份恢复。
事后翻执行记录,Agent 本来只是想下载一个 sshpass 二进制到临时目录,用完再清理:
wsl.exe bash -lc 'set -e; d=$(mktemp -d); cd "$d";
apt-get download sshpass >/dev/null;
dpkg-deb -x sshpass_*.deb extracted;
cp extracted/usr/bin/sshpass /tmp/xxx-semantic-bin/sshpass;
chmod 700 /tmp/xxx-semantic-bin/sshpass;
cd /; find "$d" -depth -delete;
/tmp/xxx-semantic-bin/sshpass -V'
出事的是 cd /; find "$d" -depth -delete。$d 本该展开成 /tmp/tmp.XXXXXX,但在操作系统层面真实拿到的命令行里,$d 两侧的双引号已经不见了。引号一丢,搜索起点不再是临时目录而是根目录;而 WSL 默认把 Windows 固定盘挂在 /mnt/c、/mnt/d,find 又不带 -xdev,遍历就跨进了宿主机的两块盘。
四层防护为什么同时失效
1. 跨层传参把引号吃掉了
Windows 的命令行参数在系统层面只是一个字符串,不是字符串数组。一条命令从 PowerShell 传到原生可执行文件,再传给 wsl.exe,最后交给 bash,每一层都要重新解析引号和转义。层数越多,语义出现偏差的机会越大——"在 PowerShell 里写着没问题"的命令,执行时可能完全是另一回事。记录分析指向 PowerShell 调用原生 exe 时没有转义内层引号,但换一种组合,丢失点可能出现在别处,具体在哪儿丢的,得按自己的环境复现确认。
2. WSL 的自动挂载扩大了打击面
一旦工作目录到了 /,且 find 不加 -xdev(或 -mount),遍历就会从 Linux 根文件系统跨到 NTFS 挂载点上。原本"只删临时目录"的操作,作用范围变成了整机。
3. find -delete 没有任何刹车
-delete 是立即生效的删除:不进回收站、不产出中间清单、不打印将删内容。这类命令本该先跑一遍 -print 确认清单,或加 -maxdepth 限制范围。Agent 没做,规则文件里也没禁止。
4. full access 拆掉了最后一道人工闸门
无人值守 + 完全放行 + 高危命令,三件事凑齐后没有任何环节能拦住它。这一条最容易改,也最应该改。
可以落地的四层防护
第一层:把 Agent 关进隔离环境
| 方案 | 隔离强度 | 适用场景 | 代价 |
|---|---|---|---|
| 本地虚拟机 + 快照 | 高 | 长时间无人值守、需要完整桌面环境 | 占资源,大项目编译慢 |
| 容器 / microVM 沙箱 | 中高 | 单一语言项目、依赖可控 | 需要学习配置,图形化工具有限 |
| 独立机器(云主机等) | 高 | 需要长期在线、本机不能停 | 有额外成本,要同步代码 |
| 仅靠审批模式 | 低 | 有人盯着、任务较短 | 长任务频繁被打断 |
核心原则是只让项目目录进入 Agent 的视野,其他磁盘不挂载或只读挂载。如果任务本身就要长时间在线跑、又不愿让本机磁盘承担风险,可以把它放到一台与本地数据物理隔离的远程机器上,例如整机放在机房的独享 Mac,按月租用、远程连接,代码同步过去,本地磁盘根本不在它的可见范围内。是否走这条路,取决于任务时长和对本机环境的依赖程度。
第二层:权限默认收紧
- 日常用"替我审批"一类的中间权限,不要默认完全放行。
- 只有"无人值守 + 已隔离"同时成立,才考虑完全放行。
- 在项目根目录放一份规则文件,把边界写死,例如:
## 执行边界
- 所有读写必须落在项目根目录内;执行前先打印解析后的绝对路径。
- 禁止 find -delete、rm -rf、xargs rm、git clean -fdx 等批量删除。
- 禁止用 wsl.exe / bash -lc 包裹命令,统一走 PowerShell 7 或 Git Bash。
- 需要清理时,把待删清单写入文件,交人工确认后再执行。
- 删除一律走回收站或移动到 .trash/。
规则文件不是硬约束,Agent 有可能绕开,但它能明显降低"它自己决定顺手删点东西"的概率。
第三层:给删除动作加物理刹车
- 用
trash一类的命令替换rm。注意这层包装通常写在.zshrc/.bashrc,而 Agent 调起的是非交互 shell,默认不加载这些文件,需要改到~/.profile或系统级配置里,或者直接替换二进制。这一点必须实测,别以为加了别名就安全。 - 部分 CLI 支持在命令执行前挂 hook,可拦下高危命令并要求确认,具体配置方式以官方文档为准。
- 执行
find时强制带-xdev,并把起点限定在项目目录:
find "$PROJECT_DIR" -xdev -depth -name '*.tmp' -print # 先看清单
find "$PROJECT_DIR" -xdev -depth -name '*.tmp' -delete # 再执行删除
- 如果必须用 WSL,可以考虑关掉自动挂载,让它看不到 Windows 盘符:
# /etc/wsl.conf
[automount]
enabled = false
关掉后 Windows 文件就访问不到了,值不值得看日常习惯;也可以保留挂载,改用 -xdev 加规则文件限制遍历范围。WSL 配置项随版本变化,动手前请核对官方文档。
- 在 Windows 上尽量让 Agent 固定用一种终端(PowerShell 7 原生或 Git Bash),不要让它自由地在 PowerShell 和 WSL 之间来回切换。
第四层:备份是唯一的后悔药
- 遵循 3-2-1:三份副本、两种介质、一份异地。
- Windows 开卷影副本或文件历史记录,macOS 用 Time Machine,Linux 用快照加异地同步。
- 定期做一次恢复演练。备份没验证过,等于没有备份。
- 让 Agent 自己"先备份再操作"意义有限——它如果判断得准哪些该备份,多半也不会删错。
几个有分歧的点
是 PowerShell 的锅吗? 一部分人把矛头指向 PowerShell 的引号与转义规则,另一部分人认为换成 bash 一样会出事,真正的问题是 find -delete、WSL 跨挂载点遍历和完全放行三者叠加。更准确的说法是:Windows 的参数拼接方式放大了风险,但它只是链条上的一环,换终端能降低概率,不能消除风险。
换个模型是不是就好了? 有人建议换另一家的 Agent,但多个回复都提到不同工具都出现过删文件的案例。工具差异确实存在,可把安全寄托在"这个模型更聪明"上并不可靠。
人工审批够不够? 有人实测长任务里半小时要确认几十条命令,命令又长又难读;加上引号可能在到达执行层之前就已经变形,人工确认的价值被大幅稀释。审批适合有人盯着、任务较短的场景,长时间无人值守还是要靠隔离。
虚拟机是不是万能? 大型项目的编译、图形化工具、部分驱动在虚拟机里体验不佳,快照也有存储成本。折中做法是快照 + 云备份 + 规则文件三件套,按项目重要性分配投入。
一份可以直接照做的检查清单
- Agent 的默认权限不是完全放行
- 无人值守任务只在虚拟机 / 容器 / 独立机器里跑
- 隔离环境只挂载项目目录,敏感盘不挂载或只读
- 项目根目录有规则文件,明确禁止批量删除和跨层包裹命令
-
rm已替换为回收站方案,并验证过在非交互 shell 下生效 -
find一律带-xdev和明确的起始路径 - 系统级快照 / 卷影副本 / Time Machine 已开启并验证可恢复
- 关键数据有异地或云端副本
- 长任务开始前先小范围试跑,检查执行日志
小结
这次事故的可怕之处不在于 Agent 有多"坏",而在于它真心想做好一件事:建个临时目录,用完清理掉。一个引号在多层传参中丢失,加上 WSL 自动挂载和 find -delete 的即时生效,就足以让"清理临时目录"变成"清空两块盘"。关进隔离环境、把权限收紧到刚好够用、给删除动作加一道物理刹车、再准备一份验证过的备份——这四件事没有一件需要高深技术,但缺任何一件,剩下三件都可能白做。