cc-safety-net 0.8.0 → 0.8.2
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 +6 -89
- package/dist/bin/cc-safety-net.js +36 -44
- package/dist/bin/doctor/hooks.d.ts +1 -0
- package/dist/bin/doctor/types.d.ts +2 -0
- package/dist/core/rules-rm.d.ts +1 -0
- package/dist/index.js +10 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -164,11 +164,9 @@ Running both together provides defense-in-depth. Sandboxing handles unknown thre
|
|
|
164
164
|
```bash
|
|
165
165
|
/plugin marketplace add kenryu42/cc-marketplace
|
|
166
166
|
/plugin install safety-net@cc-marketplace
|
|
167
|
+
/reload-plugins
|
|
167
168
|
```
|
|
168
169
|
|
|
169
|
-
> [!NOTE]
|
|
170
|
-
> After installing the plugin, you need to restart your Claude Code for it to take effect.
|
|
171
|
-
|
|
172
170
|
### Claude Code Auto-Update
|
|
173
171
|
|
|
174
172
|
1. Run `/plugin` → Select `Marketplaces` → Choose `cc-marketplace` → Enable auto-update
|
|
@@ -207,95 +205,14 @@ gemini extensions install https://github.com/kenryu42/gemini-safety-net
|
|
|
207
205
|
|
|
208
206
|
### GitHub Copilot CLI Installation
|
|
209
207
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
> [!NOTE]
|
|
213
|
-
> Copilot CLI currently supports two hook configuration styles:
|
|
214
|
-
>
|
|
215
|
-
> - Hook files:
|
|
216
|
-
> - repository: `.github/hooks/*.json`
|
|
217
|
-
> - user: `~/.copilot/hooks/*.json` on Copilot CLI `0.0.422+`
|
|
218
|
-
> - Inline `hooks` inside Copilot config files on Copilot CLI `1.0.8+`:
|
|
219
|
-
> - user: `~/.copilot/config.json`
|
|
220
|
-
> - repository: `.github/copilot/settings.json`
|
|
221
|
-
> - local override: `.github/copilot/settings.local.json`
|
|
222
|
-
>
|
|
223
|
-
> Copilot settings cascade from user -> repository -> local (later files override earlier ones, so local overrides repository overrides user). `disableAllHooks: true` disables both repo-level and user-level hooks. If you use `COPILOT_HOME`, replace `~/.copilot` with that directory.
|
|
224
|
-
|
|
225
|
-
#### Option A: Hook Files
|
|
226
|
-
|
|
227
|
-
This is the classic hook-file format. It still works, and it is the easiest shared setup for a repository.
|
|
228
|
-
|
|
229
|
-
1. **Create the hooks directory** in your repository:
|
|
230
|
-
|
|
231
|
-
```bash
|
|
232
|
-
mkdir -p .github/hooks
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
2. **Create `.github/hooks/safety-net.json`**:
|
|
236
|
-
|
|
237
|
-
```json
|
|
238
|
-
{
|
|
239
|
-
"version": 1,
|
|
240
|
-
"hooks": {
|
|
241
|
-
"preToolUse": [
|
|
242
|
-
{
|
|
243
|
-
"type": "command",
|
|
244
|
-
"bash": "npx -y cc-safety-net --copilot-cli",
|
|
245
|
-
"cwd": ".",
|
|
246
|
-
"timeoutSec": 15
|
|
247
|
-
}
|
|
248
|
-
]
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
3. **Restart Copilot CLI** — hooks are loaded at session start.
|
|
254
|
-
|
|
255
|
-
The hook will intercept shell commands and block destructive operations before they execute.
|
|
256
|
-
|
|
257
|
-
To install the same hook globally for your user account on Copilot CLI `0.0.422+`, place the same JSON file in:
|
|
258
|
-
|
|
259
|
-
- `~/.copilot/hooks/safety-net.json`
|
|
260
|
-
|
|
261
|
-
#### Option B: Inline Hooks In Copilot Settings
|
|
262
|
-
|
|
263
|
-
On Copilot CLI `1.0.8+`, you can define the same hook inline in Copilot settings files instead of a separate `.json` file under `.github/hooks` or `~/.copilot/hooks`.
|
|
264
|
-
|
|
265
|
-
```json
|
|
266
|
-
{
|
|
267
|
-
"hooks": {
|
|
268
|
-
"preToolUse": [
|
|
269
|
-
{
|
|
270
|
-
"type": "command",
|
|
271
|
-
"bash": "npx -y cc-safety-net --copilot-cli",
|
|
272
|
-
"cwd": ".",
|
|
273
|
-
"timeoutSec": 15
|
|
274
|
-
}
|
|
275
|
-
]
|
|
276
|
-
}
|
|
277
|
-
}
|
|
208
|
+
```bash
|
|
209
|
+
/plugin install kenryu42/copilot-safety-net
|
|
278
210
|
```
|
|
279
211
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
- `~/.copilot/config.json`
|
|
283
|
-
- `.github/copilot/settings.json`
|
|
284
|
-
- `.github/copilot/settings.local.json`
|
|
285
|
-
|
|
286
|
-
Recommended usage:
|
|
287
|
-
|
|
288
|
-
- Use `~/.copilot/config.json` for your personal default across repositories.
|
|
289
|
-
- Use `.github/copilot/settings.json` for a committed repository-wide setup.
|
|
290
|
-
- Use `.github/copilot/settings.local.json` for personal repo-specific overrides, and add it to `.gitignore`.
|
|
291
|
-
|
|
292
|
-
If you need to turn hooks off explicitly, set:
|
|
212
|
+
> [!NOTE]
|
|
213
|
+
> After installing the plugin, you need to restart your Copilot CLI for it to take effect.
|
|
293
214
|
|
|
294
|
-
|
|
295
|
-
{
|
|
296
|
-
"disableAllHooks": true
|
|
297
|
-
}
|
|
298
|
-
```
|
|
215
|
+
---
|
|
299
216
|
|
|
300
217
|
## Status Line Integration
|
|
301
218
|
|
|
@@ -2865,6 +2865,7 @@ function normalizePathForComparison(p) {
|
|
|
2865
2865
|
}
|
|
2866
2866
|
var REASON_RM_RF = "rm -rf outside cwd is blocked. Use explicit paths within the current directory, or delete manually.";
|
|
2867
2867
|
var REASON_RM_RF_ROOT_HOME = "rm -rf targeting root or home directory is extremely dangerous and always blocked.";
|
|
2868
|
+
var REASON_RM_HOME_CWD = "rm -rf in home directory is dangerous. Change to a project directory first.";
|
|
2868
2869
|
function analyzeRm(tokens, options = {}) {
|
|
2869
2870
|
const {
|
|
2870
2871
|
cwd,
|
|
@@ -2921,18 +2922,16 @@ function classifyTarget(target, ctx) {
|
|
|
2921
2922
|
if (isDangerousRootOrHomeTarget(target)) {
|
|
2922
2923
|
return { kind: "root_or_home_target" };
|
|
2923
2924
|
}
|
|
2924
|
-
const anchoredCwd = ctx.anchoredCwd;
|
|
2925
|
-
if (anchoredCwd) {
|
|
2926
|
-
if (isCwdSelfTarget(target, anchoredCwd)) {
|
|
2927
|
-
return { kind: "cwd_self_target" };
|
|
2928
|
-
}
|
|
2929
|
-
}
|
|
2930
2925
|
if (isTempTarget(target, ctx.trustTmpdirVar)) {
|
|
2931
2926
|
return { kind: "temp_target" };
|
|
2932
2927
|
}
|
|
2928
|
+
const anchoredCwd = ctx.anchoredCwd;
|
|
2933
2929
|
if (anchoredCwd) {
|
|
2934
2930
|
if (isCwdHomeForRmPolicy(anchoredCwd, ctx.homeDir)) {
|
|
2935
|
-
return { kind: "
|
|
2931
|
+
return { kind: "home_cwd_target" };
|
|
2932
|
+
}
|
|
2933
|
+
if (isCwdSelfTarget(target, anchoredCwd)) {
|
|
2934
|
+
return { kind: "cwd_self_target" };
|
|
2936
2935
|
}
|
|
2937
2936
|
if (isTargetWithinCwd(target, anchoredCwd, ctx.resolvedCwd ?? anchoredCwd)) {
|
|
2938
2937
|
return { kind: "within_anchored_cwd" };
|
|
@@ -2944,10 +2943,12 @@ function reasonForClassification(classification, ctx) {
|
|
|
2944
2943
|
switch (classification.kind) {
|
|
2945
2944
|
case "root_or_home_target":
|
|
2946
2945
|
return REASON_RM_RF_ROOT_HOME;
|
|
2947
|
-
case "cwd_self_target":
|
|
2948
|
-
return REASON_RM_RF;
|
|
2949
2946
|
case "temp_target":
|
|
2950
2947
|
return null;
|
|
2948
|
+
case "home_cwd_target":
|
|
2949
|
+
return REASON_RM_HOME_CWD;
|
|
2950
|
+
case "cwd_self_target":
|
|
2951
|
+
return REASON_RM_RF;
|
|
2951
2952
|
case "within_anchored_cwd":
|
|
2952
2953
|
if (ctx.paranoid) {
|
|
2953
2954
|
return `${REASON_RM_RF} (SAFETY_NET_PARANOID_RM enabled)`;
|
|
@@ -3069,14 +3070,6 @@ function isTargetWithinCwd(target, originalCwd, effectiveCwd) {
|
|
|
3069
3070
|
return false;
|
|
3070
3071
|
}
|
|
3071
3072
|
}
|
|
3072
|
-
function isHomeDirectory(cwd) {
|
|
3073
|
-
const home = process.env.HOME ?? homedir3();
|
|
3074
|
-
try {
|
|
3075
|
-
return normalizePathForComparison(cwd) === normalizePathForComparison(home);
|
|
3076
|
-
} catch {
|
|
3077
|
-
return false;
|
|
3078
|
-
}
|
|
3079
|
-
}
|
|
3080
3073
|
|
|
3081
3074
|
// src/core/analyze/parallel.ts
|
|
3082
3075
|
var REASON_PARALLEL_RM = "parallel rm -rf with dynamic input is dangerous. Use explicit file list instead.";
|
|
@@ -3497,7 +3490,6 @@ function matchesBlockArgs(tokens, blockArgs, shortOpts) {
|
|
|
3497
3490
|
// src/core/analyze/segment.ts
|
|
3498
3491
|
var REASON_INTERPRETER_DANGEROUS = "Detected potentially dangerous command in interpreter code.";
|
|
3499
3492
|
var REASON_INTERPRETER_BLOCKED = "Interpreter one-liners are blocked in paranoid mode.";
|
|
3500
|
-
var REASON_RM_HOME_CWD = "rm -rf in home directory is dangerous. Change to a project directory first.";
|
|
3501
3493
|
function deriveCwdContext(options) {
|
|
3502
3494
|
const cwdUnknown = options.effectiveCwd === null;
|
|
3503
3495
|
const cwdForRm = cwdUnknown ? undefined : options.effectiveCwd ?? options.cwd;
|
|
@@ -3561,11 +3553,6 @@ function analyzeSegment(tokens, depth, options) {
|
|
|
3561
3553
|
}
|
|
3562
3554
|
}
|
|
3563
3555
|
if (isRm) {
|
|
3564
|
-
if (cwdForRm && isHomeDirectory(cwdForRm)) {
|
|
3565
|
-
if (hasRecursiveForceFlags(stripped)) {
|
|
3566
|
-
return REASON_RM_HOME_CWD;
|
|
3567
|
-
}
|
|
3568
|
-
}
|
|
3569
3556
|
const rmResult = analyzeRm(stripped, {
|
|
3570
3557
|
cwd: cwdForRm,
|
|
3571
3558
|
originalCwd,
|
|
@@ -3731,6 +3718,7 @@ function analyzeCommand(command, options = {}) {
|
|
|
3731
3718
|
}
|
|
3732
3719
|
|
|
3733
3720
|
// src/bin/doctor/hooks.ts
|
|
3721
|
+
var COPILOT_PLUGIN_CONFIG_PATH = "copilot-plugin";
|
|
3734
3722
|
var SELF_TEST_CASES = [
|
|
3735
3723
|
{ command: "git reset --hard", description: "git reset --hard", expectBlocked: true },
|
|
3736
3724
|
{ command: "rm -rf /", description: "rm -rf /", expectBlocked: true },
|
|
@@ -4178,13 +4166,15 @@ function detectAllHooks(cwd, options) {
|
|
|
4178
4166
|
errors: errors.length > 0 ? errors : undefined
|
|
4179
4167
|
};
|
|
4180
4168
|
}
|
|
4181
|
-
if (hooksCheck.activeConfigPaths.length > 0) {
|
|
4169
|
+
if (options?.copilotPluginInstalled === true || hooksCheck.activeConfigPaths.length > 0) {
|
|
4170
|
+
const viaPlugin = options?.copilotPluginInstalled === true;
|
|
4171
|
+
const primaryConfigPath = hooksCheck.activeConfigPaths[0];
|
|
4182
4172
|
return {
|
|
4183
4173
|
platform: "copilot-cli",
|
|
4184
4174
|
status: "configured",
|
|
4185
|
-
method: "hook config",
|
|
4186
|
-
configPath:
|
|
4187
|
-
configPaths: hooksCheck.activeConfigPaths,
|
|
4175
|
+
method: viaPlugin ? "plugin list" : "hook config",
|
|
4176
|
+
configPath: primaryConfigPath ?? (viaPlugin ? COPILOT_PLUGIN_CONFIG_PATH : undefined),
|
|
4177
|
+
configPaths: hooksCheck.activeConfigPaths.length > 0 ? hooksCheck.activeConfigPaths : undefined,
|
|
4188
4178
|
selfTest: runSelfTest(),
|
|
4189
4179
|
errors: errors.length > 0 ? errors : undefined
|
|
4190
4180
|
};
|
|
@@ -4205,11 +4195,12 @@ function detectAllHooks(cwd, options) {
|
|
|
4205
4195
|
|
|
4206
4196
|
// src/bin/doctor/system-info.ts
|
|
4207
4197
|
import { spawn } from "node:child_process";
|
|
4208
|
-
var CURRENT_VERSION = "0.8.
|
|
4198
|
+
var CURRENT_VERSION = "0.8.2";
|
|
4209
4199
|
var VERSION_FETCH_TIMEOUT_MS = 2000;
|
|
4210
4200
|
function getPackageVersion() {
|
|
4211
4201
|
return CURRENT_VERSION;
|
|
4212
4202
|
}
|
|
4203
|
+
var COPILOT_PLUGIN_ID = "copilot-safety-net";
|
|
4213
4204
|
var defaultVersionFetcher = async (args) => {
|
|
4214
4205
|
const [cmd, ...rest] = args;
|
|
4215
4206
|
if (!cmd)
|
|
@@ -4260,6 +4251,12 @@ function parseVersion(output) {
|
|
|
4260
4251
|
`)[0]?.trim();
|
|
4261
4252
|
return firstLine || null;
|
|
4262
4253
|
}
|
|
4254
|
+
function hasCopilotSafetyNetPlugin(output) {
|
|
4255
|
+
if (!output)
|
|
4256
|
+
return false;
|
|
4257
|
+
const pluginPattern = new RegExp(`(^|[^a-z0-9-])${COPILOT_PLUGIN_ID}([^a-z0-9-]|$)`, "m");
|
|
4258
|
+
return pluginPattern.test(output);
|
|
4259
|
+
}
|
|
4263
4260
|
async function getSystemInfo(fetcher = defaultVersionFetcher) {
|
|
4264
4261
|
const fetchCopilotVersion = async () => {
|
|
4265
4262
|
const binaryVersionPromise = fetcher(["copilot", "--binary-version"]);
|
|
@@ -4270,14 +4267,15 @@ async function getSystemInfo(fetcher = defaultVersionFetcher) {
|
|
|
4270
4267
|
}
|
|
4271
4268
|
return fallbackVersionPromise;
|
|
4272
4269
|
};
|
|
4273
|
-
const [claudeRaw, openCodeRaw, geminiRaw, copilotRaw, nodeRaw, npmRaw, bunRaw] = await Promise.all([
|
|
4270
|
+
const [claudeRaw, openCodeRaw, geminiRaw, copilotRaw, nodeRaw, npmRaw, bunRaw, pluginListRaw] = await Promise.all([
|
|
4274
4271
|
fetcher(["claude", "--version"]),
|
|
4275
4272
|
fetcher(["opencode", "--version"]),
|
|
4276
4273
|
fetcher(["gemini", "--version"]),
|
|
4277
4274
|
fetchCopilotVersion(),
|
|
4278
4275
|
fetcher(["node", "--version"]),
|
|
4279
4276
|
fetcher(["npm", "--version"]),
|
|
4280
|
-
fetcher(["bun", "--version"])
|
|
4277
|
+
fetcher(["bun", "--version"]),
|
|
4278
|
+
fetcher(["copilot", "plugin", "list"])
|
|
4281
4279
|
]);
|
|
4282
4280
|
return {
|
|
4283
4281
|
version: CURRENT_VERSION,
|
|
@@ -4288,6 +4286,7 @@ async function getSystemInfo(fetcher = defaultVersionFetcher) {
|
|
|
4288
4286
|
nodeVersion: parseVersion(nodeRaw),
|
|
4289
4287
|
npmVersion: parseVersion(npmRaw),
|
|
4290
4288
|
bunVersion: parseVersion(bunRaw),
|
|
4289
|
+
copilotPluginInstalled: hasCopilotSafetyNetPlugin(pluginListRaw),
|
|
4291
4290
|
platform: `${process.platform} ${process.arch}`
|
|
4292
4291
|
};
|
|
4293
4292
|
}
|
|
@@ -4353,7 +4352,10 @@ function parseDoctorFlags(args) {
|
|
|
4353
4352
|
async function runDoctor(options = {}) {
|
|
4354
4353
|
const cwd = options.cwd ?? process.cwd();
|
|
4355
4354
|
const system = await getSystemInfo();
|
|
4356
|
-
const hooks = detectAllHooks(cwd, {
|
|
4355
|
+
const hooks = detectAllHooks(cwd, {
|
|
4356
|
+
copilotCliVersion: system.copilotCliVersion,
|
|
4357
|
+
copilotPluginInstalled: system.copilotPluginInstalled
|
|
4358
|
+
});
|
|
4357
4359
|
const configInfo = getConfigInfo(cwd);
|
|
4358
4360
|
const environment = getEnvironmentInfo();
|
|
4359
4361
|
const activity = getActivitySummary(7);
|
|
@@ -4667,19 +4669,9 @@ function explainSegment(tokens, depth, options, steps) {
|
|
|
4667
4669
|
return { reason };
|
|
4668
4670
|
}
|
|
4669
4671
|
if (isRm) {
|
|
4670
|
-
if (effectiveCwd && isHomeDirectory(effectiveCwd) && hasRecursiveForceFlags(strippedTokens)) {
|
|
4671
|
-
const reason2 = "rm -rf in home directory is dangerous. Change to a project directory first.";
|
|
4672
|
-
steps.push({
|
|
4673
|
-
type: "rule-check",
|
|
4674
|
-
ruleModule: "rules-rm.ts",
|
|
4675
|
-
ruleFunction: "isHomeDirectory",
|
|
4676
|
-
matched: true,
|
|
4677
|
-
reason: reason2
|
|
4678
|
-
});
|
|
4679
|
-
return { reason: reason2 };
|
|
4680
|
-
}
|
|
4681
4672
|
const reason = analyzeRm(strippedTokens, {
|
|
4682
|
-
cwd:
|
|
4673
|
+
cwd: cwdForRm,
|
|
4674
|
+
originalCwd,
|
|
4683
4675
|
paranoid: options.paranoidRm,
|
|
4684
4676
|
allowTmpdirVar
|
|
4685
4677
|
});
|
|
@@ -5276,7 +5268,7 @@ function formatTraceJson(result) {
|
|
|
5276
5268
|
return JSON.stringify(result, null, 2);
|
|
5277
5269
|
}
|
|
5278
5270
|
// src/bin/help.ts
|
|
5279
|
-
var version = "0.8.
|
|
5271
|
+
var version = "0.8.2";
|
|
5280
5272
|
var INDENT = " ";
|
|
5281
5273
|
var PROGRAM_NAME = "cc-safety-net";
|
|
5282
5274
|
function formatOptionFlags(option) {
|
|
@@ -6,6 +6,7 @@ import type { LoadConfigOptions } from '@/core/config';
|
|
|
6
6
|
interface HookDetectOptions extends LoadConfigOptions {
|
|
7
7
|
homeDir?: string;
|
|
8
8
|
copilotCliVersion?: string | null;
|
|
9
|
+
copilotPluginInstalled?: boolean;
|
|
9
10
|
}
|
|
10
11
|
/**
|
|
11
12
|
* Strip JSONC-style comments and trailing commas from a string.
|
|
@@ -105,6 +105,8 @@ export interface SystemInfo {
|
|
|
105
105
|
npmVersion: string | null;
|
|
106
106
|
/** Bun version (from `bun --version`) */
|
|
107
107
|
bunVersion: string | null;
|
|
108
|
+
/** Whether the copilot-safety-net plugin is installed (from `copilot plugin list`) */
|
|
109
|
+
copilotPluginInstalled: boolean;
|
|
108
110
|
/** Platform (e.g., "darwin arm64") */
|
|
109
111
|
platform: string;
|
|
110
112
|
}
|
package/dist/core/rules-rm.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1724,6 +1724,7 @@ function normalizePathForComparison(p) {
|
|
|
1724
1724
|
}
|
|
1725
1725
|
var REASON_RM_RF = "rm -rf outside cwd is blocked. Use explicit paths within the current directory, or delete manually.";
|
|
1726
1726
|
var REASON_RM_RF_ROOT_HOME = "rm -rf targeting root or home directory is extremely dangerous and always blocked.";
|
|
1727
|
+
var REASON_RM_HOME_CWD = "rm -rf in home directory is dangerous. Change to a project directory first.";
|
|
1727
1728
|
function analyzeRm(tokens, options = {}) {
|
|
1728
1729
|
const {
|
|
1729
1730
|
cwd,
|
|
@@ -1780,18 +1781,16 @@ function classifyTarget(target, ctx) {
|
|
|
1780
1781
|
if (isDangerousRootOrHomeTarget(target)) {
|
|
1781
1782
|
return { kind: "root_or_home_target" };
|
|
1782
1783
|
}
|
|
1783
|
-
const anchoredCwd = ctx.anchoredCwd;
|
|
1784
|
-
if (anchoredCwd) {
|
|
1785
|
-
if (isCwdSelfTarget(target, anchoredCwd)) {
|
|
1786
|
-
return { kind: "cwd_self_target" };
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
1784
|
if (isTempTarget(target, ctx.trustTmpdirVar)) {
|
|
1790
1785
|
return { kind: "temp_target" };
|
|
1791
1786
|
}
|
|
1787
|
+
const anchoredCwd = ctx.anchoredCwd;
|
|
1792
1788
|
if (anchoredCwd) {
|
|
1793
1789
|
if (isCwdHomeForRmPolicy(anchoredCwd, ctx.homeDir)) {
|
|
1794
|
-
return { kind: "
|
|
1790
|
+
return { kind: "home_cwd_target" };
|
|
1791
|
+
}
|
|
1792
|
+
if (isCwdSelfTarget(target, anchoredCwd)) {
|
|
1793
|
+
return { kind: "cwd_self_target" };
|
|
1795
1794
|
}
|
|
1796
1795
|
if (isTargetWithinCwd(target, anchoredCwd, ctx.resolvedCwd ?? anchoredCwd)) {
|
|
1797
1796
|
return { kind: "within_anchored_cwd" };
|
|
@@ -1803,10 +1802,12 @@ function reasonForClassification(classification, ctx) {
|
|
|
1803
1802
|
switch (classification.kind) {
|
|
1804
1803
|
case "root_or_home_target":
|
|
1805
1804
|
return REASON_RM_RF_ROOT_HOME;
|
|
1806
|
-
case "cwd_self_target":
|
|
1807
|
-
return REASON_RM_RF;
|
|
1808
1805
|
case "temp_target":
|
|
1809
1806
|
return null;
|
|
1807
|
+
case "home_cwd_target":
|
|
1808
|
+
return REASON_RM_HOME_CWD;
|
|
1809
|
+
case "cwd_self_target":
|
|
1810
|
+
return REASON_RM_RF;
|
|
1810
1811
|
case "within_anchored_cwd":
|
|
1811
1812
|
if (ctx.paranoid) {
|
|
1812
1813
|
return `${REASON_RM_RF} (SAFETY_NET_PARANOID_RM enabled)`;
|
|
@@ -1928,14 +1929,6 @@ function isTargetWithinCwd(target, originalCwd, effectiveCwd) {
|
|
|
1928
1929
|
return false;
|
|
1929
1930
|
}
|
|
1930
1931
|
}
|
|
1931
|
-
function isHomeDirectory(cwd) {
|
|
1932
|
-
const home = process.env.HOME ?? homedir();
|
|
1933
|
-
try {
|
|
1934
|
-
return normalizePathForComparison(cwd) === normalizePathForComparison(home);
|
|
1935
|
-
} catch {
|
|
1936
|
-
return false;
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
1932
|
|
|
1940
1933
|
// src/core/analyze/parallel.ts
|
|
1941
1934
|
var REASON_PARALLEL_RM = "parallel rm -rf with dynamic input is dangerous. Use explicit file list instead.";
|
|
@@ -2356,7 +2349,6 @@ function matchesBlockArgs(tokens, blockArgs, shortOpts) {
|
|
|
2356
2349
|
// src/core/analyze/segment.ts
|
|
2357
2350
|
var REASON_INTERPRETER_DANGEROUS = "Detected potentially dangerous command in interpreter code.";
|
|
2358
2351
|
var REASON_INTERPRETER_BLOCKED = "Interpreter one-liners are blocked in paranoid mode.";
|
|
2359
|
-
var REASON_RM_HOME_CWD = "rm -rf in home directory is dangerous. Change to a project directory first.";
|
|
2360
2352
|
function deriveCwdContext(options) {
|
|
2361
2353
|
const cwdUnknown = options.effectiveCwd === null;
|
|
2362
2354
|
const cwdForRm = cwdUnknown ? undefined : options.effectiveCwd ?? options.cwd;
|
|
@@ -2420,11 +2412,6 @@ function analyzeSegment(tokens, depth, options) {
|
|
|
2420
2412
|
}
|
|
2421
2413
|
}
|
|
2422
2414
|
if (isRm) {
|
|
2423
|
-
if (cwdForRm && isHomeDirectory(cwdForRm)) {
|
|
2424
|
-
if (hasRecursiveForceFlags(stripped)) {
|
|
2425
|
-
return REASON_RM_HOME_CWD;
|
|
2426
|
-
}
|
|
2427
|
-
}
|
|
2428
2415
|
const rmResult = analyzeRm(stripped, {
|
|
2429
2416
|
cwd: cwdForRm,
|
|
2430
2417
|
originalCwd,
|
package/package.json
CHANGED