Skip to main content
自定义阻止规则可以强制执行团队约定或项目专用的安全策略。规则采用 rulebook 布局,并合并用户范围与项目范围的配置。你可以保留个人默认值,再用项目配置调整项目规则。
重大更改:运行时不再加载旧版内联配置文件(.safety-net.json~/.cc-safety-net/config.json)。旧文件中的规则在迁移前不会生效,运行时也不会为此给出提示。rule verify 会报告旧文件。普通命令仍会运行。执行 npx -y cc-safety-net rule migrate 可把旧规则转换为 rulebook 布局。请参阅迁移旧配置
编写规则是 /cc-safety-net 技能的工作流之一。在智能体中运行它,用自然语言说明你想要什么:
该技能的其他工作流见 /cc-safety-net 技能。如果智能体不支持技能,可以这样提示它:

规则配置文件位置

CC Safety Net 从两个范围加载 rulebook 并将它们合并:
  1. 用户范围~/.cc-safety-net/rules/rule.json(用 rule init --global 创建)。用它保存适用于所有项目的个人默认值。
  2. 项目范围:项目根目录中的 .cc-safety-net/rules/rule.json。用它保存可提交到版本控制的团队或项目专属规则。
本地 rulebook 来源通过 project-rules 这样的裸名称引用。GitHub rulebook 来源使用 owner/repo#ref/<rulebook-name>,指向该仓库中的 .cc-safety-net/rules/<rulebook-name>/rulebook.jsonrule addrule update 会把该文件按相同的相对路径落盘到你自己的范围内,因此生效的每个 rulebook 都是配置目录下的一个文件。

范围合并行为

  • 两个范围的 rulebook 会合并,用户范围在前。
  • 活动 rulebook 名称重复时,以先声明者为准。 用户范围先加载,因此会遮蔽同名的项目 rulebook。后一个 rulebook 的所有规则都不生效。名称冲突会产生警告,并使运行时进入 degraded。请在 rulebook 文件和引用它的 rule.json 中同时重命名其中一个 rulebook。名称冲突有确定的处理方式,不是致命错误,因此即使另一个范围已经占用了该名称,在本范围添加或更新来源仍会成功。
  • 每个范围的 overrides 只作用于该范围自己的规则。 指向用户范围规则的项目覆盖会被忽略并给出警告,该规则保持用户配置的状态。项目配置无法禁用或改写用户规则。
  • 不匹配任何已知规则的覆盖键会被忽略并给出警告;其他覆盖和规则保持各自配置的状态。
  • 两个范围的 transparent_wrappers 取并集。
如果两个位置都没有配置,则只有内置规则生效。

管理 rulebook 来源

rulebook 来源由 rule.jsonrules 数组中的条目引用。共有两类:
  • 本地来源:形如 project-rules 的裸名称。rulebook 位于 .cc-safety-net/rules/project-rules/rulebook.json(项目)或 ~/.cc-safety-net/rules/project-rules/rulebook.json(用户)。本地来源必须留在各自的配置目录内。
  • GitHub 来源owner/repo#ref/<rulebook-name>,指向该仓库该 ref 下的 .cc-safety-net/rules/<rulebook-name>/rulebook.jsonrule addrule update 会把获取到的字节原样落盘到你自己范围内的 .cc-safety-net/rules/<rulebook-name>/rulebook.json,因此它的加载方式与本地 rulebook 完全一致。
rule 命令来添加、更新和删除来源,不要手动编辑 rule.json
加上 --global-g)即可操作用户范围而非项目范围。rule list 例外:它始终读取两个范围,且不接受 --global。每个 rule 子命令、它的选项及退出行为见 CLI 命令

从仓库安装 rulebook

除规范形式 owner/repo#ref/<rulebook-name> 外,rule add 还接受只写仓库的 owner/repo。只写仓库时,会添加该仓库在 .cc-safety-net/rules/ 下发布的全部 rulebook。有两个选项可以缩小范围,且都只能用于 owner/repo 形式的 rule add
  • --only <rulebook...> 接受一个或多个 rulebook 名称,并保留你书写的顺序。
  • --ref <ref> 指定分支、标签或提交,而不使用仓库的默认分支。ref 可以包含 / 分段,因此 --ref feature/rulebook-v2 也可用。
rule.json 保存的是规范形式 owner/repo#ref/<rulebook-name>,其中保留的是你指定的 ref,而不是解析出的提交,因此这个 ref 仍会跟着上游移动。解析出的提交只会报告、不会保存:添加时打印 Vendored at 1a2b3c4. 并写入 rulebook 文件,没有 lockfile 来固定它。 rule update 会重新解析每个选中的来源,分支或标签 ref 会跟到它当前指向的位置,落盘副本据此重写。各个来源相互独立:获取或校验失败的来源保留原有落盘副本,并报告 Failed to update <spec>: <message>,其余来源照常更新。唯一的例外是资源限制失败,它会中止整次运行。

资源限制

每个范围的 rules 数组最多容纳 64 个来源。超过限制时,rule.json 校验失败,并只报告一条错误:Rule config exceeds CC Safety Net's safe source limit. 系统不会逐条列出超限数组,整个范围的配置会像其他无效 rule.json 一样被丢弃。 从 GitHub 获取内容时有固定预算。rule addrule update 最多并发处理 4 个来源,单次运行最多发出 131 个 GitHub 请求,所有来源合计最多读取 64 MiB 响应字节。超出任一预算都会中止本次运行,并报 Rule synchronization exceeds CC Safety Net's safe resource limits.,而且失败的不只是超限的那个来源,而是本次运行中的全部来源。 rulebook 文件本身也有上限,且在 schema 校验之前检查。超出其中任何一项的 rulebook 只会得到一行 Rulebook exceeds CC Safety Net's safe validation limits.,不含逐字段的说明: 通过上限检查、但未通过 schema 校验的 rulebook,最多报告 64 条错误,其后是 Additional rulebook validation errors were omitted.

rulebook 是实时文件

没有 lockfile,没有 digest,也没有缓存。每个来源都从 <config-dir>/<rulebook-name>/rulebook.json 加载,运行时在每次工具调用时读取该文件。本地 rulebook 直接在该位置编写,远程 rulebook 由 rule addrule update 落盘到同一位置。保存后的改动从下一条命令起生效,之后无需再做任何发布或重建。 如果某个来源的 rulebook.json 缺失、不可读或无效,该来源不生效:它不贡献任何规则,其余所有来源和全部内置保护继续生效,普通命令也照常运行,运行时报告 degradedrule.json 不可读或无效时,该范围内的所有来源都不生效。GitHub 来源尚未落盘时,运行 npx -y cc-safety-net rule update 完整的状态模型、确切的诊断字符串和修复步骤见配置恢复

rule sync 已弃用

rule sync 不再做任何同步。它现在只做一件事:离线迁移早期版本遗留的 rule.lock 文件和 cache 目录。它会把仍与记录的 digest 一致的缓存 rulebook 落盘到其来源实际加载的路径,然后删除这两者。每次运行都以这一行开头:
遗留文件由 doctor 报告为 info 级检查项 Rulebook lock and cache leftovers detected。迁移打印的全部消息,以及它拒绝运行的情形,见 rule sync

透明包装器

如果团队通过 rtk 之类的包装器运行命令,分析器默认只能看到包装器。把包装器列入 transparent_wrappers 后,CC Safety Net 会提取其中可见的受保护子命令。内置分析和自定义规则便能像处理裸命令一样,处理 rtk git reset --hardrtk docker system prune rule wrapper 子命令来配置包装器,不要手动编辑 rule.json
这个字段的规则:
  • 没有内置默认值。 只配置你有意信任的包装器。
  • 包装器名称必须匹配 ^[a-zA-Z][a-zA-Z0-9_-]*$,并且在文件内必须唯一。
  • 保留命令不能作为包装器gitbusybox、内置分析的命令 rmfindxargsparallel、所有 shell 包装器、所有解释器以及 awk 解释器。
  • 解包时,会取包装器标志和 VAR=value 赋值之后的第一个可保护子命令,或者显式 -- 之后紧跟的那个 token。本身不可保护的子命令不会被解包。
  • 这里列出的包装器,以及重写或隐藏子命令而不是 exec 一个可见子命令的包装器,仍然不会被解包。这类命令只有顶层危险文本 fallback 扫描才可能捕获。
transparent_wrappers 位于 rule.json 中。如果某个范围的 rule.json 变得不可读,该范围的包装器就不再生效。这是配置被丢弃会削弱内置覆盖范围的唯一情况。rulebook 加载失败不影响 rule.json 的可读性,因此包装器不受影响。

创建你的第一个自定义规则

创建一份入门用的项目规则配置:
这会创建一个未生效的 .cc-safety-net/rules/rule.json,其中尚未配置 rulebook 来源:
加上 --example 还会在 .cc-safety-net/rules/example-rules/rulebook.json 写入一个未激活的示例 rulebook。只有该文件尚不存在时才会写入;而且 rule init 并不会引用它,因此必须把它添加为来源,它才会生效:
要编写自己的 rulebook,请创建 .cc-safety-net/rules/project-rules/rulebook.json,再用 npx -y cc-safety-net rule add project-rules 注册它。此时 rule.json 如下:
规则定义就写在该 rulebook 文件里:
保存文件就够了。从下一条命令起,git add -Agit add --allgit add . 都会被阻止,并显示你的自定义消息。想在此之前检查这个文件,运行:

rule.json schema

顶层 rule.json 决定哪些 rulebook 生效、应用覆盖并声明透明包装器。它与 policy.json 相互独立。policy.json 配置安全级别、内置保护、允许路径、拒绝路径和审计保留期,详情见策略
integer
必填
schema 版本。必须为 1
array
rulebook 来源字符串列表。默认为空数组。文件内的来源名称必须唯一,且最多允许 64 个来源。见资源限制
object
<rulebook-name>/<rule-name> 为键的规则覆盖。值可以是 "off"(禁用该规则),也可以是一个对象(替换该规则的阻止消息)。对象形式必须包含 reason,可以带上可选的 intent;省略 intent 时,规则自身的意图保持不变。
array
会透明执行可见受保护子命令的命令名称,让分析能够看穿它们。默认为空数组。条目必须唯一,且不能是保留命令。见透明包装器
同时替换消息和面向智能体的意图的覆盖如下所示:

rule.json 编辑器支持

CC Safety Net 为 rule.json 发布了一份 JSON Schema,它由运行时校验所用的同一份 schema 生成。把编辑器指向它,即可获得补全和校验:
它涵盖上面列出的 rule.json 字段,即 versionrulesoverridestransparent_wrappers。运行 rule verify 会为缺少 $schema 的有效规则配置补上这一引用。policy.json 没有公开发布的 schema。

Rulebook schema

每个 rulebook 都放在各自的 rulebook.json 文件中。
integer
必填
rulebook schema 版本。必须为 12。其他取值会以 rulebook_version must be 1 or 2 校验失败。见版本 2 规则
string
必填
rulebook 名称。必须与本地目录名或 GitHub 来源名称一致。
string
必填
rulebook 版本字符串。
string
rulebook 的人类可读描述。
string
rulebook 作者。
array
必填
此 rulebook 允许为其定义规则的命令。
array
必填
自定义阻止规则。见规则 schema
array
可选的 rulebook fixture。见 Fixture schema。版本 1 的 fixture 只做结构校验;版本 2 的 fixture 还会针对该 rulebook 自身的规则实际求值。

规则 schema

下面这些字段定义的是 rulebook_version 1 的规则。版本 2 用 match 对象取代了 subcommandblock_args,见版本 2 规则
string
必填
在 rulebook 内必须唯一。必须以字母开头,后跟字母、数字、连字符或下划线。最多 64 个字符。
string
必填
要匹配的基础命令。必须列在 allowed_commands 中。
string
要匹配的子命令,例如 addinstall。如果省略,则匹配任何子命令。
array
必填
触发阻止的参数(至少需要一个)。
string
必填
阻止时显示的消息。最多 256 个字符。
string
追加到阻止消息页脚的智能体行为意图。取 hard_stopuse_alternativescope_downmanual_onlystop_and_explain 之一。默认为 manual_only

版本 2 规则

写上 "rulebook_version": 2,即可按命令路径精确匹配,而不是靠子命令加一堆参数。在 gcloud compute 下阻止 delete 的版本 1 规则,只要命令里任意位置出现该 token 就会命中,因此 gcloud compute instances create delete 也会被阻止;命令路径写作 ["compute", "instances", "delete"] 的版本 2 规则则不会。 版本 1 的 rulebook 保持原样:字段、匹配行为,以及只做结构校验的 fixture 都不变。每个 rulebook 都按它自己声明的版本校验。 版本 2 规则沿用版本 1 的 namecommandreasonintent,并用 match 对象取代 subcommandblock_args
array
必填
必须按此顺序跟在命令后面的命令词。非空字符串组成的非空数组。
array
参数中必须至少字面出现其中一个 token。非空字符串组成、无重复的非空数组。
array
参数中只要字面出现其中任意一个 token,就不匹配。非空字符串组成、无重复的非空数组。
版本 2 不是忽略版本 1 的字段,而是直接拒绝。仍带有 subcommandblock_args 的规则会以 rules[0].subcommand: not supported in rulebook_version 2rules[0].block_args: not supported in rulebook_version 2 校验失败。

版本 2 匹配

  • 命令:与版本 1 一样,规范化为小写 basename。
  • 命令路径:CC Safety Net 依次扫描参数,跳过已知的带值全局选项及其取值。此后遇到的命令词必须与 command_path 完全一致,顺序也要一致。路径之后的参数不影响路径匹配。
  • 全局选项表:内置带值全局选项表只有 awsgcloudaz 三份。Terraform 不需要:它唯一的全局选项 -chdir=DIR= 连接,会作为单个 token 被跳过。
  • 未知选项:以 - 开头、不在该命令选项表中的 token 会被跳过,且不消耗取值。因此把表外的带值选项与取值分开书写(--newflag value),规则就匹配不上了。这是有意为之:对识别不了的选项,CC Safety Net 选择 fail open 而不是阻止。作为 rulebook 作者,请把它当作已知缺口并在 rulebook 中写明。
  • 不展开短选项-Ap 就是 -Ap。请把想匹配的每种写法都列出来,例如 "-destroy""--destroy"
  • 字面匹配且区分大小写:不支持正则表达式、glob 或子串匹配。
  • 先命中者生效:规则按顺序求值,第一条命中的规则产生阻止。
  • 发布通道需要单独的规则gcloud beta compute instances delete 不会匹配 command_path["compute", "instances", "delete"] 的规则。请另写一条 ["beta", "compute", "instances", "delete"] 的规则。

Fixture schema

fixture 用于记录预期行为。CC Safety Net 只解析它们的命令并交给该 rulebook 的规则求值,绝不执行。
string
必填
Shell 命令 fixture。
string
必填
只能是 blockedallowed
string
预期会阻止该命令的规则。blocked fixture 必填。
版本 1 的 fixture 只做结构校验。版本 2 的 fixture 会在 rule addrule update 获取来源时、以及 rule verify 读取 rulebook 目录时,针对该 rulebook 自身的规则求值。blocked fixture 只有在它指定的规则先命中时才算通过;allowed fixture 只有在没有任何规则命中时才算通过。fixture 失败的来源会在文件写入之前被拒绝,因此与自身 fixture 相矛盾的 rulebook 不会生效。加载 rulebook 时不会重新求值 fixture。 每条失败都会带上该 fixture 在 tests 中的下标:
rule verify 会在每条前面加上 rulebook 文件名,形如 example-rules/rulebook.json: tests[0]: ...

匹配行为

下面关于子命令、参数和选项的说明适用于 rulebook_version 1 的规则;版本 2 规则的匹配方式见版本 2 匹配。命令规范化、执行顺序和透明包装器对两个版本都适用。
  • 命令规范化:匹配前,命令先取其 basename。/usr/local/bin/npm 能匹配 "command": "npm" 的规则。
  • 子命令检测:子命令是紧跟在命令之后的第一个非选项参数。在 git --no-pager add -A 中,子命令是 add
  • 参数匹配block_args 中的参数按字面匹配,不支持正则表达式或 glob。
  • 短选项展开:合并书写的短标志会在匹配前拆开。-Ap 视为 -A-p
  • 长选项匹配:长选项按字符串精确匹配。--all-files 匹配 --all
  • 任意参数匹配:只要命令中出现 block_args 里的任意一个参数,就会被阻止。
  • 只增不减:自定义规则只能新增限制,无法绕过内置保护。
已知限制-Cfoo 被视为 -C -f -o -o,而不是 -C foo。阻止 -f 时,可能对紧贴在选项后的取值产生误报。

示例

阻止智能体全局安装包:
阻止 docker system prune

阻止消息格式

完整布局见阻止消息的形式。自定义规则会在原因前加上 rulebook 名称和规则名称,用于识别触发阻止的 rulebook:
前缀是 <rulebook-name>/<rule-name>。它也是你在 rule.jsonoverrides 中用来禁用规则("off")或替换其 reason 的键。

验证你的 rulebook

创建或编辑 rulebook 后,用以下命令验证:
rule verify 会检查两个范围的 rule.json,按防护加载配置的方式加载每个已配置的来源,并校验当前仓库 .cc-safety-net/rules/ 下的每个 rulebook 目录,其中包括版本 2 的 fixture。它绝不获取远程内容。

迁移旧配置

旧版内联配置文件(.safety-net.json~/.cc-safety-net/config.json不再在运行时加载 旧规则不会在原位置生效,也不会阻止普通命令。运行时不读取旧文件,因此防护阶段没有任何提示。npx -y cc-safety-net rule verify 才会报告残留的旧文件。升级后请运行一次。
之前,规则内嵌在单个内联配置文件中:
之后rule migrate 自动创建基于 rulebook 的布局:

无效的自定义规则配置

加载失败的自定义规则配置会被丢弃并停止生效,不会因此阻止命令。普通命令照常运行,其他有效的来源和所有内置保护继续生效。运行时会报告 degraded 被丢弃的来源会减少拒绝,不会增加拒绝,因此这类故障不会直接阻碍操作。请定期运行 npx cc-safety-net status。完整的回退矩阵、诊断字符串、报告位置和修复顺序见配置恢复
自定义规则配置不具备防篡改能力rule.json 和 rulebook 文件都只是尽力而为;只有 policy.json 属于受保护路径。如果手动添加或修改自定义规则,请务必用 npx -y cc-safety-net rule verify 验证。
最后修改于 2026年8月31日