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.
Files changed (97) hide show
  1. package/README.md +380 -114
  2. package/dist/bin/cc-safety-net.js +8565 -3694
  3. package/dist/bin/commands/doctor.d.ts +11 -2
  4. package/dist/bin/commands/explain.d.ts +16 -2
  5. package/dist/bin/commands/hook.d.ts +14 -0
  6. package/dist/bin/commands/index.d.ts +67 -3
  7. package/dist/bin/commands/rule.d.ts +14 -0
  8. package/dist/bin/commands/statusline.d.ts +10 -2
  9. package/dist/bin/commands/types.d.ts +11 -0
  10. package/dist/bin/doctor/config.d.ts +1 -1
  11. package/dist/bin/doctor/hooks.d.ts +4 -1
  12. package/dist/bin/doctor/system-info.d.ts +6 -2
  13. package/dist/bin/doctor/types.d.ts +30 -1
  14. package/dist/bin/explain/config.d.ts +3 -1
  15. package/dist/bin/hook/common.d.ts +20 -0
  16. package/dist/bin/hook/config-edit.d.ts +11 -0
  17. package/dist/bin/hook/constants.d.ts +6 -0
  18. package/dist/bin/hook/install/kimi-cli.d.ts +3 -0
  19. package/dist/bin/hook/install/types.d.ts +4 -0
  20. package/dist/bin/hook/install.d.ts +3 -0
  21. package/dist/bin/hook/integrations.d.ts +12 -0
  22. package/dist/bin/hook/kimi-cli.d.ts +1 -0
  23. package/dist/bin/integration-metadata.d.ts +68 -0
  24. package/dist/bin/rule/doc.d.ts +1 -0
  25. package/dist/bin/rule/format.d.ts +21 -0
  26. package/dist/bin/rule/index.d.ts +1 -0
  27. package/dist/bin/rule/migrate.d.ts +6 -0
  28. package/dist/bin/rule/verify.d.ts +9 -0
  29. package/dist/builtin-commands/templates/cc-safety-net.d.ts +1 -0
  30. package/dist/core/analyze/awk.d.ts +3 -0
  31. package/dist/core/analyze/child-analyzer.d.ts +16 -0
  32. package/dist/core/analyze/child-command.d.ts +15 -0
  33. package/dist/core/analyze/find.d.ts +8 -6
  34. package/dist/core/analyze/index.d.ts +4 -0
  35. package/dist/core/analyze/parallel.d.ts +4 -1
  36. package/dist/core/{rules-rm.d.ts → analyze/rm.d.ts} +0 -1
  37. package/dist/core/analyze/segment.d.ts +2 -2
  38. package/dist/core/analyze/shell-git-env.d.ts +10 -0
  39. package/dist/core/analyze/xargs.d.ts +2 -1
  40. package/dist/core/audit.d.ts +3 -0
  41. package/dist/core/config.d.ts +5 -3
  42. package/dist/core/env.d.ts +39 -1
  43. package/dist/core/format.d.ts +1 -0
  44. package/dist/core/git/config.d.ts +1 -0
  45. package/dist/core/git/env.d.ts +12 -0
  46. package/dist/core/git/index.d.ts +3 -0
  47. package/dist/core/git/parse.d.ts +9 -0
  48. package/dist/core/git/rules.d.ts +8 -0
  49. package/dist/core/git/worktree-relaxation.d.ts +11 -0
  50. package/dist/core/git/worktree.d.ts +11 -0
  51. package/dist/core/path.d.ts +1 -0
  52. package/dist/core/rules/custom-rule-validation.d.ts +5 -0
  53. package/dist/core/rules/policy/config-file.d.ts +19 -0
  54. package/dist/core/rules/policy/index.d.ts +6 -0
  55. package/dist/core/rules/policy/lockfile.d.ts +5 -0
  56. package/dist/core/rules/policy/paths.d.ts +31 -0
  57. package/dist/core/rules/policy/resolver.d.ts +16 -0
  58. package/dist/core/rules/policy/scope-policy.d.ts +18 -0
  59. package/dist/core/rules/policy/sources.d.ts +28 -0
  60. package/dist/core/rules/policy/sync.d.ts +10 -0
  61. package/dist/core/rules/policy/types.d.ts +77 -0
  62. package/dist/core/rules/rulebook.d.ts +28 -0
  63. package/dist/core/shell/command.d.ts +2 -0
  64. package/dist/core/shell/index.d.ts +4 -0
  65. package/dist/core/shell/options.d.ts +3 -0
  66. package/dist/core/shell/segments.d.ts +6 -0
  67. package/dist/core/shell/shared.d.ts +4 -0
  68. package/dist/core/shell/wrappers.d.ts +17 -0
  69. package/dist/index.d.ts +1 -1
  70. package/dist/index.js +4880 -1578
  71. package/dist/{features → opencode}/builtin-commands/commands.d.ts +1 -1
  72. package/dist/opencode/builtin-commands/index.d.ts +2 -0
  73. package/dist/{features → opencode}/builtin-commands/types.d.ts +1 -1
  74. package/dist/pi/builtin-commands/commands.d.ts +15 -0
  75. package/dist/pi/builtin-commands/index.d.ts +1 -0
  76. package/dist/pi/index.d.ts +5 -0
  77. package/dist/pi/index.js +6405 -0
  78. package/dist/pi/tool-use.d.ts +20 -0
  79. package/dist/types.d.ts +45 -2
  80. package/package.json +16 -8
  81. package/dist/bin/commands/claude-code.d.ts +0 -2
  82. package/dist/bin/commands/copilot-cli.d.ts +0 -2
  83. package/dist/bin/commands/custom-rules-doc.d.ts +0 -2
  84. package/dist/bin/commands/gemini-cli.d.ts +0 -2
  85. package/dist/bin/commands/verify-config.d.ts +0 -2
  86. package/dist/bin/custom-rules-doc.d.ts +0 -1
  87. package/dist/bin/verify-config.d.ts +0 -12
  88. package/dist/core/analyze.d.ts +0 -21
  89. package/dist/core/rules-git.d.ts +0 -8
  90. package/dist/core/shell.d.ts +0 -17
  91. package/dist/features/builtin-commands/index.d.ts +0 -2
  92. package/dist/features/builtin-commands/templates/set-custom-rules.d.ts +0 -1
  93. package/dist/features/builtin-commands/templates/verify-custom-rules.d.ts +0 -1
  94. /package/dist/bin/{hooks → hook}/claude-code.d.ts +0 -0
  95. /package/dist/bin/{hooks → hook}/copilot-cli.d.ts +0 -0
  96. /package/dist/bin/{hooks → hook}/gemini-cli.d.ts +0 -0
  97. /package/dist/core/{rules-custom.d.ts → rules/custom.d.ts} +0 -0
package/README.md CHANGED
@@ -1,12 +1,15 @@
1
- # Claude Code Safety Net
1
+ # CC Safety Net
2
2
 
3
- [![CI](https://github.com/kenryu42/claude-code-safety-net/actions/workflows/ci.yml/badge.svg)](https://github.com/kenryu42/claude-code-safety-net/actions/workflows/ci.yml)
4
- [![codecov](https://codecov.io/github/kenryu42/claude-code-safety-net/branch/main/graph/badge.svg?token=C9QTION6ZF)](https://codecov.io/github/kenryu42/claude-code-safety-net)
5
- [![Version](https://img.shields.io/github/v/tag/kenryu42/claude-code-safety-net?label=version&color=blue)](https://github.com/kenryu42/claude-code-safety-net)
3
+ [![CI](https://github.com/kenryu42/cc-safety-net/actions/workflows/ci.yml/badge.svg)](https://github.com/kenryu42/cc-safety-net/actions/workflows/ci.yml)
4
+ [![codecov](https://codecov.io/github/kenryu42/cc-safety-net/branch/main/graph/badge.svg?token=C9QTION6ZF)](https://codecov.io/github/kenryu42/cc-safety-net)
5
+ [![Version](https://img.shields.io/github/v/tag/kenryu42/cc-safety-net?label=version&color=blue)](https://github.com/kenryu42/cc-safety-net)
6
+ [![Codex](https://img.shields.io/badge/Codex-white)](#codex-installation)
6
7
  [![Claude Code](https://img.shields.io/badge/Claude%20Code-D27656)](#claude-code-installation)
7
- [![OpenCode](https://img.shields.io/badge/OpenCode-black)](#opencode-installation)
8
- [![Gemini CLI](https://img.shields.io/badge/Gemini%20CLI-678AE3)](#gemini-cli-installation)
9
8
  [![Copilot CLI](https://img.shields.io/badge/Copilot%20CLI-4EA5C9)](#github-copilot-cli-installation)
9
+ [![Gemini CLI](https://img.shields.io/badge/Gemini%20CLI-678AE3)](#gemini-cli-installation)
10
+ [![Kimi CLI](https://img.shields.io/badge/Kimi%20CLI-5587FF)](#kimi-cli-installation)
11
+ [![OpenCode](https://img.shields.io/badge/OpenCode-black)](#opencode-installation)
12
+ [![Pi](https://img.shields.io/badge/Pi%20Coding-22262E)](#pi-installation)
10
13
  [![License: MIT](https://img.shields.io/badge/License-MIT-red.svg)](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 Claude Code plugin that acts as a safety net, catching destructive git and filesystem commands before they execute.
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
- - [Development](#development)
42
- - [Custom Rules (Experimental)](#custom-rules-experimental)
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 (5 levels) |
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
- ### OpenCode Installation
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
- Install the cc-safety-net plugin in `~/.config/opencode/opencode.json` (or `.jsonc`) according to the schema at: https://opencode.ai/config.json
207
+ ```bash
208
+ gemini extensions install https://github.com/kenryu42/gemini-safety-net
184
209
  ```
185
210
 
186
- **Option B: Manual setup**
211
+ ---
187
212
 
188
- 1. **Add the plugin to your config** `~/.config/opencode/opencode.json` (or `.jsonc`):
213
+ ### GitHub Copilot CLI Installation
189
214
 
190
- ```json
191
- {
192
- "plugin": ["cc-safety-net"]
193
- }
194
- ```
215
+ ```bash
216
+ /plugin install kenryu42/copilot-safety-net
217
+ ```
195
218
 
196
219
  ---
197
220
 
198
- ### Gemini CLI Installation
221
+ ### Kimi CLI Installation
222
+
223
+ Install CC Safety Net into your Kimi CLI config:
199
224
 
200
225
  ```bash
201
- gemini extensions install https://github.com/kenryu42/gemini-safety-net
226
+ npx -y cc-safety-net hook install --kimi-cli
202
227
  ```
203
228
 
204
229
  ---
205
230
 
206
- ### GitHub Copilot CLI Installation
231
+
232
+ ### OpenCode Installation
233
+
234
+ Install CC Safety Net with OpenCode's native plugin command:
207
235
 
208
236
  ```bash
209
- /plugin install kenryu42/copilot-safety-net
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
- ## Status Line Integration
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
- The easiest way to configure the status line is using the built-in slash command:
244
+ Install CC Safety Net with Pi's package installer:
224
245
 
225
- ```
226
- /set-statusline
246
+ ```bash
247
+ pi install npm:cc-safety-net
227
248
  ```
228
249
 
229
- This interactive command will:
230
- 1. Ask whether you prefer `bunx` or `npx`
231
- 2. Check for existing status line configuration
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
- ### Manual Setup
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 --statusline"
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 --statusline"
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 --statusline"
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 --statusline"
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 🔒` | `SAFETY_NET_STRICT=1` — fail-closed on unparseable commands |
300
- | Paranoid mode | `🛡️ Safety Net 👁️` | `SAFETY_NET_PARANOID=1` — all paranoid checks enabled |
301
- | Paranoid RM only | `🛡️ Safety Net 🗑️` | `SAFETY_NET_PARANOID_RM=1` — blocks `rm -rf` even within cwd |
302
- | Paranoid interpreters only | `🛡️ Safety Net 🐚` | `SAFETY_NET_PARANOID_INTERPRETERS=1` — blocks interpreter one-liners |
303
- | Strict + Paranoid | `🛡️ Safety Net 🔒👁️` | Both strict and paranoid modes enabled |
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 (SAFETY_NET_STRICT, SAFETY_NET_PARANOID, etc.) |
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 (paths outside cwd) | Recursive file deletion outside the current directory |
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
- ## Development
455
+ ## Breaking Change: Custom Rules Migration
430
456
 
431
- See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to this project.
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
- ## Custom Rules (Experimental)
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
- > Use `/set-custom-rules` to create custom rules interactively with natural language.
439
- >
440
- > **GitHub Copilot CLI users**: Since Copilot CLI doesn't support custom slash commands, prompt your agent with:
441
- > ```text
442
- > run npx cc-safety-net --custom-rules-doc and help me set up custom rules
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
- ### Quick Example
530
+ ### Create Rules Manually
446
531
 
447
- Create `.safety-net.json` in your project root:
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/config.json` (always loaded if exists)
471
- 2. **Project scope**: `.safety-net.json` in the current working directory (loaded if exists)
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
- - Rules from both scopes are combined
475
- - If the same rule name exists in both scopes, **project scope wins**
476
- - Rule name comparison is case-insensitive (`MyRule` and `myrule` are considered duplicates)
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 override specific rules.
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 custom blocking rules (defaults to empty) |
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 identifier (letters, numbers, hyphens, underscores; max 64 chars) |
494
- | `command` | string | Yes | Base command to match (e.g., `git`, `npm`, `docker`) |
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
- "version": 1,
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
- "version": 1,
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
- Custom rules use **silent fallback** error handling. If your config file is invalid, the safety net silently falls back to built-in rules only:
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
- | Empty config file | Silent use built-in rules only |
581
- | Invalid JSON syntax | Silent — use built-in rules only |
582
- | Missing required field | Silent use built-in rules only |
583
- | Invalid field format | Silent use built-in rules only |
584
- | Duplicate rule name | Silent use built-in rules only |
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 --verify-config` or `/verify-custom-rules` slash command in your coding agent.
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
- By default, unparseable commands are allowed through. Enable strict mode to fail-closed
607
- when the hook input or shell command cannot be safely analyzed (e.g., invalid JSON,
608
- unterminated quotes, malformed `bash -c` wrappers):
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 SAFETY_NET_STRICT=1
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 SAFETY_NET_PARANOID=1
836
+ export CC_SAFETY_NET_PARANOID=1
622
837
 
623
838
  # Or enable specific paranoid checks
624
- export SAFETY_NET_PARANOID_RM=1
625
- export SAFETY_NET_PARANOID_INTERPRETERS=1
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