Git commands
Filesystem commands
rm -rf is classified by target, first match wins. Root/home targets (/, ~, $HOME), protected Git metadata, the cwd itself (rm -rf .), and the case where your cwd is your home are blocked in every level. Other literal paths outside the cwd are also blocked unless they are recognized temp targets or configured allow paths. Temp paths and paths inside the cwd are allowed. Note the distinction: rm -rf ./subdir is allowed, but rm -rf . (the cwd itself) is blocked.
Two level-dependent adjustments apply on top of that:
- Dynamic targets (
rm -rf "$target", backticks, substitutions) are allowed in standard and blocked once the fail-closed capability is on. Standard is best-effort against adversarial or dynamically generated command text. - With
CC_SAFETY_NET_PARANOID_RM=1, non-temp recursive forced removal is blocked even inside the cwd, sorm -rf ./cacheno longer passes. Temp targets and configured allow paths still do.
Configured allow paths
Absolute or~/-prefixed directories listed under destructive_command_protection.allow_paths are treated like trusted temp roots. They apply in every safety level, to rm, PowerShell Remove-Item, and find -delete.
Allow paths never widen anything else. They do not relax sensitive-path protection or deny paths. They cannot cover root, home, or protected Git metadata. An allow path containing a repository still blocks rm -rf of that repository’s .git. Because dynamic targets are classified first, an allow path never applies to an unverifiable target. Entries equal to or containing $HOME are rejected during validation and after canonicalization. Symlink escapes out of an allow path are not covered.
Heredoc data consumers
An unexpanded heredoc on stdin whose consumer only stores or publishes the body is data, not a program, so the body is never scanned as command text. The consumer must be a literalcat, tee, git apply, git commit, gh pr create, or gh issue create; the heredoc must be the command’s only input redirection; and cat/tee must not feed an output process substitution (>(...)). This allowance holds in every level, strict and paranoid included.
The heredoc must be unexpanded: either the delimiter is quoted (
<<'EOF'), or the delimiter is unquoted and its body contains no $, backtick, or backslash, which leaves the shell nothing to expand or unescape. An unquoted body that does contain one of those three characters goes through shell expansion or escape processing, so standard scans it and fail-closed modes deny it. Commands outside the heredoc are still analyzed. For example, cat <<'EOF' && rm -rf ~ blocks on the rm. For cat, tee, git commit, gh pr create, and gh issue create, a body whose delimiter is quoted is also masked before sensitive-path extraction. Prose that mentions a secret filename therefore does not block the commit. An unquoted body is not masked, even when it passes the gate, so a filename-looking token inside it is still extracted. git apply bodies stay visible because a patch names the files it writes. See Heredoc analysis for the full gate and standard-mode behavior when a heredoc fails it.
Standard-only allowances
These shapes are permitted in standard and denied once strict or paranoid is active. They are deliberate trade-offs, not oversights: standard is best-effort against adversarial or dynamic input.
The quoted-assignment deferral is narrow. The assignment itself executes nothing, and a quoted expansion stays one argv word, so it cannot split into a command plus flags. Any reference the analyzer cannot prove is such a data use keeps the assignment-time block: an unquoted expansion (
env $W), any expansion in command position (even quoted), a reference inside a command substitution, or a reference in an unquoted heredoc body. Handing the value to a shell is caught downstream instead: eval "$W", bash -c "$W", and echo "$W" | sh all deny because the shell execution source cannot be verified.
The generator allowance checks the command’s shape, not its author. There is no list of trusted generators. eval "$(CMD)" and source <(CMD) / . <(CMD) are admitted only when CMD is:
- one simple command, with no redirection and no nested command;
- made of literal words only, with no leading environment assignment; and
- headed by a basename that is not a remote fetcher (
curl,wget,fetch,aria2c,http,https,xh,xhs,nc,ncat,netcat), not a shell, and not a standard command wrapper such assudoorenv.
CMD itself is still analyzed, so eval "$(rm -rf /)" blocks. What is not checked is the shell CMD prints: standard extends trust to the output of every literal local command, including eval "$(cat somefile)". See eval and source of a generated command for the shapes that stay blocked.
Resource-exhaustion limits are not part of this trade-off. A command that exceeds the parser’s recursion or structural-validation bounds is denied in every level, standard included.
Device commands
dd, mkfs, and shred are analyzed in every level, but only the genuinely destructive shapes are blocked.
PowerShell commands
Selecting
posix as the shell mode deliberately disables the PowerShell removal rules entirely, while keeping the cross-shell rules such as git.reset-hard and rm.recursive-force-root-or-home. In auto mode, an explicit Remove-Item is detected, including after ;, a newline, &&, or ||. The same applies to Get-Content, Set-Content, Add-Content, Copy-Item, and Move-Item, and to an alias such as gc or cp whose argument is spelled as a PowerShell path expression.
Sensitive-path allowances
Sensitive-path protection is a bounded pattern set over supported shapes, so some shapes that mention a sensitive filename are still allowed.
Two of those allowances have precise boundaries worth knowing:
- Env templates. The exact basenames
.env.example,.env.sample,.env.template, and.env.defaults, plus any name that starts with.env.example.or.env.sample., such as.env.example.local, are exempted before any other sensitive-path rule runs, so they stay readable and writable even inside a protected home directory. The prefix form does not extend to the other two templates:.env.template.local, like every other.env.*name, blocks undersecret.pattern.env-variant. - Vendored directories. When any path segment is
node_modulesor__pycache__, or the adjacent pairvendor/bundleorvendor/cacheappears (a lonevendorsegment does not count), exactly two rule groups are suppressed: the extension rules (.pem,.p12,.key, …) and the broad extensionless key-basename rulesecret.pattern.ssh-key-basename(*_rsa,*_dsa,*_ed25519,*_ecdsa). Every other rule still applies there.node_modules/x/.envandnode_modules/x/id_rsablock as usual..gitis not in the skip set, so key material inside a.gittree (for example.git/hooks/deploy_key_rsa) matches the rules like anywhere else.
Worktree mode exceptions
WithCC_SAFETY_NET_WORKTREE=1, CC Safety Net permits selected local-discard commands after it verifies a linked Git worktree. If verification fails, the command remains blocked.
The following commands are allowed inside a linked worktree when worktree mode is active:
git restore <file>andgit restore --worktree <file>git checkout -- <file>,git checkout <ref> -- <file>,git checkout --force, and ambiguous multi-positional checkout formsgit switch --discard-changesandgit switch -f/--forcegit reset --hardandgit reset --mergegit clean -f(and combined flags like-fd)git rm --force/-f
git push --forceaffects the remote.git branch -Daffects shared refs.git stash drop/git stash clearaffect the stash shared across worktrees.git worktree remove --forcecould delete another worktree.
rm .git, rm -rf <resolved gitDir>, rm -rf <commonDir>, and the redirection > .git all still hard-stop, because the marker file’s resolved Git directories are protected too.