使用语义分析,而不是通配符模式
编码智能体支持使用通配符匹配的 deny rule,例如匹配git reset --hard 的通配符规则。这类规则拿原始命令字符串去和模式比对,因此空格、标志顺序或命令包装上的任何变化,都可能让阻止悄无声息地失效。调换标志顺序(rm -r -f /)、放进 shell 包装器(sh -c "rm -rf /")或藏在解释器后面,都能绕过字符串匹配。
CC Safety Net 会解析每条命令,再交给理解 git、rm、Remove-Item、find、xargs 和 parallel 实际选项语法的分析器。因此,判定基于命令的作用,而不是命令的外观。
代价是复杂性:解析器必须正确处理 shell 语法,每个受支持的命令都需要自己的分析器。收益是对最要紧的那些命令具备抗绕过能力。管线本身见架构,每个分析器的具体行为见分析引擎。
固定顺序,并先执行始终启用的保护
在每个集成中,每次工具调用都经过相同的有序阶段。其中两个阶段会刻意在加载策略快照之前运行:用户和项目策略文件的保护,以及 Git 元数据保护。 这个顺序很重要。配置之后才运行的保护,不会比配置本身更可靠,而被攻陷的智能体很可能先修改配置。这两个防护在读取策略文件前就能拒绝请求,因此不携带配置状态。预设或 override 无法放宽它们,被保护文件中的设置也关不掉这两个防护。代价是这类拒绝不能报告安全级别或 fallback 原因,因为此时两者尚未确定。这里用诊断细节换取无条件保护。 敏感路径保护则位于这条线的另一侧,在快照之后执行。它确实受策略控制,因为哪些路径算敏感本就该由本地决定,而且拒绝路径只有在你能添加自己的路径时才有意义。 具体阶段表见架构。无法完成分析时 fail closed
CC Safety Net 无法完成分析时会阻止,而不是允许:- 防护中任何位置抛出的错误都会在每个入口点变为拒绝,并归因于抛出错误的阶段。
- 超过工具输入边界或解析器预算都会导致拒绝,在所有安全级别下都是如此。
- Strict 模式把 fail-closed 进一步扩展到解析器无法完全理解的命令,以及它无法验证的破坏性目标。
让智能体继续任务的拒绝方式
拒绝不是一种错误状态。它在当前会话中作为普通工具结果返回给智能体。正是这一行为决定了 CC Safety Net 如何撰写每一条阻止消息。 一句干巴巴的permission denied 可能让智能体去重试类似命令,或者干脆停下整个任务。反复换着变体尝试,有可能撞上一种未受保护的写法,还会白白消耗智能体的轮次;停下整个任务则把一次安全动作变成一次停工。
因此,每条消息都力求给智能体一个有效的后续动作。原因会用平实的语言说明这条命令本会做什么,并在存在更安全的替代方案时点明该方案。每条规则还带有一个 intent,由它选择结尾指令:报告阻止并继续任务的其余部分、改用指定的替代方案、用更窄的明确目标重试、把操作交给用户,或者重构命令而不是穷举变体。即使内部错误触发 fail-closed 拒绝,intent 也会要求重构命令,不要重试。这样,工具意外失败时仍能引导智能体采取有用的应对措施。
这条指令刻意只是建议性的。没有任何机制强制智能体遵守;执行由防护完成,不服从的重试同样会被阻止。消息的意义在于让合规路径成为最省事的一条,从而在常见情况下,会话吸收一次阻止后就能继续前进。消息结构和完整的 intent 表见工作原理。
最小依赖面
CC Safety Net 运行时只依赖一个延迟加载的软件包。命令段拆分、引号、重定向、命令替换和动态 word 来源等结构信息,都来自它自有的有界 POSIX 和 PowerShell 解析器,不依赖第三方语法库。选择自建解析器有以下原因:- 更小的依赖树意味着更小的供应链攻击面,而解析器是攻击者最想混淆的组件。
- 分析需要通用 tokenizer 不会保留的事实,例如哪些 word 来自展开、哪个目标锚定在工作目录、使用了哪种引用形式。因此,自有解析器是让这些事实成为一等数据的唯一途径。
- 解析器预算可以是固定常量而不是配置项,于是资源耗尽成为一种有界、可测试的失败模式,而不是开放式的失败。
- 对于把 CC Safety Net 作为 hook 子进程运行的智能体,它在每次 shell 工具调用时都是全新启动,因此启动时间很重要,依赖越少冷启动越快。
Rulebook 系统
早期版本把自定义规则存为单个项目文件中的内联 JSON。CC Safety Net 出于四个原因改用了 rulebook 系统:- 共享:
rule add和rule update按指定 ref 从 GitHub 仓库获取 rulebook,并落盘到使用方自己的rules/<name>/rulebook.json,使团队无需手工复制 JSON 即可共享阻止策略。落盘副本就是使用方仓库里的一个文件:人可以直接阅读,可以在 pull request 中评审,更新改了什么也能从 diff 看出来。 - 完整性:
rule add和rule update会把 ref 解析到 commit,通过 HTTPS 获取文件(禁止重定向,响应的字节数和时间都有上限),再做 schema 验证,并要求name与来源一致。对rulebook_version: 2,还会用该 rulebook 自身的 fixture 校验它自己的规则。任何一步失败的来源,都会在写入任何内容之前被拒绝。落盘之后没有再与上游内容比对的机制,此后与仓库中其他文件一样靠评审保证。 - 范围:rulebook 支持独立的用户(全局)和项目范围,并使用不同配置目录。
- 验证:rulebook 内容必须通过 schema 验证,才能影响阻止判定。
使用分级级别,而不是一个设置
保护分为 standard、strict 和 paranoid 三个预设,而不是一个开关。是否阻止无法验证的命令,取决于命令的来源。 Standard 针对有人监督的会话做了优化:它阻止可识别的破坏性命令和敏感内容访问,同时容许无法解析但无害的文本,因此不会打断日常工作。面对对抗性或动态输入,它明确只提供尽力保护;当命令可能来自提示注入或其他不可信上下文时,它不是合适的设置。 Strict 和 paranoid 用额外限制换取确定性。Strict 阻止无法验证的内容;paranoid 还会阻止通常安全但偶尔会造成灾难的类别。设计上不会通过增加 standard 模式的解析启发式来填补缺口。静态分析无法确定的目标,不会因为增加猜测就变得安全。发现新缺口时,应为 strict 或 paranoid 添加 fail-closed fixture,并记录 standard 模式的残余风险。 各项能力仍可单独设置,任何 per-rule override 都能在 standard 下强制启用 strict tier 的规则。而任何 override 都做不到的,是削弱灾难性规则或始终启用的保护。级别见模式,各条边界的准确位置见分析引擎。纵深防御,而不是替代方案
CC Safety Net 不声称自己是完整的安全方案。它是纵深防御堆栈中的一层:- 权限 deny rule 提供快速且用户可配置的阻止。CC Safety Net 在权限系统之前运行,因此无论 deny rule 如何配置,它都会检查每条命令。
- 操作系统级沙箱限制文件系统和网络访问,但不理解边界内的操作是否具有破坏性。沙箱目录中的
git reset --hard从沙箱角度看在技术上安全,但仍会造成严重误操作。
Worktree 放宽
Linked Git worktree 会产生可用性问题:在 worktree 中工作的开发者通常需要运行git checkout -- . 或 git reset --hard 来丢弃该 worktree 中的本地更改,但默认规则会把它们作为 local-discard 操作阻止。
Worktree 模式只放宽 local discard,不会全面关闭这条规则。目录必须通过验证,确实属于 linked worktree,而且没有任何设置重定向 Git 上下文。仅仅看起来像 worktree 并不够。验证无法完成时,命令仍会被阻止。
这项放宽刻意保持狭窄:所有影响远程的操作(force push、删除分支、丢弃 stash)仍被阻止,可能波及这个可丢弃 worktree 之外的 local discard 同样仍被阻止。具体条件和不可放宽的情况见分析引擎,启用方法见模式。
后续阅读
技术指南先讲用户看到的流程,再讲设计依据。本页是第 5 步,也是最后一步。 相关页面:安全模型说明这些决定保护的信任边界,已知限制说明它们不能解决的问题,配置恢复说明上文引用的ready 和 degraded 约定。