@giuseppe.trisciuoglio/pi-prevent-destructive-commands 1.1.0 → 1.2.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/CHANGELOG.md +18 -1
- package/README.md +27 -6
- package/package.json +4 -2
- package/src/checker.ts +20 -11
- package/src/comment-guard.ts +617 -0
- package/src/config.ts +26 -0
- package/src/index.ts +27 -33
- package/src/nx-guard.ts +18 -52
- package/src/patch-paths.ts +57 -0
- package/src/project-config.ts +8 -3
- package/src/rules/args.ts +56 -0
- package/src/rules/aws.ts +5 -12
- package/src/rules/file-reading.ts +8 -13
- package/src/rules/gh.ts +36 -0
- package/src/rules/git.ts +44 -0
- package/src/token-segments.ts +39 -0
- package/test/comment-guard-test.ts +371 -0
- package/test/gh-guard-test.ts +50 -0
- package/test/git-guards-command-test.ts +19 -39
- package/test/helpers.ts +113 -0
- package/test/heredoc-test.ts +3 -29
- package/test/migration-guard-test.ts +7 -16
- package/test/project-config-test.ts +4 -33
- package/test/smoke-test.ts +3 -31
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- New guard: new comments in source files are blocked. Any tool call that would introduce a new comment into a source file (Java, TypeScript/JavaScript, Dart, Go, Rust, C/C++, C#, Swift, Kotlin, Scala, PHP, CSS/SCSS, Python, Ruby, shell, YAML, TOML, Terraform, GraphQL, SQL, Lua, Haskell, Elm, HTML/XML/SVG/Markdown) is stopped, covering `write`, `edit`, `apply_patch` (unified diff and `*** Begin Patch` formats), and bash write vectors (`cat > a.ts <<EOF`, `tee b.py <<EOF`, `echo "// x" >> c.ts`). Detection is string-aware (comment markers inside string literals are not comments), comments a file already contains may be carried over untouched, and shebang lines, tooling directives (`@ts-ignore`, `eslint-disable`, `biome-ignore`, `noqa`, …) and purely decorative lines are exempt. Opt out per project with `{"disableCommentGuard": true}` in the opt-out file. Adds `test/comment-guard-test.ts` (`npm run test:comment-guard`).
|
|
13
|
+
- New guard: interactive `git commit` without a message option (bare `git commit`, `git commit -a`, `git commit --amend`, ...) is blocked because it opens the editor and hangs the session when the agent runs unattended. Applies unconditionally, even on projects that opted out of the git add/commit/push guards; commits with a message (`git commit -m x`, `-F file`, `--amend --no-edit`, ...) are unaffected.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Extracted shared helpers to remove all code clones reported by jscpd (≥ 50 tokens): `src/rules/args.ts` (positional-argument scanning and longest-subcommand matching for the checker and the aws/gh/file-reading rules), `src/patch-paths.ts` (patch/file-path extraction shared by the extension entry point and the Nx guard), `src/token-segments.ts` (command-chain segmentation for the bash-level guards), and `test/helpers.ts` (shared test utilities: `check`, `makeProject`, `isBlocked`, `runCheckCases`, `runIsolatedTest`). No behavior change.
|
|
18
|
+
|
|
19
|
+
## [1.2.0] - 2026-09-28
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- New guard: destructive GitHub CLI commands are blocked. `gh api` calls carrying a destructive method (`gh api -X DELETE ...`, `gh api --method DELETE ...`, including the `--method=DELETE` form) and destructive `gh` subcommands (`gh repo delete`, `gh repo rename`, `gh release delete`, `gh gist delete`, `gh secret delete`, `gh variable delete`, `gh label delete`, `gh workflow disable`, `gh run delete`) are stopped before execution. Safe `gh` usage (`gh api` GET/POST, `gh pr view`, ...) remains allowed. Adds `test/gh-guard-test.ts` (`npm run test:gh-guard`).
|
|
24
|
+
|
|
10
25
|
## [1.1.0] - 2026-09-01
|
|
11
26
|
|
|
12
27
|
### Added
|
|
@@ -65,7 +80,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
65
80
|
- No interactive confirmation — protection works in non-interactive modes (`-p`, JSON, RPC).
|
|
66
81
|
- Maximum nesting depth limit (5) to prevent obfuscation attacks.
|
|
67
82
|
|
|
68
|
-
[Unreleased]: https://github.com/giuseppe-trisciuoglio/pi-prevent-destructive-commands/compare/v1.
|
|
83
|
+
[Unreleased]: https://github.com/giuseppe-trisciuoglio/pi-prevent-destructive-commands/compare/v1.2.0...HEAD
|
|
84
|
+
|
|
85
|
+
[1.2.0]: https://github.com/giuseppe-trisciuoglio/pi-prevent-destructive-commands/compare/v1.1.0...v1.2.0
|
|
69
86
|
|
|
70
87
|
[1.1.0]: https://github.com/giuseppe-trisciuoglio/pi-prevent-destructive-commands/compare/v1.0.1...v1.1.0
|
|
71
88
|
|
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ A faithful port of Claude's [`prevent-destructive-commands.py`](https://github.c
|
|
|
18
18
|
- **Works in all modes** — Protection is active even in non-interactive sessions (`-p`, JSON, RPC).
|
|
19
19
|
- **Recursive analysis** — Traverses command wrappers, shell invocations, pipelines, and nested commands to catch obfuscated attacks.
|
|
20
20
|
- **Nx configuration guard** — In Nx workspaces, existing `package.json` and TypeScript configuration files are immutable to the agent; missing ones can still be created.
|
|
21
|
+
- **Source comment guard** — The agent cannot introduce new comments into source files (Java, TypeScript, Dart, Python, SQL, HTML, and more) through writing tools or bash write vectors; existing comments may be carried over untouched and tooling directives (`@ts-ignore`, `eslint-disable`, …) stay allowed.
|
|
21
22
|
- **Configurable** — Tune protection levels via simple flags in `src/config.ts`.
|
|
22
23
|
- **Zero dependencies** — Lightweight, fast, and self-contained.
|
|
23
24
|
- **79+ test cases** — Comprehensive smoke test suite validates all blocking rules.
|
|
@@ -30,12 +31,15 @@ All rules are defined in [`src/config.ts`](src/config.ts) and can be customized.
|
|
|
30
31
|
|----------|----------|
|
|
31
32
|
| **Destructive Git** | `git reset --hard`, `git clean`, `git push --force` / `-f` / `--delete`, `git branch -D`, `git tag -d`, `git checkout -f`, `git rebase`, `git filter-branch`, `git filter-repo`, `git reflog expire`, `git update-ref -d` |
|
|
32
33
|
| **Git add/commit/push** | `git add`, `git commit`, `git push` *(see `ENABLE_GIT_ADD_COMMIT_BLOCK` flag and the per-project opt-out below; force/delete push variants are always blocked)* |
|
|
34
|
+
| **Interactive git commit** | `git commit` without a message option (`-m`/`--message`, `-F`/`--file`, `-C`/`--reuse-message`, `-c`/`--reedit-message`, `--no-edit`, `--fixup`, or combined short forms like `-am`) — it would open the editor and hang the session. Always blocked, even when the project opts out of the git add/commit/push guards. |
|
|
33
35
|
| **rm / path-sensitive** | `rm`, `rmdir`, `shred`, `unlink` targeting paths **outside** the working directory (e.g., `/etc`, `~`, `..`). Targets inside cwd are allowed. |
|
|
34
36
|
| **find outside cwd** | `find` whose search root is **outside** the working directory (e.g., `find /etc`, `find ~`, `find ..`). Search roots inside cwd are allowed; `-exec` payloads are analyzed recursively as before. |
|
|
35
37
|
| **Destructive Docker** | `docker rm` / `rmi`, `docker container/image/volume/network rm`, `docker * prune`, `docker compose down -v`, `docker compose rm`, `docker context rm`, `docker swarm leave --force` |
|
|
38
|
+
| **Destructive GitHub CLI** | `gh api -X DELETE` / `gh api --method DELETE`, `gh repo delete` / `rename`, `gh release delete`, `gh gist delete`, `gh secret delete`, `gh variable delete`, `gh label delete`, `gh workflow disable`, `gh run delete` |
|
|
36
39
|
| **Destructive AWS CLI** | `aws s3 rm`, `aws ec2 terminate-instances`, `aws rds delete-db-instance`, `aws cloudformation delete-stack`, and 50+ more subcommands (full list in `src/config.ts`) |
|
|
37
40
|
| **Sensitive file reads** | `cat`, `grep`, etc. on `.env`, SSH keys, `.pem` files — **disabled by default** via `ENABLE_SENSITIVE_FILE_CHECK` *(see Configuration)* |
|
|
38
41
|
| **Existing Nx configuration** | `package.json`, `tsconfig.json`, `tsconfig.base.json`, `tsconfig.lib.json`, and `tsconfig.spec.json` anywhere below a workspace containing `nx.json`. Creating a missing file is allowed; changing or deleting an existing file is blocked. |
|
|
42
|
+
| **New comments in source files** | Any tool call that introduces a **new** comment into a source file: `write`/`edit`/`apply_patch` touching Java, TypeScript/JavaScript, Dart, Go, Rust, C/C++, C#, Swift, Kotlin, Scala, PHP, CSS/SCSS, Python, Ruby, shell, YAML, TOML, Terraform, GraphQL, SQL, Lua, Haskell, Elm, HTML/XML/SVG/Markdown files, plus bash write vectors (`cat > a.ts <<EOF`, `tee b.py <<EOF`, `echo "// x" >> c.ts`). Comments already in the file may be carried over; shebang lines, tooling directives (`@ts-ignore`, `eslint-disable`, `biome-ignore`, `noqa`, …) and purely decorative lines are exempt *(see the per-project opt-out below)* |
|
|
39
43
|
|
|
40
44
|
### Recursive Analysis
|
|
41
45
|
|
|
@@ -63,7 +67,7 @@ Anything the extractor cannot parse unambiguously (here-strings `<<<`, variable
|
|
|
63
67
|
### Via pi Marketplace
|
|
64
68
|
|
|
65
69
|
```bash
|
|
66
|
-
pi install
|
|
70
|
+
pi install npm:@giuseppe.trisciuoglio/pi-prevent-destructive-commands
|
|
67
71
|
```
|
|
68
72
|
|
|
69
73
|
### Manual Installation
|
|
@@ -103,14 +107,20 @@ pi /reload
|
|
|
103
107
|
|
|
104
108
|
### Per-Project Opt-Out
|
|
105
109
|
|
|
106
|
-
Create `.pi/prevent-destructive-commands.json` in the project root to
|
|
110
|
+
Create `.pi/prevent-destructive-commands.json` in the project root to tune the guards **for that project only**:
|
|
107
111
|
|
|
108
112
|
```json
|
|
109
113
|
{
|
|
110
|
-
"disableGitGuards": true
|
|
114
|
+
"disableGitGuards": true,
|
|
115
|
+
"disableCommentGuard": true
|
|
111
116
|
}
|
|
112
117
|
```
|
|
113
118
|
|
|
119
|
+
| Flag | Default | Effect |
|
|
120
|
+
|------|---------|--------|
|
|
121
|
+
| `disableGitGuards` | `false` | Disables the `git add` / `git commit` / `git push` guards. |
|
|
122
|
+
| `disableCommentGuard` | `false` | Disables the source comment guard, allowing the agent to write comments into source files again. |
|
|
123
|
+
|
|
114
124
|
Or use the `/git-guards` slash command from the pi TUI:
|
|
115
125
|
|
|
116
126
|
| Command | Effect |
|
|
@@ -139,8 +149,12 @@ prevent-destructive-commands/
|
|
|
139
149
|
│ ├── checker.ts # Recursive command walker (wrappers/shell/find/xargs)
|
|
140
150
|
│ ├── migration-guard.ts
|
|
141
151
|
│ ├── nx-guard.ts # Protects existing Nx package and TypeScript configuration files
|
|
152
|
+
│ ├── comment-guard.ts # Blocks new comments written into source files
|
|
153
|
+
│ ├── patch-paths.ts # Shared patch/file-path extraction from tool inputs
|
|
154
|
+
│ ├── token-segments.ts # Shared command-segment splitting for bash-level guards
|
|
142
155
|
│ └── rules/ # Per-category destructive-command handlers
|
|
143
156
|
│ ├── types.ts # Shared CheckResult type + helpers
|
|
157
|
+
│ ├── args.ts # Token-scanning helpers (positional args, subcommand matching)
|
|
144
158
|
│ ├── path-utils.ts # cwd-relative path resolution
|
|
145
159
|
│ ├── git.ts # git reset --hard, push --force, ...
|
|
146
160
|
│ ├── docker.ts # docker rm, system prune, ...
|
|
@@ -149,8 +163,10 @@ prevent-destructive-commands/
|
|
|
149
163
|
│ └── path-sensitive.ts # rm/rmdir/... outside-cwd detection
|
|
150
164
|
├── test/
|
|
151
165
|
│ ├── smoke-test.ts # Standalone test suite (79+ cases)
|
|
166
|
+
│ ├── helpers.ts # Shared test utilities (check, makeProject, runCheckCases, ...)
|
|
152
167
|
│ ├── migration-guard-test.ts
|
|
153
168
|
│ ├── nx-guard-test.ts # Verifies Nx configuration protection and allowed creation
|
|
169
|
+
│ ├── comment-guard-test.ts # Verifies new-comment blocking across write, edit, patch, bash
|
|
154
170
|
│ └── e2e-install-test.ts # End-to-end: verifies real installation/discovery by pi
|
|
155
171
|
├── tsconfig.json # TypeScript configuration
|
|
156
172
|
├── package.json # Package metadata for pi marketplace
|
|
@@ -175,6 +191,8 @@ Standalone tests for the tokenizer/checker logic (no dependency on pi itself):
|
|
|
175
191
|
npm run test:smoke
|
|
176
192
|
npm run test:heredoc
|
|
177
193
|
npm run test:nx-guard
|
|
194
|
+
npm run test:comment-guard
|
|
195
|
+
```
|
|
178
196
|
|
|
179
197
|
# Or directly with tsx
|
|
180
198
|
npx tsx test/smoke-test.ts
|
|
@@ -202,6 +220,7 @@ npx tsx test/e2e-install-test.ts
|
|
|
202
220
|
The test suite covers:
|
|
203
221
|
- All destructive Git operations
|
|
204
222
|
- Existing Nx package and TypeScript configuration protection, including direct tool writes, patches, shell redirection, and package-manager dependency changes
|
|
223
|
+
- Source comment blocking across `write`, `edit`, `apply_patch`, and bash write vectors (heredoc, `tee`, `echo`/`printf`), including string-literal false-positive checks and the per-project opt-out
|
|
205
224
|
- Path-sensitive `rm` protection
|
|
206
225
|
- Docker destructive commands
|
|
207
226
|
- AWS CLI destructive subcommands
|
|
@@ -222,6 +241,7 @@ As with the original Claude plugin, the analysis is static and therefore cannot
|
|
|
222
241
|
| Limitation | Example | Explanation |
|
|
223
242
|
|------------|---------|-------------|
|
|
224
243
|
| **Unknown wrappers** | Custom destructive tools | The extension covers known patterns; unknown wrappers or custom destructive tools are not intercepted. |
|
|
244
|
+
| **Comment guard scope** | `python -c`, `sed` insertion; `.vue`/`.svelte`/`.astro` files | The comment guard covers writing tools and the common bash write vectors (heredoc, `tee`, `echo`/`printf` arguments); other generators are not intercepted. Mixed-language single-file components are excluded to avoid false positives across template/script/style blocks. A bash rewrite of an existing file counts every comment in the heredoc body as new. |
|
|
225
245
|
|
|
226
246
|
Two cases that used to be listed here are now handled:
|
|
227
247
|
|
|
@@ -232,13 +252,14 @@ Two cases that used to be listed here are now handled:
|
|
|
232
252
|
|
|
233
253
|
## How It Works
|
|
234
254
|
|
|
235
|
-
When pi attempts to execute a
|
|
255
|
+
When pi attempts to execute a tool call, this extension intercepts the `tool_call` event and:
|
|
236
256
|
|
|
237
257
|
1. **Extracts heredoc bodies** so prose fed through `cat > file <<'EOF'` is treated as data, keeping bodies under analysis only when an executor on the command line would run them.
|
|
238
258
|
2. **Tokenizes** the command string using a shlex-like shell tokenizer that respects quotes and escapes.
|
|
239
259
|
3. **Analyzes** the token stream recursively, traversing wrappers, shell invocations, and pipelines.
|
|
240
|
-
4. **
|
|
241
|
-
5. **
|
|
260
|
+
4. **Checks writing tool calls** against the file-content guards: immutable Nx configuration, and new comments introduced into source files (string-aware, per language family, with only-newly-introduced comments blocked).
|
|
261
|
+
5. **Blocks** if any destructive pattern is detected, returning a clear reason to the agent.
|
|
262
|
+
6. **Allows** safe commands to pass through without modification.
|
|
242
263
|
|
|
243
264
|
The agent never receives an interactive prompt — the block is final and must be handled by finding a safe alternative.
|
|
244
265
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@giuseppe.trisciuoglio/pi-prevent-destructive-commands",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Unconditionally blocks destructive bash commands before execution. A pi extension that guards against rm, git reset --hard, docker rm, aws delete operations, and more.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"node": ">=18.0.0"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
|
-
"test": "npm run test:smoke && npm run test:heredoc && npm run test:migration-guard && npm run test:nx-guard && npm run test:project-config && npm run test:find-start-path && npm run test:git-guards-command && npm run test:e2e",
|
|
35
|
+
"test": "npm run test:smoke && npm run test:heredoc && npm run test:migration-guard && npm run test:nx-guard && npm run test:project-config && npm run test:find-start-path && npm run test:git-guards-command && npm run test:gh-guard && npm run test:comment-guard && npm run test:e2e",
|
|
36
36
|
"test:smoke": "npx tsx test/smoke-test.ts",
|
|
37
37
|
"test:heredoc": "npx tsx test/heredoc-test.ts",
|
|
38
38
|
"test:e2e": "npx tsx test/e2e-install-test.ts",
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
"test:project-config": "npx tsx test/project-config-test.ts",
|
|
42
42
|
"test:find-start-path": "npx tsx test/find-start-path-test.ts",
|
|
43
43
|
"test:git-guards-command": "npx tsx test/git-guards-command-test.ts",
|
|
44
|
+
"test:gh-guard": "npx tsx test/gh-guard-test.ts",
|
|
45
|
+
"test:comment-guard": "npx tsx test/comment-guard-test.ts",
|
|
44
46
|
"lint": "tsc --noEmit",
|
|
45
47
|
"typecheck": "tsc --noEmit"
|
|
46
48
|
},
|
package/src/checker.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { tokenize } from "./tokenizer";
|
|
12
|
+
import { forEachPositionalArg } from "./rules/args";
|
|
12
13
|
import {
|
|
13
14
|
DELEGATION_COMMANDS,
|
|
14
15
|
FILE_READING_COMMANDS,
|
|
@@ -25,6 +26,7 @@ import { extractHeredocs } from "./heredoc";
|
|
|
25
26
|
import { checkAws } from "./rules/aws";
|
|
26
27
|
import { checkDocker } from "./rules/docker";
|
|
27
28
|
import { checkFileReading } from "./rules/file-reading";
|
|
29
|
+
import { checkGh } from "./rules/gh";
|
|
28
30
|
import { checkGit } from "./rules/git";
|
|
29
31
|
import { checkPathSensitive } from "./rules/path-sensitive";
|
|
30
32
|
import { isOutsideCwd, resolvePath } from "./rules/path-utils";
|
|
@@ -84,6 +86,9 @@ export function checkCommand(
|
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
/**
|
|
89
|
+
* Checks tokenized commands, including nested delegates, for reasons to block.
|
|
90
|
+
* Returns a blocking result with the first reason found, or `SAFE` otherwise.
|
|
91
|
+
*
|
|
87
92
|
* @param cwd Directory relative paths resolve against for *this* call. At the top
|
|
88
93
|
* level this is the real working directory; recursive calls pass the effective
|
|
89
94
|
* `currentCwd` tracked at the point of recursion (see the `cd` handling below).
|
|
@@ -182,18 +187,15 @@ export function checkTokens(
|
|
|
182
187
|
|
|
183
188
|
// Quoted-command wrappers: the first positional argument is a command
|
|
184
189
|
if (QUOTED_COMMAND_WRAPPERS.has(token)) {
|
|
185
|
-
let
|
|
186
|
-
|
|
187
|
-
const arg = tokens[j];
|
|
188
|
-
if (!arg || SHELL_OPERATORS.has(arg)) break;
|
|
189
|
-
if (arg.startsWith("-")) {
|
|
190
|
-
j++;
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
190
|
+
let dangerous: CheckResult | undefined;
|
|
191
|
+
forEachPositionalArg(tokens, i, (arg) => {
|
|
193
192
|
const r = checkCommand(arg, currentCwd ?? cwd, depth + 1, boundaryCwd, options);
|
|
194
|
-
if (r.dangerous)
|
|
195
|
-
|
|
196
|
-
|
|
193
|
+
if (r.dangerous) {
|
|
194
|
+
dangerous = r;
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
if (dangerous) return dangerous;
|
|
197
199
|
i++;
|
|
198
200
|
continue;
|
|
199
201
|
}
|
|
@@ -295,6 +297,13 @@ export function checkTokens(
|
|
|
295
297
|
continue;
|
|
296
298
|
}
|
|
297
299
|
|
|
300
|
+
if (token === "gh") {
|
|
301
|
+
const r = checkGh(tokens, i);
|
|
302
|
+
if (r.dangerous) return r;
|
|
303
|
+
i++;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
|
|
298
307
|
if (token === "git") {
|
|
299
308
|
const r = checkGit(tokens, i, options.gitGuardsEnabled);
|
|
300
309
|
if (r.dangerous) return r;
|