cc-safety-net 0.8.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +380 -114
- package/dist/bin/cc-safety-net.js +8565 -3694
- package/dist/bin/commands/doctor.d.ts +11 -2
- package/dist/bin/commands/explain.d.ts +16 -2
- package/dist/bin/commands/hook.d.ts +14 -0
- package/dist/bin/commands/index.d.ts +67 -3
- package/dist/bin/commands/rule.d.ts +14 -0
- package/dist/bin/commands/statusline.d.ts +10 -2
- package/dist/bin/commands/types.d.ts +11 -0
- package/dist/bin/doctor/config.d.ts +1 -1
- package/dist/bin/doctor/hooks.d.ts +4 -1
- package/dist/bin/doctor/system-info.d.ts +6 -2
- package/dist/bin/doctor/types.d.ts +30 -1
- package/dist/bin/explain/config.d.ts +3 -1
- package/dist/bin/hook/common.d.ts +20 -0
- package/dist/bin/hook/config-edit.d.ts +11 -0
- package/dist/bin/hook/constants.d.ts +6 -0
- package/dist/bin/hook/install/kimi-cli.d.ts +3 -0
- package/dist/bin/hook/install/types.d.ts +4 -0
- package/dist/bin/hook/install.d.ts +3 -0
- package/dist/bin/hook/integrations.d.ts +12 -0
- package/dist/bin/hook/kimi-cli.d.ts +1 -0
- package/dist/bin/integration-metadata.d.ts +68 -0
- package/dist/bin/rule/doc.d.ts +1 -0
- package/dist/bin/rule/format.d.ts +21 -0
- package/dist/bin/rule/index.d.ts +1 -0
- package/dist/bin/rule/migrate.d.ts +6 -0
- package/dist/bin/rule/verify.d.ts +9 -0
- package/dist/builtin-commands/templates/cc-safety-net.d.ts +1 -0
- package/dist/core/analyze/awk.d.ts +3 -0
- package/dist/core/analyze/child-analyzer.d.ts +16 -0
- package/dist/core/analyze/child-command.d.ts +15 -0
- package/dist/core/analyze/find.d.ts +8 -6
- package/dist/core/analyze/index.d.ts +4 -0
- package/dist/core/analyze/parallel.d.ts +4 -1
- package/dist/core/{rules-rm.d.ts → analyze/rm.d.ts} +0 -1
- package/dist/core/analyze/segment.d.ts +2 -2
- package/dist/core/analyze/shell-git-env.d.ts +10 -0
- package/dist/core/analyze/xargs.d.ts +2 -1
- package/dist/core/audit.d.ts +3 -0
- package/dist/core/config.d.ts +5 -3
- package/dist/core/env.d.ts +39 -1
- package/dist/core/format.d.ts +1 -0
- package/dist/core/git/config.d.ts +1 -0
- package/dist/core/git/env.d.ts +12 -0
- package/dist/core/git/index.d.ts +3 -0
- package/dist/core/git/parse.d.ts +9 -0
- package/dist/core/git/rules.d.ts +8 -0
- package/dist/core/git/worktree-relaxation.d.ts +11 -0
- package/dist/core/git/worktree.d.ts +11 -0
- package/dist/core/path.d.ts +1 -0
- package/dist/core/rules/custom-rule-validation.d.ts +5 -0
- package/dist/core/rules/policy/config-file.d.ts +19 -0
- package/dist/core/rules/policy/index.d.ts +6 -0
- package/dist/core/rules/policy/lockfile.d.ts +5 -0
- package/dist/core/rules/policy/paths.d.ts +31 -0
- package/dist/core/rules/policy/resolver.d.ts +16 -0
- package/dist/core/rules/policy/scope-policy.d.ts +18 -0
- package/dist/core/rules/policy/sources.d.ts +28 -0
- package/dist/core/rules/policy/sync.d.ts +10 -0
- package/dist/core/rules/policy/types.d.ts +77 -0
- package/dist/core/rules/rulebook.d.ts +28 -0
- package/dist/core/shell/command.d.ts +2 -0
- package/dist/core/shell/index.d.ts +4 -0
- package/dist/core/shell/options.d.ts +3 -0
- package/dist/core/shell/segments.d.ts +6 -0
- package/dist/core/shell/shared.d.ts +4 -0
- package/dist/core/shell/wrappers.d.ts +17 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4880 -1578
- package/dist/{features → opencode}/builtin-commands/commands.d.ts +1 -1
- package/dist/opencode/builtin-commands/index.d.ts +2 -0
- package/dist/{features → opencode}/builtin-commands/types.d.ts +1 -1
- package/dist/pi/builtin-commands/commands.d.ts +15 -0
- package/dist/pi/builtin-commands/index.d.ts +1 -0
- package/dist/pi/index.d.ts +5 -0
- package/dist/pi/index.js +6405 -0
- package/dist/pi/tool-use.d.ts +20 -0
- package/dist/types.d.ts +45 -2
- package/package.json +16 -8
- package/dist/bin/commands/claude-code.d.ts +0 -2
- package/dist/bin/commands/copilot-cli.d.ts +0 -2
- package/dist/bin/commands/custom-rules-doc.d.ts +0 -2
- package/dist/bin/commands/gemini-cli.d.ts +0 -2
- package/dist/bin/commands/verify-config.d.ts +0 -2
- package/dist/bin/custom-rules-doc.d.ts +0 -1
- package/dist/bin/verify-config.d.ts +0 -12
- package/dist/core/analyze.d.ts +0 -21
- package/dist/core/rules-git.d.ts +0 -8
- package/dist/core/shell.d.ts +0 -17
- package/dist/features/builtin-commands/index.d.ts +0 -2
- package/dist/features/builtin-commands/templates/set-custom-rules.d.ts +0 -1
- package/dist/features/builtin-commands/templates/verify-custom-rules.d.ts +0 -1
- /package/dist/bin/{hooks → hook}/claude-code.d.ts +0 -0
- /package/dist/bin/{hooks → hook}/copilot-cli.d.ts +0 -0
- /package/dist/bin/{hooks → hook}/gemini-cli.d.ts +0 -0
- /package/dist/core/{rules-custom.d.ts → rules/custom.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# CC Safety Net
|
|
2
2
|
|
|
3
|
-
[](https://github.com/kenryu42/cc-safety-net/actions/workflows/ci.yml)
|
|
4
|
+
[](https://codecov.io/github/kenryu42/cc-safety-net)
|
|
5
|
+
[](https://github.com/kenryu42/cc-safety-net)
|
|
6
|
+
[](#codex-installation)
|
|
6
7
|
[](#claude-code-installation)
|
|
7
|
-
[](#opencode-installation)
|
|
8
|
-
[](#gemini-cli-installation)
|
|
9
8
|
[](#github-copilot-cli-installation)
|
|
9
|
+
[](#gemini-cli-installation)
|
|
10
|
+
[](#kimi-cli-installation)
|
|
11
|
+
[](#opencode-installation)
|
|
12
|
+
[](#pi-installation)
|
|
10
13
|
[](https://opensource.org/licenses/MIT)
|
|
11
14
|
|
|
12
15
|
<div align="center">
|
|
@@ -15,7 +18,7 @@
|
|
|
15
18
|
|
|
16
19
|
</div>
|
|
17
20
|
|
|
18
|
-
A
|
|
21
|
+
A Coding Agent CLI plugin that acts as a safety net, catching destructive git and filesystem commands before they execute.
|
|
19
22
|
|
|
20
23
|
## Contents
|
|
21
24
|
|
|
@@ -24,13 +27,14 @@ A Claude Code plugin that acts as a safety net, catching destructive git and fil
|
|
|
24
27
|
- [What About Sandboxing?](#what-about-sandboxing)
|
|
25
28
|
- [Prerequisites](#prerequisites)
|
|
26
29
|
- [Quick Start](#quick-start)
|
|
30
|
+
- [Codex Installation](#codex-installation)
|
|
27
31
|
- [Claude Code Installation](#claude-code-installation)
|
|
28
|
-
- [OpenCode Installation](#opencode-installation)
|
|
29
32
|
- [Gemini CLI Installation](#gemini-cli-installation)
|
|
30
33
|
- [GitHub Copilot CLI Installation](#github-copilot-cli-installation)
|
|
34
|
+
- [Kimi CLI Installation](#kimi-cli-installation)
|
|
35
|
+
- [OpenCode Installation](#opencode-installation)
|
|
36
|
+
- [Pi Installation](#pi-installation)
|
|
31
37
|
- [Status Line Integration](#status-line-integration)
|
|
32
|
-
- [Setup via Slash Command](#setup-via-slash-command)
|
|
33
|
-
- [Manual Setup](#manual-setup)
|
|
34
38
|
- [Emoji Mode Indicators](#emoji-mode-indicators)
|
|
35
39
|
- [Diagnostics](#diagnostics)
|
|
36
40
|
- [Explain (Debug Analysis)](#explain-debug-analysis)
|
|
@@ -38,20 +42,22 @@ A Claude Code plugin that acts as a safety net, catching destructive git and fil
|
|
|
38
42
|
- [Commands Allowed](#commands-allowed)
|
|
39
43
|
- [What Happens When Blocked](#what-happens-when-blocked)
|
|
40
44
|
- [Testing the Hook](#testing-the-hook)
|
|
41
|
-
- [
|
|
42
|
-
- [Custom Rules
|
|
45
|
+
- [Breaking Change: Custom Rules Migration](#breaking-change-custom-rules-migration)
|
|
46
|
+
- [Custom Rules](#custom-rules)
|
|
43
47
|
- [Config File Location](#config-file-location)
|
|
44
48
|
- [Rule Schema](#rule-schema)
|
|
45
49
|
- [Matching Behavior](#matching-behavior)
|
|
46
|
-
- [Examples](#examples)
|
|
50
|
+
- [Rule Examples](#rule-examples)
|
|
47
51
|
- [Error Handling](#error-handling)
|
|
48
52
|
- [Advanced Features](#advanced-features)
|
|
49
53
|
- [Strict Mode](#strict-mode)
|
|
50
54
|
- [Paranoid Mode](#paranoid-mode)
|
|
55
|
+
- [Worktree Mode](#worktree-mode)
|
|
51
56
|
- [Shell Wrapper Detection](#shell-wrapper-detection)
|
|
52
57
|
- [Interpreter One-Liner Detection](#interpreter-one-liner-detection)
|
|
53
58
|
- [Secret Redaction](#secret-redaction)
|
|
54
59
|
- [Audit Logging](#audit-logging)
|
|
60
|
+
- [Development](#development)
|
|
55
61
|
- [License](#license)
|
|
56
62
|
|
|
57
63
|
## Why This Exists
|
|
@@ -66,12 +72,12 @@ Claude Code's `.claude/settings.json` supports [deny rules](https://code.claude.
|
|
|
66
72
|
|
|
67
73
|
### At a Glance
|
|
68
74
|
|
|
69
|
-
| | Permission Deny Rules | Safety Net |
|
|
75
|
+
| | Permission Deny Rules | CC Safety Net |
|
|
70
76
|
|---|---|---|
|
|
71
77
|
| **Setup** | Manual configuration required | Works out of the box |
|
|
72
78
|
| **Parsing** | Wildcard pattern matching | Semantic command analysis |
|
|
73
79
|
| **Execution order** | Runs second | Runs first (PreToolUse hook) |
|
|
74
|
-
| **Shell wrappers** | Not handled automatically (must match wrapper forms) | Recursively analyzed (
|
|
80
|
+
| **Shell wrappers** | Not handled automatically (must match wrapper forms) | Recursively analyzed (up to 10 levels) |
|
|
75
81
|
| **Interpreter one-liners** | Not handled automatically (must match interpreter forms) | Detected and blocked |
|
|
76
82
|
|
|
77
83
|
### Permission Rules Have Known Bypass Vectors
|
|
@@ -86,9 +92,9 @@ Even with wildcard matching, Bash permission patterns are intentionally limited
|
|
|
86
92
|
| Extra whitespace | `rm -rf /` (double space) bypasses pattern |
|
|
87
93
|
| Shell wrappers | `sh -c "rm -rf /"` bypasses `Bash(rm:*)` entirely |
|
|
88
94
|
|
|
89
|
-
### Safety Net Handles What Patterns Can't
|
|
95
|
+
### CC Safety Net Handles What Patterns Can't
|
|
90
96
|
|
|
91
|
-
| Scenario | Permission Rules | Safety Net |
|
|
97
|
+
| Scenario | Permission Rules | CC Safety Net |
|
|
92
98
|
|----------|------------------|------------|
|
|
93
99
|
| `git checkout -b feature` (safe) | Blocked by `Bash(git checkout:*)` | Allowed |
|
|
94
100
|
| `git checkout -- file` (dangerous) | Blocked by `Bash(git checkout:*)` | Blocked |
|
|
@@ -99,17 +105,17 @@ Even with wildcard matching, Bash permission patterns are intentionally limited
|
|
|
99
105
|
|
|
100
106
|
### Defense in Depth
|
|
101
107
|
|
|
102
|
-
PreToolUse hooks run [**before**](https://code.claude.com/docs/en/iam#additional-permission-control-with-hooks) the permission system. This means Safety Net inspects every command first, regardless of your permission configuration. Even if you misconfigure deny rules, Safety Net provides a fallback layer of protection.
|
|
108
|
+
PreToolUse hooks run [**before**](https://code.claude.com/docs/en/iam#additional-permission-control-with-hooks) the permission system. This means CC Safety Net inspects every command first, regardless of your permission configuration. Even if you misconfigure deny rules, CC Safety Net provides a fallback layer of protection.
|
|
103
109
|
|
|
104
|
-
**Use both together**: Permission deny rules for quick, user-configurable blocks; Safety Net for robust, bypass-resistant protection that works out of the box.
|
|
110
|
+
**Use both together**: Permission deny rules for quick, user-configurable blocks; CC Safety Net for robust, bypass-resistant protection that works out of the box.
|
|
105
111
|
|
|
106
112
|
## What About Sandboxing?
|
|
107
113
|
|
|
108
|
-
Claude Code offers [native sandboxing](https://code.claude.com/docs/en/sandboxing) that provides OS-level filesystem and network isolation. Here's how it compares to Safety Net:
|
|
114
|
+
Claude Code offers [native sandboxing](https://code.claude.com/docs/en/sandboxing) that provides OS-level filesystem and network isolation. Here's how it compares to CC Safety Net:
|
|
109
115
|
|
|
110
116
|
### Different Layers of Protection
|
|
111
117
|
|
|
112
|
-
| | Sandboxing | Safety Net |
|
|
118
|
+
| | Sandboxing | CC Safety Net |
|
|
113
119
|
|---|---|---|
|
|
114
120
|
| **Enforcement** | OS-level (Seatbelt/bubblewrap) | Application-level (PreToolUse hook) |
|
|
115
121
|
| **Approach** | Containment — restricts filesystem + network access | Command analysis — blocks destructive operations |
|
|
@@ -125,7 +131,7 @@ Sandboxing restricts filesystem + network access, but it doesn't understand whet
|
|
|
125
131
|
> [!NOTE]
|
|
126
132
|
> Whether they're auto-run or require confirmation depends on your sandbox mode (auto-allow vs regular permissions), and network access still depends on your allowed-domain policy. Claude Code can also retry a command outside the sandbox via `dangerouslyDisableSandbox` (with user permission); this can be disabled with `allowUnsandboxedCommands: false`.
|
|
127
133
|
|
|
128
|
-
| Command | Sandboxing | Safety Net |
|
|
134
|
+
| Command | Sandboxing | CC Safety Net |
|
|
129
135
|
|---------|------------|------------|
|
|
130
136
|
| `git reset --hard` | Allowed (within cwd) | **Blocked** |
|
|
131
137
|
| `git checkout -- .` | Allowed (within cwd) | **Blocked** |
|
|
@@ -142,16 +148,16 @@ Sandboxing is the better choice when your primary concern is:
|
|
|
142
148
|
- **Prompt injection attacks** — Reduces exfiltration risk by restricting outbound domains (depends on your allowed-domain policy)
|
|
143
149
|
- **Malicious dependencies** — Limits filesystem writes and network access by default (subject to your sandbox configuration)
|
|
144
150
|
- **Untrusted code execution** — OS-level containment is stronger than pattern matching
|
|
145
|
-
- **Network control** — Safety Net has no network protection
|
|
151
|
+
- **Network control** — CC Safety Net has no network protection
|
|
146
152
|
|
|
147
153
|
### Recommended: Use Both
|
|
148
154
|
|
|
149
155
|
They protect against different threats:
|
|
150
156
|
|
|
151
157
|
- **Sandboxing** contains blast radius — even if something goes wrong, damage is limited to cwd and approved network domains
|
|
152
|
-
- **Safety Net** prevents footguns — catches git-specific mistakes that are technically "safe" from the sandbox's perspective
|
|
158
|
+
- **CC Safety Net** prevents footguns — catches git-specific mistakes that are technically "safe" from the sandbox's perspective
|
|
153
159
|
|
|
154
|
-
Running both together provides defense-in-depth. Sandboxing handles unknown threats; Safety Net handles known destructive patterns that sandboxing permits.
|
|
160
|
+
Running both together provides defense-in-depth. Sandboxing handles unknown threats; CC Safety Net handles known destructive patterns that sandboxing permits.
|
|
155
161
|
|
|
156
162
|
## Prerequisites
|
|
157
163
|
|
|
@@ -159,6 +165,29 @@ Running both together provides defense-in-depth. Sandboxing handles unknown thre
|
|
|
159
165
|
|
|
160
166
|
## Quick Start
|
|
161
167
|
|
|
168
|
+
### Codex Installation
|
|
169
|
+
|
|
170
|
+
1. Enable Codex plugin hooks in `~/.codex/config.toml`:
|
|
171
|
+
|
|
172
|
+
```toml
|
|
173
|
+
[features]
|
|
174
|
+
plugin_hooks = true
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
2. Add the marketplace:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
codex plugin marketplace add kenryu42/cc-marketplace
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
3. Start Codex.
|
|
184
|
+
4. In the TUI, run `/plugins`.
|
|
185
|
+
5. Use arrow keys to select `[cc-marketplace]`.
|
|
186
|
+
6. Press Enter to install the plugin.
|
|
187
|
+
7. run `/hooks` and select the safety-net PreToolUse hook and press `t` to trust it.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
162
191
|
### Claude Code Installation
|
|
163
192
|
|
|
164
193
|
```bash
|
|
@@ -173,66 +202,56 @@ Running both together provides defense-in-depth. Sandboxing handles unknown thre
|
|
|
173
202
|
|
|
174
203
|
---
|
|
175
204
|
|
|
176
|
-
###
|
|
177
|
-
|
|
178
|
-
**Option A: Let an LLM do it**
|
|
179
|
-
|
|
180
|
-
Paste this into any LLM agent (Claude Code, OpenCode, Cursor, etc.):
|
|
205
|
+
### Gemini CLI Installation
|
|
181
206
|
|
|
182
|
-
```
|
|
183
|
-
|
|
207
|
+
```bash
|
|
208
|
+
gemini extensions install https://github.com/kenryu42/gemini-safety-net
|
|
184
209
|
```
|
|
185
210
|
|
|
186
|
-
|
|
211
|
+
---
|
|
187
212
|
|
|
188
|
-
|
|
213
|
+
### GitHub Copilot CLI Installation
|
|
189
214
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
```
|
|
215
|
+
```bash
|
|
216
|
+
/plugin install kenryu42/copilot-safety-net
|
|
217
|
+
```
|
|
195
218
|
|
|
196
219
|
---
|
|
197
220
|
|
|
198
|
-
###
|
|
221
|
+
### Kimi CLI Installation
|
|
222
|
+
|
|
223
|
+
Install CC Safety Net into your Kimi CLI config:
|
|
199
224
|
|
|
200
225
|
```bash
|
|
201
|
-
|
|
226
|
+
npx -y cc-safety-net hook install --kimi-cli
|
|
202
227
|
```
|
|
203
228
|
|
|
204
229
|
---
|
|
205
230
|
|
|
206
|
-
|
|
231
|
+
|
|
232
|
+
### OpenCode Installation
|
|
233
|
+
|
|
234
|
+
Install CC Safety Net with OpenCode's native plugin command:
|
|
207
235
|
|
|
208
236
|
```bash
|
|
209
|
-
|
|
237
|
+
opencode plugin -g cc-safety-net
|
|
210
238
|
```
|
|
211
239
|
|
|
212
|
-
> [!NOTE]
|
|
213
|
-
> After installing the plugin, you need to restart your Copilot CLI for it to take effect.
|
|
214
|
-
|
|
215
240
|
---
|
|
216
241
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
Safety Net can display its status in Claude Code's status line, showing whether protection is active and which modes are enabled.
|
|
220
|
-
|
|
221
|
-
### Setup via Slash Command
|
|
242
|
+
### Pi Installation
|
|
222
243
|
|
|
223
|
-
|
|
244
|
+
Install CC Safety Net with Pi's package installer:
|
|
224
245
|
|
|
225
|
-
```
|
|
226
|
-
|
|
246
|
+
```bash
|
|
247
|
+
pi install npm:cc-safety-net
|
|
227
248
|
```
|
|
228
249
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
3. Offer to replace or pipe with existing commands
|
|
233
|
-
4. Write the configuration to `~/.claude/settings.json`
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Status Line Integration
|
|
234
253
|
|
|
235
|
-
|
|
254
|
+
CC Safety Net can display its status in Claude Code's status line, showing whether protection is active and which modes are enabled.
|
|
236
255
|
|
|
237
256
|
Add the following to your `~/.claude/settings.json`:
|
|
238
257
|
|
|
@@ -242,7 +261,7 @@ Add the following to your `~/.claude/settings.json`:
|
|
|
242
261
|
{
|
|
243
262
|
"statusLine": {
|
|
244
263
|
"type": "command",
|
|
245
|
-
"command": "bunx cc-safety-net --
|
|
264
|
+
"command": "bunx cc-safety-net statusline --claude-code"
|
|
246
265
|
}
|
|
247
266
|
}
|
|
248
267
|
```
|
|
@@ -253,7 +272,7 @@ Add the following to your `~/.claude/settings.json`:
|
|
|
253
272
|
{
|
|
254
273
|
"statusLine": {
|
|
255
274
|
"type": "command",
|
|
256
|
-
"command": "BUN_BE_BUN=1 claude x cc-safety-net --
|
|
275
|
+
"command": "BUN_BE_BUN=1 claude x cc-safety-net statusline --claude-code"
|
|
257
276
|
}
|
|
258
277
|
}
|
|
259
278
|
```
|
|
@@ -268,20 +287,20 @@ Add the following to your `~/.claude/settings.json`:
|
|
|
268
287
|
{
|
|
269
288
|
"statusLine": {
|
|
270
289
|
"type": "command",
|
|
271
|
-
"command": "npx -y cc-safety-net --
|
|
290
|
+
"command": "npx -y cc-safety-net statusline --claude-code"
|
|
272
291
|
}
|
|
273
292
|
}
|
|
274
293
|
```
|
|
275
294
|
|
|
276
295
|
**Piping with existing status line:**
|
|
277
296
|
|
|
278
|
-
If you already have a status line command, you can pipe Safety Net at the end:
|
|
297
|
+
If you already have a status line command, you can pipe CC Safety Net at the end:
|
|
279
298
|
|
|
280
299
|
```json
|
|
281
300
|
{
|
|
282
301
|
"statusLine": {
|
|
283
302
|
"type": "command",
|
|
284
|
-
"command": "your-existing-command | bunx cc-safety-net --
|
|
303
|
+
"command": "your-existing-command | bunx cc-safety-net statusline --claude-code"
|
|
285
304
|
}
|
|
286
305
|
}
|
|
287
306
|
```
|
|
@@ -294,15 +313,16 @@ The status line displays different emojis based on the current configuration:
|
|
|
294
313
|
|
|
295
314
|
| Status | Display | Meaning |
|
|
296
315
|
|--------|---------|---------|
|
|
297
|
-
| Plugin disabled | `🛡️ Safety Net ❌` | Safety Net plugin is not enabled |
|
|
298
|
-
| Default mode | `🛡️ Safety Net ✅` | Protection active with default settings |
|
|
299
|
-
| Strict mode | `🛡️ Safety Net 🔒` | `
|
|
300
|
-
| Paranoid mode | `🛡️ Safety Net 👁️` | `
|
|
301
|
-
| Paranoid RM only | `🛡️ Safety Net 🗑️` | `
|
|
302
|
-
| Paranoid interpreters only | `🛡️ Safety Net 🐚` | `
|
|
303
|
-
|
|
|
316
|
+
| Plugin disabled | `🛡️ CC Safety Net ❌` | CC Safety Net plugin is not enabled |
|
|
317
|
+
| Default mode | `🛡️ CC Safety Net ✅` | Protection active with default settings |
|
|
318
|
+
| Strict mode | `🛡️ CC Safety Net 🔒` | `CC_SAFETY_NET_STRICT=1` — fail-closed on unparseable commands |
|
|
319
|
+
| Paranoid mode | `🛡️ CC Safety Net 👁️` | `CC_SAFETY_NET_PARANOID=1` — all paranoid checks enabled |
|
|
320
|
+
| Paranoid RM only | `🛡️ CC Safety Net 🗑️` | `CC_SAFETY_NET_PARANOID_RM=1` — blocks `rm -rf` even within cwd |
|
|
321
|
+
| Paranoid interpreters only | `🛡️ CC Safety Net 🐚` | `CC_SAFETY_NET_PARANOID_INTERPRETERS=1` — blocks interpreter one-liners |
|
|
322
|
+
| Worktree mode | `🛡️ CC Safety Net 🌳` | `CC_SAFETY_NET_WORKTREE=1` — relax local git discards inside linked worktrees |
|
|
323
|
+
| Strict + Paranoid | `🛡️ CC Safety Net 🔒👁️` | Both strict and paranoid modes enabled |
|
|
304
324
|
|
|
305
|
-
Multiple mode emojis are combined when multiple environment variables are set.
|
|
325
|
+
Multiple mode emojis are combined when multiple environment variables are set. Mode flags use `CC_SAFETY_NET_*` names; legacy `SAFETY_NET_*` names are still accepted.
|
|
306
326
|
|
|
307
327
|
## Diagnostics
|
|
308
328
|
|
|
@@ -321,7 +341,7 @@ The doctor command checks:
|
|
|
321
341
|
| Hook Integration | Verifies the plugin is properly configured for each supported platform |
|
|
322
342
|
| Self-Test | Runs sample commands to confirm blocking works correctly |
|
|
323
343
|
| Configuration | Validates custom rules in user and project configs |
|
|
324
|
-
| Environment | Shows status of mode flags (
|
|
344
|
+
| Environment | Shows status of mode flags (`CC_SAFETY_NET_STRICT`, `CC_SAFETY_NET_PARANOID`, etc.; legacy `SAFETY_NET_*` also listed when set) |
|
|
325
345
|
| Recent Activity | Summarizes blocked commands from the last 7 days |
|
|
326
346
|
| System Info | Displays versions of all relevant tools |
|
|
327
347
|
| Update Check | Checks if a newer version is available |
|
|
@@ -335,7 +355,7 @@ The doctor command checks:
|
|
|
335
355
|
|
|
336
356
|
## Explain (Debug Analysis)
|
|
337
357
|
|
|
338
|
-
Trace how Safety Net analyzes a command step-by-step. Useful for debugging why a command is blocked or allowed, or when developing custom rules.
|
|
358
|
+
Trace how CC Safety Net analyzes a command step-by-step. Useful for debugging why a command is blocked or allowed, or when developing custom rules.
|
|
339
359
|
|
|
340
360
|
```bash
|
|
341
361
|
npx cc-safety-net explain "git reset --hard"
|
|
@@ -367,6 +387,8 @@ npx cc-safety-net explain --cwd /tmp "git status"
|
|
|
367
387
|
| git checkout \<ref\> \<path\> | May overwrite working tree when Git disambiguates ref vs pathspec |
|
|
368
388
|
| git restore files | Discards uncommitted changes |
|
|
369
389
|
| git restore --worktree | Explicitly discards working tree changes |
|
|
390
|
+
| git switch --discard-changes | Discards uncommitted changes when switching branches |
|
|
391
|
+
| git switch --force / -f | Discards uncommitted changes (force switch) |
|
|
370
392
|
| git reset --hard | Destroys all uncommitted changes |
|
|
371
393
|
| git reset --merge | Can lose uncommitted changes |
|
|
372
394
|
| git clean -f | Removes untracked files permanently |
|
|
@@ -375,13 +397,16 @@ npx cc-safety-net explain --cwd /tmp "git status"
|
|
|
375
397
|
| git stash drop | Permanently deletes stashed changes |
|
|
376
398
|
| git stash clear | Deletes ALL stashed changes |
|
|
377
399
|
| git worktree remove --force | Force-deletes worktree without checking for changes |
|
|
378
|
-
| rm -rf (
|
|
400
|
+
| rm -rf (destructive targets) | Recursive file deletion of root, home, parent, absolute, or non-temp paths outside cwd |
|
|
379
401
|
| rm -rf / or ~ or $HOME | Root/home deletion is extremely dangerous |
|
|
380
402
|
| find ... -delete | Permanently removes files matching criteria |
|
|
381
403
|
| xargs rm -rf | Dynamic input makes targets unpredictable |
|
|
382
404
|
| xargs \<shell\> -c | Can execute arbitrary commands |
|
|
383
405
|
| parallel rm -rf | Dynamic input makes targets unpredictable |
|
|
384
406
|
| parallel \<shell\> -c | Can execute arbitrary commands |
|
|
407
|
+
| dd writing to block devices | Can overwrite disks or partitions |
|
|
408
|
+
| mkfs on block devices | Formats disks or partitions |
|
|
409
|
+
| shred | Permanently destroys file contents |
|
|
385
410
|
|
|
386
411
|
## Commands Allowed
|
|
387
412
|
|
|
@@ -398,6 +423,7 @@ npx cc-safety-net explain --cwd /tmp "git status"
|
|
|
398
423
|
| rm -rf /var/tmp/... | System temp directory |
|
|
399
424
|
| rm -rf $TMPDIR/... | User's temp directory |
|
|
400
425
|
| rm -rf ./... (within cwd) | Limited to current working directory |
|
|
426
|
+
| git restore / checkout -- / reset --hard / clean -f (in linked worktree) | Relaxed only when `CC_SAFETY_NET_WORKTREE=1` and cwd is a linked worktree |
|
|
401
427
|
|
|
402
428
|
## What Happens When Blocked
|
|
403
429
|
|
|
@@ -405,7 +431,7 @@ When a destructive command is detected, the plugin blocks the tool execution and
|
|
|
405
431
|
|
|
406
432
|
Example output:
|
|
407
433
|
```text
|
|
408
|
-
BLOCKED by Safety Net
|
|
434
|
+
BLOCKED by CC Safety Net
|
|
409
435
|
|
|
410
436
|
Reason: git checkout -- discards uncommitted changes permanently. Use 'git stash' first.
|
|
411
437
|
|
|
@@ -426,29 +452,109 @@ git checkout -- README.md
|
|
|
426
452
|
git checkout -b test-branch
|
|
427
453
|
```
|
|
428
454
|
|
|
429
|
-
##
|
|
455
|
+
## Breaking Change: Custom Rules Migration
|
|
430
456
|
|
|
431
|
-
|
|
457
|
+
> [!WARNING]
|
|
458
|
+
> The custom rules system has moved from legacy inline config files to a rulebook-based layout. Legacy inline config files (`.safety-net.json` and `~/.cc-safety-net/config.json`) are **no longer loaded at runtime**. If they contain rules, commands now **fail closed** (stay blocked) until you migrate.
|
|
459
|
+
|
|
460
|
+
### Who Is Affected
|
|
461
|
+
|
|
462
|
+
- **Affected**: users who previously defined custom rules in `.safety-net.json` (project scope) or `~/.cc-safety-net/config.json` (user scope).
|
|
463
|
+
- **Not affected**: users with no custom rules. All built-in destructive-command protections are unchanged and continue to work out of the box.
|
|
464
|
+
|
|
465
|
+
### What Breaks
|
|
466
|
+
|
|
467
|
+
| Legacy file state | New behavior |
|
|
468
|
+
|-------------------|--------------|
|
|
469
|
+
| Empty legacy file | Silently ignored — built-in rules only |
|
|
470
|
+
| Legacy file with rules | Fail closed until migrated with `rule migrate` |
|
|
471
|
+
| Invalid legacy file | Fail closed until fixed and migrated, or removed |
|
|
472
|
+
|
|
473
|
+
"Fail closed" means commands stay blocked until the legacy rules are migrated to the new layout.
|
|
474
|
+
|
|
475
|
+
### How to Migrate
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
# Convert legacy inline rules into the new rulebook layout
|
|
479
|
+
npx -y cc-safety-net rule migrate
|
|
480
|
+
|
|
481
|
+
# Optionally delete verified legacy files after migration
|
|
482
|
+
npx -y cc-safety-net rule migrate --cleanup
|
|
483
|
+
|
|
484
|
+
# Validate the migrated rules
|
|
485
|
+
npx -y cc-safety-net rule verify
|
|
486
|
+
npx -y cc-safety-net rule test
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Before / After
|
|
490
|
+
|
|
491
|
+
**Before** — a single inline config with rules embedded:
|
|
492
|
+
|
|
493
|
+
```text
|
|
494
|
+
.safety-net.json # project rules (inline)
|
|
495
|
+
~/.cc-safety-net/config.json # user rules (inline)
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
**After** — `rule migrate` creates a rulebook-based layout automatically:
|
|
499
|
+
|
|
500
|
+
```text
|
|
501
|
+
.cc-safety-net/rules/rule.json # project rulebook sources + overrides
|
|
502
|
+
.cc-safety-net/rules/project-rules/rulebook.json # migrated project rules
|
|
503
|
+
~/.cc-safety-net/rules/rule.json # user rulebook sources + overrides
|
|
504
|
+
~/.cc-safety-net/rules/user-rules/rulebook.json # migrated user rules
|
|
505
|
+
```
|
|
432
506
|
|
|
433
|
-
|
|
507
|
+
See [Custom Rules](#custom-rules) for the full authoring guide and [Error Handling](#error-handling) for fail-closed details.
|
|
508
|
+
|
|
509
|
+
## Custom Rules
|
|
434
510
|
|
|
435
511
|
Beyond the built-in protections, you can define your own blocking rules to enforce team conventions or project-specific safety policies.
|
|
436
512
|
|
|
437
513
|
> [!TIP]
|
|
438
|
-
>
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
514
|
+
> The best way to create custom rules is to use the `/cc-safety-net` skill to create custom rules interactively with natural language.
|
|
515
|
+
|
|
516
|
+
### Examples
|
|
517
|
+
|
|
518
|
+
```
|
|
519
|
+
/cc-safety-net read my package.json and suggest blocking rules
|
|
520
|
+
/cc-safety-net set up rules to block all terraform destroy commands
|
|
521
|
+
/cc-safety-net verify my rules and fix any errors
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
> [!NOTE]
|
|
525
|
+
> If your agent does not support skills, prompt it with:
|
|
526
|
+
> ```
|
|
527
|
+
> run npx -y cc-safety-net rule doc and help me set up custom rules
|
|
443
528
|
> ```
|
|
444
529
|
|
|
445
|
-
###
|
|
530
|
+
### Create Rules Manually
|
|
446
531
|
|
|
447
|
-
Create
|
|
532
|
+
Create a starter project rule config and rulebook:
|
|
533
|
+
|
|
534
|
+
```bash
|
|
535
|
+
npx -y cc-safety-net rule init
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
This creates `.cc-safety-net/rules/rule.json`:
|
|
448
539
|
|
|
449
540
|
```json
|
|
450
541
|
{
|
|
451
542
|
"version": 1,
|
|
543
|
+
"rules": ["project-rules"],
|
|
544
|
+
"overrides": {}
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Rule definitions live in `.cc-safety-net/rules/project-rules/rulebook.json`:
|
|
549
|
+
|
|
550
|
+
```json
|
|
551
|
+
{
|
|
552
|
+
"rulebook_version": 1,
|
|
553
|
+
"name": "project-rules",
|
|
554
|
+
"version": "1.0.0",
|
|
555
|
+
"description": "Project-specific CC Safety Net rules.",
|
|
556
|
+
"author": "project",
|
|
557
|
+
"allowed_commands": ["git"],
|
|
452
558
|
"rules": [
|
|
453
559
|
{
|
|
454
560
|
"name": "block-git-add-all",
|
|
@@ -457,25 +563,48 @@ Create `.safety-net.json` in your project root:
|
|
|
457
563
|
"block_args": ["-A", "--all", "."],
|
|
458
564
|
"reason": "Use 'git add <specific-files>' instead of blanket add."
|
|
459
565
|
}
|
|
566
|
+
],
|
|
567
|
+
"tests": [
|
|
568
|
+
{
|
|
569
|
+
"command": "git add -A",
|
|
570
|
+
"expect": "blocked",
|
|
571
|
+
"rule": "block-git-add-all"
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
"command": "git add README.md",
|
|
575
|
+
"expect": "allowed"
|
|
576
|
+
}
|
|
460
577
|
]
|
|
461
578
|
}
|
|
462
579
|
```
|
|
463
580
|
|
|
581
|
+
After editing rulebooks, run:
|
|
582
|
+
|
|
583
|
+
```bash
|
|
584
|
+
npx -y cc-safety-net rule sync
|
|
585
|
+
npx -y cc-safety-net rule verify
|
|
586
|
+
npx -y cc-safety-net rule test
|
|
587
|
+
```
|
|
588
|
+
|
|
464
589
|
Now `git add -A`, `git add --all`, and `git add .` will be blocked with your custom message.
|
|
465
590
|
|
|
466
591
|
### Config File Location
|
|
467
592
|
|
|
468
593
|
Config files are loaded from two scopes and merged:
|
|
469
594
|
|
|
470
|
-
1. **User scope**: `~/.cc-safety-net/
|
|
471
|
-
2. **Project scope**: `.safety-net.json` in the current working directory
|
|
595
|
+
1. **User scope**: `~/.cc-safety-net/rules/rule.json` (use `rule init --global`)
|
|
596
|
+
2. **Project scope**: `.cc-safety-net/rules/rule.json` in the current working directory
|
|
597
|
+
|
|
598
|
+
Local rulebook sources are bare names like `project-rules`. GitHub rulebook sources use `owner/repo#ref/<rulebook-name>` and point to `.cc-safety-net/rules/<rulebook-name>/rulebook.json` in that repository.
|
|
599
|
+
|
|
600
|
+
Legacy inline config files (`.safety-net.json` and `~/.cc-safety-net/config.json`) are no longer loaded at runtime. Empty legacy files are ignored, but legacy files with rules and invalid legacy files fail closed until migrated or fixed. Convert existing legacy rules with `npx -y cc-safety-net rule migrate`; use `npx -y cc-safety-net rule migrate --cleanup` if you also want to delete verified legacy files after migration. See [Breaking Change: Custom Rules Migration](#breaking-change-custom-rules-migration) for the full upgrade guide.
|
|
472
601
|
|
|
473
602
|
**Merging behavior**:
|
|
474
|
-
-
|
|
475
|
-
-
|
|
476
|
-
-
|
|
603
|
+
- Rulebooks from both scopes are combined
|
|
604
|
+
- Duplicate active rulebook names are invalid
|
|
605
|
+
- Project overrides win over user overrides for the same `<rulebook-name>/<rule-name>` key
|
|
477
606
|
|
|
478
|
-
This allows you to define personal defaults in user scope while letting projects
|
|
607
|
+
This allows you to define personal defaults in user scope while letting projects disable or replace reasons for specific rules.
|
|
479
608
|
|
|
480
609
|
If no config file is found in either location, only built-in rules apply.
|
|
481
610
|
|
|
@@ -484,18 +613,44 @@ If no config file is found in either location, only built-in rules apply.
|
|
|
484
613
|
| Field | Type | Required | Description |
|
|
485
614
|
|-------|------|----------|-------------|
|
|
486
615
|
| `version` | integer | Yes | Schema version (must be `1`) |
|
|
487
|
-
| `rules` | array | No | List of
|
|
616
|
+
| `rules` | array | No | List of rulebook source strings (defaults to empty) |
|
|
617
|
+
| `overrides` | object | No | Rule overrides keyed by `<rulebook-name>/<rule-name>` |
|
|
618
|
+
|
|
619
|
+
Override values are either `"off"` to disable a rule or `{ "reason": "..." }` to replace the rule reason.
|
|
620
|
+
|
|
621
|
+
### Rulebook Schema
|
|
622
|
+
|
|
623
|
+
| Field | Type | Required | Description |
|
|
624
|
+
|-------|------|----------|-------------|
|
|
625
|
+
| `rulebook_version` | integer | Yes | Rulebook schema version (must be `1`) |
|
|
626
|
+
| `name` | string | Yes | Rulebook name; must match the local directory name or GitHub source name |
|
|
627
|
+
| `version` | string | Yes | Rulebook version |
|
|
628
|
+
| `description` | string | No | Human-readable description |
|
|
629
|
+
| `author` | string | No | Rulebook author |
|
|
630
|
+
| `allowed_commands` | array | Yes | Commands this rulebook is allowed to define rules for |
|
|
631
|
+
| `rules` | array | Yes | Custom blocking rules |
|
|
632
|
+
| `tests` | array | Yes | Rulebook fixtures |
|
|
488
633
|
|
|
489
634
|
### Rule Schema
|
|
490
635
|
|
|
491
636
|
| Field | Type | Required | Description |
|
|
492
637
|
|-------|------|----------|-------------|
|
|
493
|
-
| `name` | string | Yes | Unique
|
|
494
|
-
| `command` | string | Yes | Base command to match
|
|
638
|
+
| `name` | string | Yes | Unique within the rulebook (letters, numbers, hyphens, underscores; max 64 chars) |
|
|
639
|
+
| `command` | string | Yes | Base command to match; must be listed in `allowed_commands` |
|
|
495
640
|
| `subcommand` | string | No | Subcommand to match (e.g., `add`, `install`). If omitted, matches any. |
|
|
496
641
|
| `block_args` | array | Yes | Arguments that trigger the block (at least one required) |
|
|
497
642
|
| `reason` | string | Yes | Message shown when blocked (max 256 chars) |
|
|
498
643
|
|
|
644
|
+
### Fixture Schema
|
|
645
|
+
|
|
646
|
+
| Field | Type | Required | Description |
|
|
647
|
+
|-------|------|----------|-------------|
|
|
648
|
+
| `command` | string | Yes | Shell command fixture |
|
|
649
|
+
| `expect` | string | Yes | Either `blocked` or `allowed` |
|
|
650
|
+
| `rule` | string | For blocked fixtures | Rule expected to block the command |
|
|
651
|
+
|
|
652
|
+
Every rule must have at least one blocked fixture. Add allowed fixtures for close-but-safe commands.
|
|
653
|
+
|
|
499
654
|
### Matching Behavior
|
|
500
655
|
|
|
501
656
|
- **Commands** are normalized to basename (`/usr/bin/git` → `git`)
|
|
@@ -510,13 +665,28 @@ If no config file is found in either location, only built-in rules apply.
|
|
|
510
665
|
|
|
511
666
|
- **Short option expansion**: `-Cfoo` is treated as `-C -f -o -o`, not `-C foo`. Blocking `-f` may false-positive on attached option values.
|
|
512
667
|
|
|
513
|
-
### Examples
|
|
668
|
+
### Rule Examples
|
|
514
669
|
|
|
515
670
|
#### Block global npm installs
|
|
516
671
|
|
|
672
|
+
`.cc-safety-net/rules/rule.json`:
|
|
673
|
+
|
|
517
674
|
```json
|
|
518
675
|
{
|
|
519
676
|
"version": 1,
|
|
677
|
+
"rules": ["project-rules"],
|
|
678
|
+
"overrides": {}
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
`.cc-safety-net/rules/project-rules/rulebook.json`:
|
|
683
|
+
|
|
684
|
+
```json
|
|
685
|
+
{
|
|
686
|
+
"rulebook_version": 1,
|
|
687
|
+
"name": "project-rules",
|
|
688
|
+
"version": "1.0.0",
|
|
689
|
+
"allowed_commands": ["npm"],
|
|
520
690
|
"rules": [
|
|
521
691
|
{
|
|
522
692
|
"name": "block-npm-global",
|
|
@@ -525,6 +695,17 @@ If no config file is found in either location, only built-in rules apply.
|
|
|
525
695
|
"block_args": ["-g", "--global"],
|
|
526
696
|
"reason": "Global npm installs can cause version conflicts. Use npx or local install."
|
|
527
697
|
}
|
|
698
|
+
],
|
|
699
|
+
"tests": [
|
|
700
|
+
{
|
|
701
|
+
"command": "npm install -g typescript",
|
|
702
|
+
"expect": "blocked",
|
|
703
|
+
"rule": "block-npm-global"
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
"command": "npm install typescript",
|
|
707
|
+
"expect": "allowed"
|
|
708
|
+
}
|
|
528
709
|
]
|
|
529
710
|
}
|
|
530
711
|
```
|
|
@@ -533,7 +714,10 @@ If no config file is found in either location, only built-in rules apply.
|
|
|
533
714
|
|
|
534
715
|
```json
|
|
535
716
|
{
|
|
536
|
-
"
|
|
717
|
+
"rulebook_version": 1,
|
|
718
|
+
"name": "project-rules",
|
|
719
|
+
"version": "1.0.0",
|
|
720
|
+
"allowed_commands": ["docker"],
|
|
537
721
|
"rules": [
|
|
538
722
|
{
|
|
539
723
|
"name": "block-docker-system-prune",
|
|
@@ -542,6 +726,17 @@ If no config file is found in either location, only built-in rules apply.
|
|
|
542
726
|
"block_args": ["prune"],
|
|
543
727
|
"reason": "docker system prune removes all unused data. Use targeted cleanup instead."
|
|
544
728
|
}
|
|
729
|
+
],
|
|
730
|
+
"tests": [
|
|
731
|
+
{
|
|
732
|
+
"command": "docker system prune",
|
|
733
|
+
"expect": "blocked",
|
|
734
|
+
"rule": "block-docker-system-prune"
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
"command": "docker ps",
|
|
738
|
+
"expect": "allowed"
|
|
739
|
+
}
|
|
545
740
|
]
|
|
546
741
|
}
|
|
547
742
|
```
|
|
@@ -550,7 +745,10 @@ If no config file is found in either location, only built-in rules apply.
|
|
|
550
745
|
|
|
551
746
|
```json
|
|
552
747
|
{
|
|
553
|
-
"
|
|
748
|
+
"rulebook_version": 1,
|
|
749
|
+
"name": "project-rules",
|
|
750
|
+
"version": "1.0.0",
|
|
751
|
+
"allowed_commands": ["git", "npm"],
|
|
554
752
|
"rules": [
|
|
555
753
|
{
|
|
556
754
|
"name": "block-git-add-all",
|
|
@@ -566,33 +764,47 @@ If no config file is found in either location, only built-in rules apply.
|
|
|
566
764
|
"block_args": ["-g", "--global"],
|
|
567
765
|
"reason": "Use npx or local install instead of global."
|
|
568
766
|
}
|
|
767
|
+
],
|
|
768
|
+
"tests": [
|
|
769
|
+
{
|
|
770
|
+
"command": "git add -A",
|
|
771
|
+
"expect": "blocked",
|
|
772
|
+
"rule": "block-git-add-all"
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
"command": "npm install -g typescript",
|
|
776
|
+
"expect": "blocked",
|
|
777
|
+
"rule": "block-npm-global"
|
|
778
|
+
}
|
|
569
779
|
]
|
|
570
780
|
}
|
|
571
781
|
```
|
|
572
782
|
|
|
573
783
|
### Error Handling
|
|
574
784
|
|
|
575
|
-
|
|
785
|
+
Rulebook-backed custom rules fail closed when configured rulebooks cannot be loaded safely:
|
|
576
786
|
|
|
577
787
|
| Scenario | Behavior |
|
|
578
788
|
|----------|----------|
|
|
579
789
|
| Config file not found | Silent — use built-in rules only |
|
|
580
|
-
|
|
|
581
|
-
|
|
|
582
|
-
|
|
|
583
|
-
| Invalid
|
|
584
|
-
|
|
|
790
|
+
| Invalid rule config | Fail closed until fixed |
|
|
791
|
+
| Empty legacy config | Silent — use built-in rules only |
|
|
792
|
+
| Legacy config with rules and no migrated rule config | Fail closed until `rule migrate` creates the new rule config |
|
|
793
|
+
| Invalid legacy config | Fail closed until fixed or removed |
|
|
794
|
+
| Missing or stale lock/cache | Fail closed until `rule sync` repairs it |
|
|
795
|
+
| Invalid local rulebook | Fail closed until the rulebook is fixed and synced |
|
|
796
|
+
| Invalid GitHub rulebook | Fail closed until the source is fixed or removed |
|
|
585
797
|
|
|
586
798
|
|
|
587
799
|
> [!IMPORTANT]
|
|
588
|
-
> If you add or modify custom rules manually, always validate them with `npx -y cc-safety-net
|
|
800
|
+
> If you add or modify custom rules manually, always validate them with `npx -y cc-safety-net rule verify` and `npx -y cc-safety-net rule test`.
|
|
589
801
|
|
|
590
802
|
### Block Output Format
|
|
591
803
|
|
|
592
804
|
When a custom rule blocks a command, the output includes the rule name:
|
|
593
805
|
|
|
594
806
|
```text
|
|
595
|
-
BLOCKED by Safety Net
|
|
807
|
+
BLOCKED by CC Safety Net
|
|
596
808
|
|
|
597
809
|
Reason: [block-git-add-all] Use 'git add <specific-files>' instead of blanket add.
|
|
598
810
|
|
|
@@ -601,14 +813,17 @@ Command: git add -A
|
|
|
601
813
|
|
|
602
814
|
## Advanced Features
|
|
603
815
|
|
|
816
|
+
Mode and debug flags use **`CC_SAFETY_NET_*`** environment variables. Older **`SAFETY_NET_*`** names (without the `CC_` prefix) still work for strict, paranoid, and worktree toggles.
|
|
817
|
+
|
|
604
818
|
### Strict Mode
|
|
605
819
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
unterminated quotes
|
|
820
|
+
Malformed or missing hook input JSON always fails closed. By default, ambiguous shell
|
|
821
|
+
command parsing is allowed through. Enable strict mode to fail closed when a shell
|
|
822
|
+
command cannot be safely analyzed (e.g., unterminated quotes or malformed `bash -c`
|
|
823
|
+
wrappers):
|
|
609
824
|
|
|
610
825
|
```bash
|
|
611
|
-
export
|
|
826
|
+
export CC_SAFETY_NET_STRICT=1
|
|
612
827
|
```
|
|
613
828
|
|
|
614
829
|
### Paranoid Mode
|
|
@@ -618,11 +833,11 @@ You can enable it globally or via focused toggles:
|
|
|
618
833
|
|
|
619
834
|
```bash
|
|
620
835
|
# Enable all paranoid checks
|
|
621
|
-
export
|
|
836
|
+
export CC_SAFETY_NET_PARANOID=1
|
|
622
837
|
|
|
623
838
|
# Or enable specific paranoid checks
|
|
624
|
-
export
|
|
625
|
-
export
|
|
839
|
+
export CC_SAFETY_NET_PARANOID_RM=1
|
|
840
|
+
export CC_SAFETY_NET_PARANOID_INTERPRETERS=1
|
|
626
841
|
```
|
|
627
842
|
|
|
628
843
|
Paranoid behavior:
|
|
@@ -631,6 +846,48 @@ Paranoid behavior:
|
|
|
631
846
|
- **interpreters**: blocks interpreter one-liners like `python -c`, `node -e`, `ruby -e`,
|
|
632
847
|
and `perl -e` (these can hide destructive commands).
|
|
633
848
|
|
|
849
|
+
### Worktree Mode
|
|
850
|
+
|
|
851
|
+
Linked git worktrees are designed as disposable, isolated workspaces — discarding
|
|
852
|
+
changes inside one doesn't risk the main working tree. Worktree mode relaxes
|
|
853
|
+
local-discard rules when (and only when) the command is proven to run inside a
|
|
854
|
+
linked worktree:
|
|
855
|
+
|
|
856
|
+
```bash
|
|
857
|
+
export CC_SAFETY_NET_WORKTREE=1
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
When enabled, these commands are allowed inside a linked worktree:
|
|
861
|
+
|
|
862
|
+
- `git restore <file>` and `git restore --worktree <file>`
|
|
863
|
+
- `git checkout -- <file>`, `git checkout <ref> -- <file>`, `git checkout --force`,
|
|
864
|
+
and ambiguous multi-positional checkout forms
|
|
865
|
+
- `git switch --discard-changes` and `git switch -f / --force`
|
|
866
|
+
- `git reset --hard` and `git reset --merge`
|
|
867
|
+
- `git clean -f` (and combined short flags like `-fd`)
|
|
868
|
+
|
|
869
|
+
These remain blocked even in linked worktrees because they reach beyond the
|
|
870
|
+
local working tree:
|
|
871
|
+
|
|
872
|
+
- `git push --force` (affects remote)
|
|
873
|
+
- `git branch -D` (affects shared refs)
|
|
874
|
+
- `git stash drop` / `git stash clear` (stash is shared across worktrees)
|
|
875
|
+
- `git worktree remove --force` (could delete another worktree)
|
|
876
|
+
|
|
877
|
+
Detection is fail-closed and mostly filesystem-based:
|
|
878
|
+
|
|
879
|
+
- A linked worktree is identified by a `.git` *file* containing `gitdir:` whose
|
|
880
|
+
resolved git directory contains a `commondir` file. Main worktrees and
|
|
881
|
+
submodules don't satisfy this and are not relaxed.
|
|
882
|
+
- The cwd walk uses `realpath` so symlinked paths resolve correctly.
|
|
883
|
+
- `git -C <path>` (including chained `-C` and attached `-Cpath`) is honored;
|
|
884
|
+
unresolved targets keep the command blocked.
|
|
885
|
+
- Relaxation is disabled if cwd becomes unknown (e.g., after `cd`/`pushd`),
|
|
886
|
+
if `--git-dir` / `--work-tree` is passed, or if `GIT_DIR` / `GIT_WORK_TREE`
|
|
887
|
+
/ `GIT_COMMON_DIR` is set in the environment.
|
|
888
|
+
- Git may be invoked from a trusted system path to inspect effective config that
|
|
889
|
+
could make submodule operations recursive.
|
|
890
|
+
|
|
634
891
|
### Shell Wrapper Detection
|
|
635
892
|
|
|
636
893
|
The guard recursively analyzes commands wrapped in shells:
|
|
@@ -638,6 +895,7 @@ The guard recursively analyzes commands wrapped in shells:
|
|
|
638
895
|
```bash
|
|
639
896
|
bash -c 'git reset --hard' # Blocked
|
|
640
897
|
sh -lc 'rm -rf /' # Blocked
|
|
898
|
+
bash -c 'git stash drop' # Blocked
|
|
641
899
|
```
|
|
642
900
|
|
|
643
901
|
### Interpreter One-Liner Detection
|
|
@@ -646,6 +904,10 @@ Detects destructive commands hidden in Python/Node/Ruby/Perl one-liners:
|
|
|
646
904
|
|
|
647
905
|
```bash
|
|
648
906
|
python -c 'import os; os.system("rm -rf /")' # Blocked
|
|
907
|
+
python -c 'import os; os.system("git stash drop")' # Blocked
|
|
908
|
+
python -c 'import os; os.system("dd if=/dev/zero of=/dev/sda")' # Blocked
|
|
909
|
+
python -c 'import os; os.system("mkfs.ext4 /dev/sda1")' # Blocked
|
|
910
|
+
python -c 'import os; os.system("shred -u secret.txt")' # Blocked
|
|
649
911
|
```
|
|
650
912
|
|
|
651
913
|
### Secret Redaction
|
|
@@ -662,6 +924,10 @@ All blocked commands are logged to `~/.cc-safety-net/logs/<session_id>.jsonl` fo
|
|
|
662
924
|
|
|
663
925
|
Sensitive data in log entries is automatically redacted.
|
|
664
926
|
|
|
927
|
+
## Development
|
|
928
|
+
|
|
929
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to this project.
|
|
930
|
+
|
|
665
931
|
## License
|
|
666
932
|
|
|
667
933
|
MIT
|