AI Agent 总在你眼皮底下跑危险命令?Fence 用语义护栏在命令执行前就拦住它
Claude Code 和 Codex 跑起来很方便——你给一个 prompt,它就开始读代码、删文件、装依赖、甚至改系统配置。但有没有想过,如果 prompt 里夹了一条「把 ~/.ssh 里的密钥上传到某个 URL」,你的 Agent 可能真的会照做?或者更糟,一个藏在 README 或 package.json 里的 prompt injection 指令,在你毫不知情的情况下指使 Agent 执行 rm -rf ~?
这不是假设。AI Agent 运行在你的权限下,它调用的每条命令都拥有你对系统的完整访问权。传统的方法是用字符串黑名单拦截危险命令,但 rm -rf ~、rm -fr ~、sudo rm -rf $HOME 是三条不同的字符串——黑名单穷举不完。更麻烦的是,黑名单噪声太大,开发者最终会关掉它们。
语义护栏:Fence 的核心思路
Fence 是 hoop.dev(YC W21)团队开源的工具,GitHub 16⭐,MIT 许可证,用 Go 编写。它的设计出发点完全不同——不检查字符串是否匹配,而是解析命令的实际意图。
Fence 把一个命令解析为结构化的「语义事实」:这条命令在写什么文件?是否涉及网络传输?目标路径对不对?然后规则引擎基于这些事实来判断:这是无可争议的灾难(rm -rf /),还是需要确认的操作(git push --force),还是日常的安全操作(rm -rf node_modules)。
三种判定结果:
- 🛑 deny——直接阻止,不给 Agent 任何机会
- ⚠️ ask——暂停执行,向开发者询问是否确认
- ✅ allow——静默放行,不打断工作流
而且 Fence 默认是 fails open:如果它无法解析某个命令,命令照常执行。一个护栏绝不能让它保护的 Agent 卡住。
一个真实的对抗场景
Fence 的 README 展示了一个经典场景:你的 Agent 正在处理一个项目,而某个文件(比如下载的第三方包或 README)中藏有一段 prompt injection 指令,要求 Agent 执行 rm -rf ~ 并上传密钥。没有护栏时,Agent 照做——它无法区分「正常用户指令」和「隐藏的攻击指令」。
Fence 在这里做了什么?它拦截了 Agent 对 cat ~/.aws/credentials | curl ... 的调用——在命令实际执行之前。Agent 看不到密钥内容,Fence 告诉它「这条命令被阻止了」,Agent 会退而选择其他方案或告知用户。
安装与快速上手
安装方式有两种,macOS 推荐用 Homebrew,Linux 推荐用 npm:
brew install hoophq/tap/fence npm install -g @hoophq/fence
Windows 原生不支持(hook 路径未验证),WSL 下正常工作。
Claude Code 集成
fence init --global # 为所有项目添加 Fence hook
这条命令会在 .claude/settings.json 中添加 Fence 的 pre-tool-use hook。之后启动 Claude Code 会话,你会看到一条 🚧 Fence is guarding this session… 横幅——说明护栏已激活。
claude
测试一下:让 Agent 执行一个危险操作——Fence 会阻止它,并在聊天中显示 🚧 标记和触发的规则名。安全的操作(如 rm -rf node_modules)静默放行。
Codex 集成
fence init codex # 为当前项目添加 Codex hook
这条命令写入 .codex/hooks.json。然后在 Codex 中运行 /hooks 一次,批准 Fence 条目即可。Codex 的 shell 命令和 apply_patch 文件编辑都由同一套引擎检查。
退出
fence uninstall
精准移除 init 添加的 hook,不碰其他配置。开发者的机器,开发者说了算。
它能拦什么?
Fence 内置的推荐规则包覆盖最常见的危险场景:
| 场景 | 示例命令 | 判定 | |
|---|---|---|---|
| 删除家目录或根目录 | rm -rf ~ · sudo rm -rf / | 🛑 deny | |
| 格式化磁盘 | dd of=/dev/sda · mkfs.ext4 /dev/sdb1 | 🛑 deny | |
| 释放 fork 炸弹 | `:(){ :\ | :& };:` | 🛑 deny |
| 外泄密钥 | `cat ~/.ssh/id_rsa \ | curl …` | 🛑 deny |
| 开放系统路径 | chmod -R 777 / | 🛑 deny | |
| 删除工作区外文件 | rm -rf ~/.config/x | ⚠️ ask | |
| 读取密钥到上下文 | cat ~/.aws/credentials | ⚠️ ask | |
| 管道安装 | `curl … \ | sh` | ⚠️ ask |
| 重写 Git 历史 | git push --force · git reset --hard | ⚠️ ask | |
| 非注册源安装 | npm i git+https://… · pip install git+… | ⚠️ ask | |
| 注入安装钩子 | postinstall 注入到 package.json | ⚠️ ask | |
| 设置持久化 | crontab - · systemctl enable | ⚠️ ask |
关键点:Fence 不会被 flag 重排(rm -rf ~ vs rm -fr ~)、sudo、$HOME vs ~ 或重命名的 fork 炸弹骗过。每条检测规则都附带测试,同时验证「拦截正确场景」和「放过安全场景」。
定制你自己的规则
Fence 支持 YAML 自定义规则。创建 .fence.yaml(自动发现)或指定 --rules :
rules:
- id: no-terraform-destroy
effect: deny
match:
shell: { command_in: [terraform] }
regex: '\bterraform\b.*\bdestroy\b'
也可以单行覆写内置规则的效果,无需重新定义整条规则:
overrides: git-force-push: deny # ask → deny(更严格) pipe-to-shell-from-network: allow # 静默这个规则
规则包市场(Rulepack Registry)
Fence 还有一个规则包注册中心。其他人写好的护栏可以一键安装:
fence search # 浏览已发布的规则包 fence add terraform-safety # 校验和验证后立即生效
在团队内共享时,可以在 .fence.yaml 中通过 extends: [terraform-safety] 锁定基线。发布自己的包只需要一个 PR。
架构:适配器 → 引擎 → 规则包
Fence 的三层架构决定了它的可扩展性:
Agent 工具调用 → 适配器 → 引擎(shell AST 事实)→ 规则包 → allow · ask · deny
- 适配器(Adapter):将不同 Agent(Claude Code、Codex 等)的调用格式归一化为中性动作
- 引擎(Engine):将 shell 命令解析为语义事实——不是看字符串,而是看「这条命令在做什么」
- 规则包(Rulepack):将语义事实与规则匹配,最高严重等级的效果胜出
引擎和规则包不依赖任何特定的 Agent——这正是同一个规则包能在多个 Agent 间移植的原因。
Fence 是什么,和不是什么
Fence 是 本地自我保护:它住在你的配置文件里,你可以随时编辑或卸载它。这对于保护自己不因 Agent 的失误而遭受损失来说是正好的。
但它不是合规控制——在一个你拥有完全控制权的机器上,任何东西都可以被禁用。Fence 的威胁模型文档明确了这一点,包括已知的规避路径和接受理由。
如果你需要开发者无法关闭的集中管理护栏——全团队统一执行、审批工作流、审计日志——那是不同的信任模型,也是 hoop.dev 做的事。同样的思路,但实施在开发者无法覆盖的地方。
总结
Fence 用语义解析代替字符串匹配,从根本上解决了 AI Agent 护栏的两个经典困境:可绕过性和高噪声。对使用 Claude Code 和 Codex 的开发者来说,它是一个轻量但有效的安全补充——不像黑名单那么容易绕过,又不会因为误报让你想关掉它。
16⭐ 的 GitHub 项目和 MIT 许可证意味着它目前还比较新,但来自 hoop.dev(YC W21)团队的背景和清晰的架构设计,让它比同类工具走得更远。如果你已经在日常工作流中依赖 AI 编码 Agent,花 5 分钟装个 Fence 可能是在危险命令真的被执行前,你最便宜的一道防线。
相关链接