Skip to main content
CC Safety Net sits between an untrusted command source (an AI coding agent) and a trusted execution environment (the host shell). This page documents the trust boundaries, what each safety level actually guarantees, how configuration failures are contained, how secrets are protected, and the attack surface. To report a vulnerability, see the security policy instead. CC Safety Net is a best-effort, static pre-execution policy gate for supported coding-agent tool calls. It is not an operating-system sandbox, a privilege boundary, or protection for commands that bypass an installed integration.

Trust boundaries

Primary boundary: AI agent to shell

The core trust boundary sits between the AI coding agent and the host shell. CC Safety Net is the gatekeeper.
  • Untrusted side — command strings generated by AI agents. These are treated as potentially hostile because agents can be manipulated via prompt injection, confused context, or adversarial instructions into producing destructive commands.
  • Trusted side — the host shell where commands would execute.
Every command that reaches the shell tool on a supported platform flows through the analysis engine before it is allowed to run. If analysis returns a block reason, the command is denied. The boundary stops at supported tool names and supported shapes. Adapters grant command-execution capability only to exact, integration-specific tool names; unknown tools keep conservative policy-file, Git-metadata, and sensitive-path inspection but their text is never treated as a shell command. Commands that bypass an installed integration entirely are outside the boundary.

Secondary boundaries

Four secondary boundaries cross into CC Safety Net from an external source. Each one is validated before it can influence analysis.

What each safety level guarantees

Standard, strict, and paranoid are presets that supply defaults for three capabilities: fail_closed, paranoid_rm, and paranoid_interpreters. The guarantee is not the same at every level.
Standard mode is not adversarial-grade. Dynamic rm -rf targets are not categorically blocked in standard — rm -rf "$target" is allowed there and blocked only in strict or paranoid. When commands may come from prompt injection or another adversarial context, strict or paranoid is required.
Independent of the level: sensitive content access and user-configured deny paths, including their descendants, are never relaxed, and the catastrophic protections (recursive deletion of root or the user’s home directory, destructive mutation of the protected Git metadata set, and destructive mutation of the canonical user policy.json) are always enforced. They do not depend on the safety level, the destructive-command master switch, per-rule overrides, or allow paths.

Configuration recovery boundary

Configuration is a trust boundary, not a kill switch. Invalid configuration resolves to one of two runtime states and never denies ordinary work merely for being invalid.
  • ready — every active source validated.
  • degraded — a candidate source was rejected and something safe is enforced in its place: an unverifiable rule source is dropped so it contributes no rules, a drifted or invalid local rulebook keeps its digest-verified cache, a duplicate rulebook name keeps the first claim, and an unreadable policy file falls back to the salvaged policy or to built-in protective defaults.
The rejected candidate is never treated as active. Dropping a source removes the denials that source contributed, which is a real reduction in enforcement relative to your configured policy — that reduction is reported on every surface rather than presented as security-neutral. What dropping cannot do is weaken a built-in rule: rulebooks contribute only blocking rules, and ignoring an unreadable rule.json restores the built-ins its overrides would have disabled. One exception is scoped and documented — transparent_wrappers lives in rule.json, so an unreadable rule.json narrows which wrapped commands built-in analysis unwraps for that scope. No command or path is allowlisted in return, because nothing is denied for being unconfigurable. Policy-file protection and Git-metadata protection are evaluated before the configuration snapshot is loaded, so they apply identically in both states and carry no configuration metadata. The complete contract — every failure row, the fallback it produces, and the recovery commands — lives in Configuration recovery.

Fail-closed enforcement

Fail-closed applies to that one tool call when analysis itself cannot complete: an unexpected analyzer failure, an input that cannot be parsed, or a resource limit reached. It is not a description of what happens to invalid configuration.
1

Hook entry points

The hook adapter wraps the analysis call in a try/catch. If analysis throws, the hook emits a deny decision with a “failed closed” reason instead of letting the command proceed. This applies to every stdin-based hook agent (Antigravity CLI, Claude Code, Cursor, Gemini CLI, GitHub Copilot CLI, Kimi Code).
2

Plugin and extension entry points

The Amp Code, OpenCode, OpenClaw, and Pi in-process integrations apply the same pattern — analysis errors are caught and re-surfaced as block messages so the platform treats them as denied commands. Codex is installed as a plugin but runs the shared stdin hook entry point, so it is covered by the step above. Hermes Agent layers the two: its managed Python plugin invokes that same stdin hook (cc-safety-net hook --hermes-agent), and the plugin itself blocks the call whenever analysis cannot complete — a missing npx, a working directory or Hermes session record it cannot resolve, a spawn failure, the 30-second timeout, a non-zero analyzer exit, or output it cannot read. See Integration architecture for which model each agent uses.
3

Malformed or oversized tool input

Untrusted recursive tool input is bounded to 64 object levels, 10,000 visited values, 10,000 own keys, 1 MiB per string, and 4 MiB of aggregate string data; hook stdin is capped at 8 MiB of raw bytes. Exceeding any boundary denies the call.
4

Parser resource exhaustion

Input beyond 131,072 UTF-16 code units, more than 16,384 words, or nesting beyond 64 levels is denied rather than analyzed incompletely. A separate budget of 16,384 derived tokens bounds the work that nested and embedded commands add after the initial parse — see Parsers and the runtime dependency surface. Both apply in every safety level, standard included.
5

Strict mode

Strict mode extends fail-closed to commands the shell parser cannot safely tokenize, so unparseable input is blocked rather than passed through. Standard mode allows safe-looking unparseable text.
Invalid configuration is deliberately not in this list. A rejected rule source is dropped and an unreadable policy file falls back to protective defaults, so ordinary work continues — see Configuration recovery boundary.
See Design principles for the rationale.

Secret redaction

Before any command or segment text is written to the audit log or returned to the agent, it passes through automatic secret redaction. The redactor scrubs PEM private keys, database URL environment variables, generic secret-bearing env assignments, common secret HTTP headers, URL credentials, presigned-URL signature query parameters (x-amz-signature, x-goog-signature, sig, signature), and known provider token prefixes (GitHub, Slack, npm, Stripe, PyPI), plus JWTs and AWS access key IDs. Each matched value is replaced with <redacted>. Redaction is conservative and pattern-based — it reduces the risk of leaking secrets that happen to appear in a command’s arguments, but it is bounded to recognized credential shapes. Absolute filesystem paths, project and directory names, hostnames, IP addresses, usernames, and any credential whose format is not on the pattern list are retained verbatim. New secret formats emerge regularly, so avoid piping real credentials through commands an agent runs. See the Audit log reference for the full redaction scope. The same bound applies to cc-safety-net explain: a real trace carries the command text you supplied, its parsed tokens, and absolute paths including your home directory. Review a trace before pasting it anywhere — see Explain trace.

Attack surface

The threat model enumerates the main attack surfaces and their mitigations. Network-level attacks and attacks on the agent platform itself are out of scope — CC Safety Net makes no network requests during command analysis and has no network layer. Resource exhaustion is bounded rather than mitigated by containment: input past the parser or tool-input limits is denied instead of analyzed incompletely.

Disclosure classification

The dividing line is: did the tool fail to stop a destructive command, or did the tool itself become the harmful vector? Coverage gaps are public because the strict and paranoid threat model already assumes an attacker can emit any destructive command, so publishing “this command shape is not caught” hands the attacker nothing they did not already have — it gets the gap fixed faster and lets users ship a custom rule as an immediate workaround. Report the command shape, not a ready-to-paste weaponized prompt-injection payload. The security policy has the submission procedure for both channels.
Last modified on August 9, 2026