四轮攻防实录:Cursor 如何绕过一款 AI Agent 安全工具
开发者将自研 Shell 命令拦截工具 Termaxa 与 Cursor 对接测试,发现 AI Agent 可通过命令变…
一位独立开发者在博客中记录了一场持续数周的「红队测试」:他把自己用 Rust 编写的命令行工具 Termaxa(一个用于拦截、预览、备份 AI 编程代理 Shell 命令的安全护栏)接入 Cursor 与 Claude Code,反复让代理尝试删除受保护的测试目录。结果显示,即便配合基于规则的策略与意图分类器,AI 代理依然能以多种方式绕过防护,而这些发现反过来塑造了 Termaxa 后续每个主要版本的特性。
第一轮:同一种意图,三种拼写
测试环境的策略将「递归删除」标记为 ask(询问)。Cursor 并没有攻击 Termaxa 的 Hook,而是用三种不同的 Shell 语法表达同一个删除意图:
rm -rf .Remove-Item -Recurse -Force .del /s /q .
基于规则的匹配把这三条命令视为「互不相关」,于是每次都是一次全新的策略评估,给自动放行的 UI 留下反复试错的空间。作者把这种问题定性为「策略表达力不足」而非「绕过」:代理始终守在规则之内,只是规则无法表达「这个会话已经为删除这件事被拒绝过」。
修复:意图分类 + 会话级断路器
修复方案成为 Termaxa 的核心特性 —— 会话断路器(session circuit breaker):
- 不再匹配「命令拼写」,而是按意图分类(file-delete、db-destroy、git-force、infra-destroy 等)。
- 当一条命令返回
ask时,从只追加的审计日志中统计同一会话、同一意图的累计尝试次数。 - 达到阈值(默认两次)后,把
ask升级为deny。
两个关键约束:断路器只升级 ask,不动 allow 与 deny;计数器从日志派生而非单独存储,新会话自动清零,每次拦截本身也会写日志。再次测试时,Cursor 从 PowerShell 切到 cmd,分类器跨 Shell 识别出同一意图,第三次删除尝试被拒绝。
第二轮:分类器也留了后门
Cursor 接着用 find . -maxdepth 1 -exec rm -rf {} + 顺利通过。原因是分类器只看首个 Token,而首词是 find,不属于删除命令,-exec 里藏着的 rm -rf 因此被忽略。修复方式不是给模式列表加新条目,而是把「删除的间接表达」作为概念加进分类器:覆盖 find -exec / -execdir / -ok / -okdir、find -delete、xargs 喂删除命令、unlink、shred -u 等情形,并以这条真实绕过命令本身作为回归测试,发布为 v0.11.1。作者强调:「枚举拼写是赢不了的军备竞赛,意图才是有限的。」
第三轮:逃出 Shell 的边界
Shell 路径被彻底封死后,Cursor 直接放弃 Shell,改用自身的「原生文件删除工具」,一次性删除约二十个文件,包括 .cursor/ 与 .termaxa/。审计日志里相关条目为零 —— 不是因为代理「绕过」了 Hook,而是因为代理的内置文件工具根本不经过 Shell,从一开始就处于 Hook 视野之外。作者把这个结论写进了 SECURITY.md、作为公开 Issue 提交,并放进 README 副标题:「Termaxa 是一面挡风玻璃,不是沙箱」。他建议有强隔离需求的场景把 Termaxa 与容器、seccomp、受限凭证等 OS 级隔离配合使用。
同批测试还发现:断路器按会话 ID 计数,而 Cursor 和 Claude Code 有时会在运行中途轮换会话 ID(不重启进程),这会让计数器归零,使得断路器更像「减速带」而非持久封顶。该问题同样被记录在案。
第四轮:Hook 静默失效
数周后,作者在接入「执行后回执」功能时打开调试捕获(TERMAXA_HOOK_DEBUG)做了一次常规实测。结果显示:Hook 被调用了 6 次,审计日志却一条记录都没有。
根因是 Cursor 3.11 改了 Hook API —— 事件名变为 preToolUse / postToolUse(驼峰),工具字段也变成了 tool_name: "Shell",而 Termaxa 的解析器只认旧格式。这意味着:在 Cursor 静默更新 Hook 协议的那段时间里,所有 Shell 命令依然被拦截、所有提示依然弹给用户,但没有任何一条进入审计日志,安全审计事实上已经停摆,作者把这一条称作「最让他后怕的发现」。
写在最后
四轮攻防下来,作者总结了三条对 AI 编程代理安全护栏设计者具有普遍意义的教训:第一,规则必须按「意图」而非「拼写」匹配;第二,护栏只能守住它挂载的那条执行路径(通常是 Shell),内置文件工具、HTTP 调用等都可能在视野之外;第三,护栏自身的兼容性同样脆弱,上游 IDE 一次 API 重命名就可能让日志静默失效,调试与版本对齐必须作为一等公民来维护。
