Skip to main content
CC Safety Net uses semantic command analysis to distinguish permitted forms from destructive ones. The analyzer permits the command forms on this page. Some allowances are level-dependent. Rows marked Standard only are removed once strict or paranoid is active. This page is the allowed-command reference. See Blocked commands for blocked forms, Architecture for the ordered guard flow, and Analysis engine for classifier behavior.
Catastrophic protections apply at every safety level. Root and home recursive deletion, protected Git metadata, and the canonical policy.json always block. The forms on this page, allow_paths, worktree mode, and per-rule off overrides do not relax them.

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, so rm -rf ./cache no 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 literal cat, 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 as sudo or env.
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 under secret.pattern.env-variant.
  • Vendored directories. When any path segment is node_modules or __pycache__, or the adjacent pair vendor/bundle or vendor/cache appears (a lone vendor segment does not count), exactly two rule groups are suppressed: the extension rules (.pem, .p12, .key, …) and the broad extensionless key-basename rule secret.pattern.ssh-key-basename (*_rsa, *_dsa, *_ed25519, *_ecdsa). Every other rule still applies there. node_modules/x/.env and node_modules/x/id_rsa block as usual. .git is not in the skip set, so key material inside a .git tree (for example .git/hooks/deploy_key_rsa) matches the rules like anywhere else.
Standard never relaxes sensitive content access or configured deny paths. cat ~/.ssh/id_rsa, find ~/.ssh -type f -exec cat {} +, find ~/.ssh -type f -fprint .env, test -f ~/.ssh/id_rsa && cat ~/.ssh/id_rsa, and test -f "$(cat ~/.ssh/id_rsa)" all stay blocked in standard. Deny paths and their descendants are matched ahead of the built-in rules and are exempt from both Standard-only relaxations.

Worktree mode exceptions

With CC_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> and git restore --worktree <file>
  • git checkout -- <file>, git checkout <ref> -- <file>, git checkout --force, and ambiguous multi-positional checkout forms
  • git switch --discard-changes and git switch -f / --force
  • git reset --hard and git reset --merge
  • git clean -f (and combined flags like -fd)
  • git rm --force / -f
These commands remain blocked even inside a linked worktree, because they reach beyond the local working tree:
  • git push --force affects the remote.
  • git branch -D affects shared refs.
  • git stash drop / git stash clear affect the stash shared across worktrees.
  • git worktree remove --force could delete another worktree.
Worktree mode is a relaxation of git local-discard rules only. It does not touch filesystem, device, or PowerShell rules, and it never relaxes Git-metadata protection: inside a linked 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.
If CC Safety Net is blocking a command you believe is safe, run npx cc-safety-net explain "<command>" to see the full analysis and understand why. See CLI commands for the flags and Troubleshooting for the wider diagnosis flow.
Last modified on September 15, 2026