Skip to main content
这一页写给构建执行命令那一侧的人:智能体、任务运行器、CI 工具链、带 shell 工具的插件宿主。你不会往这些程序里安装 hook,而是自己发起检查。 嵌入所需的只有一个函数:从 cc-safety-net/api 子路径导出的 checkCommand。类型、抛出的 TypeError 消息,以及一次调用会读取什么,都由库 API 作为签名参考承载。这一页讲的是围绕它的决策。

进程内检查还是安装 hook

两条路径走的是同一个 guard,区别在于由谁持有这次调用。 判断依据可以从最后一行推出来,但本质是归属问题。如果你的用户运行的是受支持的智能体 CLI,把安装命令给他们,让集成去做这件事;各智能体的接入方式见集成架构。如果你本身就是执行命令的程序,那就调用这个函数。为自己的运行时再造一套 hook 配置生成器,等于重建一个已经存在的集成。
包的根导出是 OpenCode 插件对象,不是引擎。checkCommand 请从 cc-safety-net/api 导入。

嵌入模式下的策略解析

嵌入模式没有单独的配置。一次调用读取的文件和 hook 读取的完全相同,而读哪个项目的文件由 cwd 参数决定。
  • 用户范围。~/.cc-safety-net/policy.json~/.cc-safety-net/rules/;设置了 CC_SAFETY_NET_HOME 时改到该位置。这是运维者自己的基线,对每次调用都生效。
  • 项目范围。<cwd>/.cc-safety-net/policy.json<cwd>/.cc-safety-net/rules/,严格按你传入的路径解析,不会向上遍历父目录。跟踪会话子目录的宿主应当把仓库根目录作为 cwd 传入,否则提交到仓库的项目策略不会被加载。
  • **合并。**项目文件按字段叠加在用户文件之上。两个范围的受保护路径列表取并集,项目文件里的 audit 会被忽略,项目放宽的每个字段都会被报告而不是无声生效。合并契约由策略承载。
  • 环境变量。CC_SAFETY_NET_LEVEL 和各能力开关在每次调用时从 process.env 读取,而且级别只能高于策略文件的设定。见环境变量
按项目的差异就是按调用的差异,因为选择它的正是 cwd。同时管理多个检出的宿主,只要传对目录,每个检出就会拿到正确的策略,不需要额外 API。 动手写调用点之前,有两个行为值得先知道。无法 stat 为可读、可搜索目录的 cwd 会返回 fail-closed 的 deny,而不是抛异常,所以路径写错会导致命令被阻止,而不是去解析另一个项目。另外,输入本身不合法时(比如缺少 cwd,或 command 不是字符串)会抛出 TypeError,那是调用方代码的缺陷,不是对命令的判定。

API 保证什么

以下几点跨小版本保持不变,宿主可以据此构建:
  • 输入形状 { command, cwd }cwd 是绝对目录路径),以及两种结果 kind:allow,和带 reason 字符串与可选 ruleIddeny
  • deny 表示不要执行该命令,抛异常同样如此。抛异常永远不等于 allow。
  • cwd 是策略解析的基准。命令里的相对路径目标和项目的 .cc-safety-net/ 配置都按你传入的 cwd 解析,绝不会用 process.cwd()
  • API 路径不写审计记录,也不发起网络请求。一次调用读取的是本地策略文件、文件系统状态和环境设置;没有任何内容离开这台机器。
规则目录在任何小版本都可能变化。被阻止的命令会随每次发布增加,reason 的措辞也随之改变,所以一次升级可能把你测试夹具依赖的某条命令从 allow 变成 deny。请按 kind 分支。把 reasonruleId 留给人和日志,而不是拿来做匹配;并像对待任何行为被你测试覆盖的依赖一样,在 lockfile 里锁定版本。

完整示例:插件宿主

采用插件架构的宿主通常会暴露一个执行前的 hook,可以在那里否决一次工具调用。集成点就只有这一处。插件把宿主的事件映射成 { command, cwd },调用 checkCommand,再把 deny 转换成宿主自己的否决形式:
这个处理函数里有四个决定最关键:
  • 只检查命令类工具。checkCommand 解析的是 shell 命令文本。宿主的 read、write、search 等工具不是命令类工具,所以只把 shell 工具路由过来,其余照常放行。
  • **cwd 是绝对路径的项目根目录。**无论宿主怎么称呼它,这个值都决定选用哪份项目策略,并作为命令中 .env 之类相对路径的基准。
  • 抛异常就阻止。catch 是契约里 fail-closed 的那一半。让抛出的错误继续流向执行的宿主,等于把一次失败的检查变成了放行。
  • **事件不合法也要阻止。**随包发布的进程内集成正是这么做的:command 字段缺失或不是字符串的事件会被阻止,而不是跳过,因为没有任何可供分析的内容。
如果宿主的否决方式是抛异常而不是返回对象,那就抛出这条拒绝消息而不是返回它。形式变了,判断没变。

运维须知

  • **日志归你自己管。**这条路径不写审计记录,所以宿主不记录的拒绝就不会留下任何痕迹。logs 命令和 GUI 面板展示的是 hook 与插件的判定,不包含嵌入调用。
  • **运维者仍用常规工具配置你。**嵌入宿主读取的是同一批策略文件,因此 statusdoctorexplain 描述的就是你的宿主会做什么,前提是它们运行在你作为 cwd 传入的同一个目录里。用户问某条命令为什么被阻止时,指给他们Explain 跟踪
  • **ruleId 仅用于诊断。**打印它、记录它、用它去查规则都可以,但不要拿它做分支条件。
  • **调用是同步的。**要批量处理就用循环。这里没有异步或批量入口,加一个也只是掩盖调用本就会做的文件读取。
  • 版本可以从包里读。cc-safety-net/package.json 是导出的子路径,会报告自身依赖版本的宿主可以把它一并带上。

相关页面

最后修改于 2026年8月31日