别把危险命令当成 Agent 的一次普通失误:用 agent-guard 在执行前拦下 hard reset、删库与毁灭性操作
AI 编程 Agent 最让人紧张的时刻,往往不是它写错了一个函数,而是它在修复错误的循环里继续执行了不可逆命令:git reset --hard 丢掉还没提交的改动,rm -rf 清空构建目录,或者把迁移调试命令带进生产数据库。模型未必“恶意”;更常见的是上下文漂移后,它仍自信地沿着错误路线操作。
这里有一个必须先厘清的边界:审查提示、让 Agent 复述命令、在终端里弹一次确认框,都不是强制控制。只要危险命令已经进入 shell,保护是否生效就取决于使用者是否恰好看见并及时制止。真正有用的第一道防线,应位于命令执行之前,并能以机器可识别的失败信号把原因送回 Agent 的工作循环。
agent-guard 是一个 MIT 许可证的 Shell 项目,定位非常克制:它是 Claude Code PreToolUse hook 的参考实现,也能用于其他会在工具调用前运行 shell hook、并尊重退出码的 Agent CLI。它不试图理解自然语言意图,而是读取 hook 传入的 JSON,从中取出拟执行的命令;匹配到规则时向标准错误输出解释,并以退出码 2 拒绝执行。对 Agent 而言,这意味着工具调用失败且原因可见;对人而言,意味着需要显式接管这一步。
为什么“执行前”比事后审计更适合不可逆操作
日志、Git 历史和审计平台擅长回答“刚刚发生了什么”,但不能找回未提交的工作区,也不能撤销已发出的云资源删除请求。对于不可恢复的动作,控制点要前移:先判定,再运行。agent-guard 的规则以“人类可读标签 + 扩展正则”的形式放在一个 Bash 数组中,匹配对象是完整命令字符串,而不只是程序名。
项目默认覆盖的范围包括硬重置、递归或强制删除、清理未跟踪文件、工作树恢复、历史重写、对共享分支强推、DROP/TRUNCATE、Redis FLUSHALL/FLUSHDB、云资源删除、kubectl delete、terraform destroy、Docker prune、危险权限修改、把下载内容直接 pipe 到 shell,以及向块设备原始写入。这个列表不是“安全认证”;它的价值在于将团队最不希望由 Agent 自动做出的动作,变成可版本化、可审查、可测试的本地策略。
例如仓库中的一条规则会识别带 --hard 的 git reset。命中时返回的信息类似如下:
BLOCKED by guard [discard all local changes (git reset --hard)] command: git reset --hard HEAD~3 This one is gated to the human. Do not retry, do not reword.
这里的“不要重试、不要换一种表述”很重要。若只返回一个模糊的失败,Agent 可能换参数、拆分命令后再次尝试;明确原因能把下一步导向人工决策,而不是让模型继续猜测绕过路径。
安装前先把范围放对:这是安全带,不是防滚架
在目标仓库中克隆项目后,可以让安装脚本把 hook 合并到项目级 .claude/settings.json:
git clone https://github.com/vandith1/agent-guard.git cd agent-guard bash install.sh --yes /path/to/your-project bash test-guard.sh
安装脚本会检查 guard-command.sh 是否存在,并在修改已有配置前创建带时间戳的备份。它写入的核心配置是一个仅匹配 Bash 工具的 PreToolUse hook;因此要先确认你的 Agent CLI 使用的是兼容的 hook 负载格式。项目自带测试脚本会把 JSON 格式的 Bash 调用喂给实际 guard,验证危险命令预期返回 2,普通的 npm test 和搜索文本中包含 delete 的 grep 预期允许通过。
不要跳过这一步测试。正则规则最容易产生两种事故:漏拦截,以及过度拦截。后者尤其影响日常开发——一条过宽的规则可能连提交信息中引用危险短语也拒绝。agent-guard 选择匹配整个命令字符串,故意不尝试解析复杂的 shell 引号、展开与 eval;这是以可能出现误拦截,换取更简单、偏向拒绝的策略。对团队来说,正确流程是每加一条规则,就同时加入一个应拒绝案例和一个相近但应放行的案例。
还有一个容易被忽略的实现细节:hook 输入并非普通命令行参数,而是标准输入中的 JSON。脚本优先调用 python3 解析 tool_input.command;如果系统没有 Python,才退回到一个较简单的 sed 提取方式。若负载里明明带有 command 字段、但脚本无法正确读出命令,它会选择失败关闭(fail closed):输出原因并拒绝本次调用,而不是默默放行。这个取舍适合保护不可逆动作,却也意味着升级 Agent CLI、修改 hook 格式或定制 wrapper 之后,必须重新运行自测并手动试一次无害命令,确认不会因协议不兼容把日常工作全部挡住。
定制规则时,应把“命令文本”与“执行语义”分开看。比如 kubectl delete 可以删掉一个临时 Job,也可以删掉关键 Namespace;默认规则按命令类别拦截,追求确定性而非语义推断。团队若需要更细粒度的例外,优先把例外放到受限的人工审批流程,而不要简单放宽正则。否则一次为了方便而加入的 .*、宽泛参数匹配,可能让本应被拒绝的命令穿过策略。每条新增模式都应配一个接近真实的命令样本,并在测试中记录预期退出码。
把规则按“不可逆”和“需审批”分层
把所有有副作用的命令全部禁止,通常只会让开发者关闭 guard。更可行的划分是两层。
第一层是永不自动执行的不可逆操作:删除本地改动、清库、销毁基础设施、改写共享历史等。agent-guard 的免费核心瞄准的正是这一层,规则命中后直接拒绝。
第二层则是会影响外部世界、但可以人工复核或回滚的动作,例如发布 npm 包、部署、迁移或向远端推送。它们不应该被伪装成“绝对禁止”,而应进入具有短时效、可审计的人类授权机制。agent-guard 的 README 也明确区分了这两类问题:核心脚本只处理 Tier 1,不替代 Tier 2 的审批设计。
这种分层还能避免一个常见误解:本地 PreToolUse hook 不是对抗恶意代码的沙箱。项目明确说明,基于正则的命令字符串匹配可能被混淆、编码载荷、别名或脚本间接调用规避。若 Agent 或插件已经在主动规避控制,问题应升级为最小权限凭据、隔离执行环境、服务端分支保护、备份与供应链审查,而不是继续堆叠正则。
在实际故障处理中,还应区分“命令被挡住”与“任务被安全地恢复”。前者只说明当前这一条 shell 调用没有执行;后者仍需要 Agent 把失败原因呈现给人,并保留它当时要运行的完整命令、目标环境和前置状态。若是误拦截,人可以审核后用受控方式执行;若是真正危险的命令,则应回到任务计划,改用只读检查、备份、临时分支或专用测试环境。把拒绝信号当作一次可追踪的工作流事件,才能避免团队为了赶进度而直接禁用整套保护。
同样,不要把规则文件里的命令清单误解为风险清单的全部。危险动作也可能藏在 Makefile、npm script、容器入口脚本或远端 CI 中;本地 hook 覆盖的是 Agent 可见且经过该工具接口发出的命令。因此落地时应先绘制 Agent 的实际执行路径:哪些操作经过 Bash,哪些通过专用 API 或 MCP 工具,哪些在 CI/CD 中发生。对后两类路径,需要在各自的权限、审批和审计层补上控制,而不是期待一段 Shell hook 自动覆盖所有边界。
在团队落地时,别把策略文件当成个人 dotfile
建议把 guard 规则和测试放进受代码评审保护的仓库:先从高确定性的命令开始,观察误报,再扩展到团队真实发生过的事故类型。规则变更应在 CI 中运行测试;共享分支还要保留服务端分支保护,因为本地 hook 可以被绕过或根本没有安装。对于生产数据库、云账号与集群,优先给 Agent 使用权限更小、环境隔离的凭据,避免“拦不住时就是全权限”的单点失败。
agent-guard 适合的场景很明确:你需要在现有 Agent 工作流中快速加上一道本地、可读、可改、可测的不可逆命令闸门。它不适合被宣传为完整安全平台。把它和备份、最小权限、隔离环境、分支保护以及真正的人类审批结合,才是让 Agent 保持高速度而不把一次错误循环放大为事故的办法。