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.
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.
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.
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.
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.
Related pages
- Security policy — how to report a bug or a vulnerability.
- Configuration recovery — the complete
readyanddegradedcontract. - Design principles — the rationale behind fail-closed and semantic analysis.
- Known limitations — residual risks like the symlink TOCTOU window.
- Audit log — where redacted command records are written.