@giuseppe.trisciuoglio/pi-prevent-destructive-commands 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/.github/workflows/ci.yml +37 -0
- package/.github/workflows/publish.yml +47 -0
- package/CHANGELOG.md +49 -0
- package/CONTRIBUTING.md +105 -0
- package/LICENSE +21 -0
- package/README.md +239 -0
- package/icon.png +0 -0
- package/package.json +59 -0
- package/src/checker.ts +257 -0
- package/src/config.ts +333 -0
- package/src/index.ts +108 -0
- package/src/migration-guard.ts +76 -0
- package/src/nx-guard.ts +287 -0
- package/src/rules/aws.ts +21 -0
- package/src/rules/docker.ts +41 -0
- package/src/rules/file-reading.ts +25 -0
- package/src/rules/git.ts +92 -0
- package/src/rules/path-sensitive.ts +51 -0
- package/src/rules/path-utils.ts +66 -0
- package/src/rules/types.ts +15 -0
- package/src/tokenizer.ts +116 -0
- package/test/e2e-install-test.ts +185 -0
- package/test/migration-guard-test.ts +37 -0
- package/test/nx-guard-test.ts +109 -0
- package/test/smoke-test.ts +181 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
name: Typecheck & Test (Node ${{ matrix.node-version }})
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
node-version: [22.x, 24.x]
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout
|
|
23
|
+
uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Setup Node.js ${{ matrix.node-version }}
|
|
26
|
+
uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: ${{ matrix.node-version }}
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: npm install
|
|
32
|
+
|
|
33
|
+
- name: Typecheck
|
|
34
|
+
run: npm run typecheck
|
|
35
|
+
|
|
36
|
+
- name: Run tests
|
|
37
|
+
run: npm test
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish:
|
|
12
|
+
name: Publish to npm
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
id-token: write
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout
|
|
20
|
+
uses: actions/checkout@v6
|
|
21
|
+
|
|
22
|
+
- name: Setup Node.js
|
|
23
|
+
uses: actions/setup-node@v6
|
|
24
|
+
with:
|
|
25
|
+
node-version: 24.x
|
|
26
|
+
registry-url: https://registry.npmjs.org
|
|
27
|
+
|
|
28
|
+
- name: Verify release tag matches package.json version
|
|
29
|
+
run: |
|
|
30
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
31
|
+
PKG_VERSION=$(node -p "require('./package.json').version")
|
|
32
|
+
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
|
|
33
|
+
echo "::error::Release tag ($GITHUB_REF_NAME) does not match package.json version ($PKG_VERSION)"
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
- name: Install dependencies
|
|
38
|
+
run: npm ci
|
|
39
|
+
|
|
40
|
+
- name: Typecheck
|
|
41
|
+
run: npm run typecheck
|
|
42
|
+
|
|
43
|
+
- name: Run tests
|
|
44
|
+
run: npm test
|
|
45
|
+
|
|
46
|
+
- name: Publish to npm
|
|
47
|
+
run: npm publish --access public
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- `cd` within an analyzed command chain (e.g. `cd /; rm etc/passwd`) is now tracked: relative paths in subsequent path-sensitive commands resolve against the effective directory instead of the untouched real cwd, while the *original* cwd remains the safety boundary. `( … )` subshell scoping is respected — a `cd` inside parentheses no longer leaks out. A `cd` to a statically unresolvable target (`cd "$VAR"`, `cd -`) causes subsequent path-sensitive commands to be blocked conservatively.
|
|
12
|
+
- `xargs`/`parallel` delegating to a path-sensitive command (`rm`, `rmdir`, `shred`, ...) with no explicit target token (e.g. `echo x | xargs rm`) is now blocked: the real arguments arrive via stdin at runtime and can't be statically verified. An explicit target is still checked normally against the working directory.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- Nx workspace protection: blocks the agent from modifying or deleting existing `package.json`, `tsconfig.json`, `tsconfig.base.json`, `tsconfig.lib.json`, and `tsconfig.spec.json` files anywhere beneath a workspace with `nx.json`. Missing files remain creatable. The guard covers direct file tools, patches, common shell mutations, and package-manager dependency commands.
|
|
16
|
+
- CI GitHub Actions workflow: typecheck and smoke tests on Node.js 22 and 24 for every push/PR to `main`.
|
|
17
|
+
- Publish GitHub Actions workflow: publishes to npm when a GitHub Release is published, after verifying the release tag matches `package.json` and re-running typecheck/tests.
|
|
18
|
+
- End-to-end installation test (`test/e2e-install-test.ts`, `npm run test:e2e`): uses the real `@earendil-works/pi-coding-agent` package to verify manual installation and `pi install` are both discovered and loaded correctly, and that the loaded `tool_call` handler blocks/allows commands as expected. `npm test` now runs the smoke suite and the e2e test.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- `package.json`'s `pi` manifest declared an `entryPoint` field that pi's extension loader does not read, so the extension was never discovered when manually installed by cloning into `<agentDir>/extensions/prevent-destructive-commands` as documented in the README. Replaced it with the `pi.extensions` array field that pi actually resolves.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Reorganized source files into `src/` and tests into `test/`.
|
|
25
|
+
- Split `checker.ts` into a slim recursive dispatcher plus per-category rule handlers under `src/rules/` (`git.ts`, `docker.ts`, `aws.ts`, `file-reading.ts`, `path-sensitive.ts`, `path-utils.ts`, `types.ts`). No behavior changes.
|
|
26
|
+
|
|
27
|
+
## [1.0.0] - 2025-07-16
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- Initial release of `prevent-destructive-commands` extension for pi.
|
|
31
|
+
- Unconditional hard-block of destructive bash commands before execution.
|
|
32
|
+
- Support for destructive Git operations: `git reset --hard`, `git clean`, `git push --force`, `git branch -D`, `git rebase`, `git filter-branch`, `git filter-repo`, and more.
|
|
33
|
+
- Support for destructive Docker operations: `docker rm`, `docker rmi`, `docker container/volume/image/network rm`, `docker * prune`, `docker compose down -v`, and more.
|
|
34
|
+
- Support for destructive AWS CLI operations: `aws s3 rm`, `aws ec2 terminate-instances`, `aws rds delete-db-instance`, `aws cloudformation delete-stack`, and more (50+ subcommands).
|
|
35
|
+
- Path-sensitive protection for `rm`, `rmdir`, `unlink`, `shred`: blocks targets outside the working directory.
|
|
36
|
+
- Optional sensitive file read protection (`cat`, `grep`, etc. on `.env`, SSH keys, credentials) — disabled by default due to false positives.
|
|
37
|
+
- Recursive analysis through command wrappers: `sudo`, `env`, `timeout`, `nice`, `nohup`, `ionice`, `time`.
|
|
38
|
+
- Recursive analysis through shell invocations: `bash -c "..."`, `sh -c "..."`, etc.
|
|
39
|
+
- Recursive analysis through `find -exec`, `xargs`, `parallel`.
|
|
40
|
+
- Recursive analysis through quoted command wrappers: `watch`, `strace`, `ltrace`.
|
|
41
|
+
- Pipeline and concatenation support: `|`, `&&`, `||`, `;`.
|
|
42
|
+
- Configurable behavior flags in `config.ts`.
|
|
43
|
+
- Comprehensive smoke test suite with 70+ test cases.
|
|
44
|
+
- Full TypeScript support with type definitions.
|
|
45
|
+
|
|
46
|
+
### Security
|
|
47
|
+
- Hard-block design: the agent receives a reason and must find an alternative.
|
|
48
|
+
- No interactive confirmation — protection works in non-interactive modes (`-p`, JSON, RPC).
|
|
49
|
+
- Maximum nesting depth limit (5) to prevent obfuscation attacks.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Contributing to prevent-destructive-commands
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing! This document provides guidelines for contributing to this project.
|
|
4
|
+
|
|
5
|
+
## Code of Conduct
|
|
6
|
+
|
|
7
|
+
Be respectful, constructive, and inclusive in all interactions.
|
|
8
|
+
|
|
9
|
+
## How to Contribute
|
|
10
|
+
|
|
11
|
+
### Reporting Issues
|
|
12
|
+
|
|
13
|
+
- Check if the issue already exists before creating a new one.
|
|
14
|
+
- Provide a clear description, steps to reproduce, and expected vs. actual behavior.
|
|
15
|
+
- Include the command that triggered (or failed to trigger) the block.
|
|
16
|
+
- Mention your pi version and Node.js version.
|
|
17
|
+
|
|
18
|
+
### Suggesting Enhancements
|
|
19
|
+
|
|
20
|
+
- Open an issue with the "enhancement" label.
|
|
21
|
+
- Describe the use case and the benefit.
|
|
22
|
+
- If proposing a new blocked command category, explain why it should be considered destructive.
|
|
23
|
+
|
|
24
|
+
### Pull Requests
|
|
25
|
+
|
|
26
|
+
1. Fork the repository and create a feature branch.
|
|
27
|
+
2. Make your changes with clear, focused commits.
|
|
28
|
+
3. Run the smoke tests: `npm test` (or `npx tsx test/smoke-test.ts`).
|
|
29
|
+
4. Update the README.md if your change affects user-facing behavior.
|
|
30
|
+
5. Update CHANGELOG.md under the "Unreleased" section.
|
|
31
|
+
6. Submit a pull request with a clear description.
|
|
32
|
+
|
|
33
|
+
## Development Setup
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Clone the repository
|
|
37
|
+
git clone https://github.com/giuseppe-trisciuoglio/pi-prevent-destructive-commands.git
|
|
38
|
+
cd pi-prevent-destructive-commands
|
|
39
|
+
|
|
40
|
+
# Install dependencies
|
|
41
|
+
npm install
|
|
42
|
+
|
|
43
|
+
# Run smoke tests
|
|
44
|
+
npm test
|
|
45
|
+
|
|
46
|
+
# Type check
|
|
47
|
+
npm run typecheck
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Project Structure
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
prevent-destructive-commands/
|
|
54
|
+
├── src/
|
|
55
|
+
│ ├── index.ts # Extension entry point (factory + tool_call hook)
|
|
56
|
+
│ ├── config.ts # Blacklists and behavior flags
|
|
57
|
+
│ ├── tokenizer.ts # Shell tokenizer (shlex-like)
|
|
58
|
+
│ ├── checker.ts # Recursive command walker (wrappers/shell/find/xargs)
|
|
59
|
+
│ └── rules/ # Per-category destructive-command handlers
|
|
60
|
+
│ ├── types.ts
|
|
61
|
+
│ ├── path-utils.ts
|
|
62
|
+
│ ├── git.ts
|
|
63
|
+
│ ├── docker.ts
|
|
64
|
+
│ ├── aws.ts
|
|
65
|
+
│ ├── file-reading.ts
|
|
66
|
+
│ └── path-sensitive.ts
|
|
67
|
+
├── test/
|
|
68
|
+
│ ├── smoke-test.ts # Standalone test suite
|
|
69
|
+
│ └── e2e-install-test.ts # End-to-end: verifies real installation/discovery by pi
|
|
70
|
+
├── tsconfig.json # TypeScript configuration
|
|
71
|
+
└── package.json # Package metadata
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Adding New Blocked Commands
|
|
75
|
+
|
|
76
|
+
To add a new command category:
|
|
77
|
+
|
|
78
|
+
1. Add the command patterns to the appropriate set in `src/config.ts`.
|
|
79
|
+
2. Add the checker logic to a new or existing handler in `src/rules/`, following the existing handler pattern (see `src/rules/git.ts` or `src/rules/docker.ts`), and wire it into the dispatch loop in `src/checker.ts`.
|
|
80
|
+
3. Add test cases to `test/smoke-test.ts`.
|
|
81
|
+
4. Update the README.md documentation.
|
|
82
|
+
|
|
83
|
+
## Style Guide
|
|
84
|
+
|
|
85
|
+
- Use English for all code comments and documentation.
|
|
86
|
+
- Use 2-space indentation (tabs converted to 2 spaces).
|
|
87
|
+
- Prefer explicit types over implicit ones.
|
|
88
|
+
- Keep functions focused and modular.
|
|
89
|
+
|
|
90
|
+
## Continuous Integration
|
|
91
|
+
|
|
92
|
+
Every push and pull request to `main` runs the [CI workflow](.github/workflows/ci.yml), which typechecks and runs the smoke test suite and the end-to-end installation test on Node.js 22 and 24.
|
|
93
|
+
|
|
94
|
+
## Releasing
|
|
95
|
+
|
|
96
|
+
Publishing to npm is handled by the [Publish workflow](.github/workflows/publish.yml) and is restricted to maintainers:
|
|
97
|
+
|
|
98
|
+
1. Bump `version` in `package.json` and move the relevant `CHANGELOG.md` entries from "Unreleased" to a new version section.
|
|
99
|
+
2. Commit and merge the version bump to `main`.
|
|
100
|
+
3. Create a GitHub Release with a tag matching `v<version>` (e.g. `v1.1.0`) for the merged commit.
|
|
101
|
+
4. Publishing the Release triggers the workflow, which verifies the tag matches `package.json`, re-runs typecheck/tests, and publishes to npm through the trusted GitHub Actions publisher.
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
By contributing, you agree that your contributions will be licensed under the MIT License.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Giuseppe Trisciuoglio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Prevent Destructive Commands
|
|
2
|
+
|
|
3
|
+
[](https://github.com/giuseppe-trisciuoglio/pi-prevent-destructive-commands/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/giuseppe-trisciuoglio/pi-prevent-destructive-commands)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://pi.dev)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
|
|
9
|
+
> **Unconditionally blocks destructive bash commands before execution.** A pi extension that guards your codebase against accidental data loss from `rm`, `git reset --hard`, `docker rm`, `aws delete` operations, and more.
|
|
10
|
+
|
|
11
|
+
A faithful port of Claude's [`prevent-destructive-commands.py`](https://github.com/giuseppe-trisciuoglio/developer-kit/blob/main/plugins/developer-kit-core/hooks/prevent-destructive-commands.py) hook, adapted for the [pi coding agent](https://pi.dev). Same rule set, same recursive tokenizer — same peace of mind.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **Hard-block protection** — Dangerous commands are blocked unconditionally. The agent receives a clear reason and must find a safe alternative.
|
|
18
|
+
- **Works in all modes** — Protection is active even in non-interactive sessions (`-p`, JSON, RPC).
|
|
19
|
+
- **Recursive analysis** — Traverses command wrappers, shell invocations, pipelines, and nested commands to catch obfuscated attacks.
|
|
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
|
+
- **Configurable** — Tune protection levels via simple flags in `src/config.ts`.
|
|
22
|
+
- **Zero dependencies** — Lightweight, fast, and self-contained.
|
|
23
|
+
- **79+ test cases** — Comprehensive smoke test suite validates all blocking rules.
|
|
24
|
+
|
|
25
|
+
## What Gets Blocked
|
|
26
|
+
|
|
27
|
+
All rules are defined in [`src/config.ts`](src/config.ts) and can be customized.
|
|
28
|
+
|
|
29
|
+
| Category | Examples |
|
|
30
|
+
|----------|----------|
|
|
31
|
+
| **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
|
+
| **Git add/commit** | `git add`, `git commit` *(see `ENABLE_GIT_ADD_COMMIT_BLOCK` flag)* |
|
|
33
|
+
| **rm / path-sensitive** | `rm`, `rmdir`, `shred`, `unlink` targeting paths **outside** the working directory (e.g., `/etc`, `~`, `..`). Targets inside cwd are allowed. |
|
|
34
|
+
| **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` |
|
|
35
|
+
| **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`) |
|
|
36
|
+
| **Sensitive file reads** | `cat`, `grep`, etc. on `.env`, SSH keys, `.pem` files — **disabled by default** via `ENABLE_SENSITIVE_FILE_CHECK` *(see Configuration)* |
|
|
37
|
+
| **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. |
|
|
38
|
+
|
|
39
|
+
### Recursive Analysis
|
|
40
|
+
|
|
41
|
+
The analyzer traverses common command wrappers and nested structures so a destructive command can't hide:
|
|
42
|
+
|
|
43
|
+
- **Wrappers**: `sudo`, `env`, `timeout`, `nice`, `nohup`, `ionice`, `time`
|
|
44
|
+
- **Shell invocations**: `bash -c "..."`, `sh -c "..."`, `zsh -c "..."`
|
|
45
|
+
- **Execution delegation**: `find -exec`, `xargs`, `parallel`
|
|
46
|
+
- **Quoted wrappers**: `watch "rm foo"`, `strace "..."`
|
|
47
|
+
- **Pipelines & chains**: `|`, `&&`, `||`, `;`
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
### Via pi Marketplace
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pi install @giuseppetrisciuoglio/pi-prevent-destructive-commands
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Manual Installation
|
|
60
|
+
|
|
61
|
+
1. Clone this repository into your pi extensions directory:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git clone https://github.com/giuseppe-trisciuoglio/pi-prevent-destructive-commands.git \
|
|
65
|
+
~/.pi/agent/extensions/prevent-destructive-commands
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
2. Reload pi to discover the extension:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pi /reload
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The extension is automatically discovered by pi in all projects.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Configuration
|
|
79
|
+
|
|
80
|
+
Edit the constants in [`src/config.ts`](src/config.ts) to tune protection:
|
|
81
|
+
|
|
82
|
+
| Flag | Default | Description |
|
|
83
|
+
|------|---------|-------------|
|
|
84
|
+
| `ENABLE_GIT_ADD_COMMIT_BLOCK` | `true` | Blocks `git add` and `git commit`. Set to `false` if you want the agent to create commits autonomously. |
|
|
85
|
+
| `ENABLE_SENSITIVE_FILE_CHECK` | `false` | Blocks reading of sensitive files (`.env`, SSH keys, credentials). Disabled by default due to false positives from substring matching (`config` matches `tsconfig`, `vite.config`; `.env` matches `.environment.ts`). Enable only if needed and consider refining `SENSITIVE_FILE_PATTERNS`. |
|
|
86
|
+
| `MAX_NESTING_DEPTH` | `5` | Maximum command nesting depth before treating as obfuscated. |
|
|
87
|
+
|
|
88
|
+
After any change, reload the extension:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pi /reload
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Project Structure
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
prevent-destructive-commands/
|
|
100
|
+
├── src/
|
|
101
|
+
│ ├── index.ts # Extension entry point (factory + tool_call hook)
|
|
102
|
+
│ ├── config.ts # Blacklists and behavior flags — tune protection here
|
|
103
|
+
│ ├── tokenizer.ts # Shell tokenizer (shlex-like)
|
|
104
|
+
│ ├── checker.ts # Recursive command walker (wrappers/shell/find/xargs)
|
|
105
|
+
│ ├── migration-guard.ts
|
|
106
|
+
│ ├── nx-guard.ts # Protects existing Nx package and TypeScript configuration files
|
|
107
|
+
│ └── rules/ # Per-category destructive-command handlers
|
|
108
|
+
│ ├── types.ts # Shared CheckResult type + helpers
|
|
109
|
+
│ ├── path-utils.ts # cwd-relative path resolution
|
|
110
|
+
│ ├── git.ts # git reset --hard, push --force, ...
|
|
111
|
+
│ ├── docker.ts # docker rm, system prune, ...
|
|
112
|
+
│ ├── aws.ts # aws s3 rm, ec2 terminate-instances, ...
|
|
113
|
+
│ ├── file-reading.ts # sensitive file read detection
|
|
114
|
+
│ └── path-sensitive.ts # rm/rmdir/... outside-cwd detection
|
|
115
|
+
├── test/
|
|
116
|
+
│ ├── smoke-test.ts # Standalone test suite (79+ cases)
|
|
117
|
+
│ ├── migration-guard-test.ts
|
|
118
|
+
│ ├── nx-guard-test.ts # Verifies Nx configuration protection and allowed creation
|
|
119
|
+
│ └── e2e-install-test.ts # End-to-end: verifies real installation/discovery by pi
|
|
120
|
+
├── tsconfig.json # TypeScript configuration
|
|
121
|
+
├── package.json # Package metadata for pi marketplace
|
|
122
|
+
└── README.md # This file
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Testing
|
|
128
|
+
|
|
129
|
+
Run the full test suite (command safety, migration, Nx, and end-to-end checks):
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
npm test
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Smoke tests
|
|
136
|
+
|
|
137
|
+
Standalone tests for the tokenizer/checker logic (no dependency on pi itself):
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm run test:smoke
|
|
141
|
+
npm run test:nx-guard
|
|
142
|
+
|
|
143
|
+
# Or directly with tsx
|
|
144
|
+
npx tsx test/smoke-test.ts
|
|
145
|
+
|
|
146
|
+
# Or with jiti
|
|
147
|
+
npx jiti test/smoke-test.ts
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### End-to-end installation test
|
|
151
|
+
|
|
152
|
+
Uses the real `@earendil-works/pi-coding-agent` package to verify the extension actually installs the way the [Installation](#installation) section describes:
|
|
153
|
+
|
|
154
|
+
- The `pi.extensions` entry in `package.json` resolves to an existing file.
|
|
155
|
+
- Manual installation (cloning into `<agentDir>/extensions/prevent-destructive-commands`) is discovered and loaded by pi's own extension loader.
|
|
156
|
+
- `pi install <path>` records the package and pi can subsequently discover and load it.
|
|
157
|
+
- Once loaded through that real pipeline, the registered `tool_call` handler still blocks/allows commands correctly.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm run test:e2e
|
|
161
|
+
|
|
162
|
+
# Or directly with tsx
|
|
163
|
+
npx tsx test/e2e-install-test.ts
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The test suite covers:
|
|
167
|
+
- All destructive Git operations
|
|
168
|
+
- Existing Nx package and TypeScript configuration protection, including direct tool writes, patches, shell redirection, and package-manager dependency changes
|
|
169
|
+
- Path-sensitive `rm` protection
|
|
170
|
+
- Docker destructive commands
|
|
171
|
+
- AWS CLI destructive subcommands
|
|
172
|
+
- Wrapper traversal (`sudo`, `env`, `timeout`)
|
|
173
|
+
- Shell invocation traversal (`bash -c`)
|
|
174
|
+
- `find -exec` and `xargs` delegation
|
|
175
|
+
- Pipeline and concatenation handling
|
|
176
|
+
- Git global flag parsing (`-C`, `--git-dir`)
|
|
177
|
+
- Edge cases and safe command verification
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Known Limitations
|
|
182
|
+
|
|
183
|
+
As with the original Claude plugin, the analysis is static and therefore cannot cover everything:
|
|
184
|
+
|
|
185
|
+
| Limitation | Example | Explanation |
|
|
186
|
+
|------------|---------|-------------|
|
|
187
|
+
| **Unknown wrappers** | Custom destructive tools | The extension covers known patterns; unknown wrappers or custom destructive tools are not intercepted. |
|
|
188
|
+
|
|
189
|
+
Two cases that used to be listed here are now handled:
|
|
190
|
+
|
|
191
|
+
- **Arguments via stdin/pipe** (`echo x \| xargs rm`) — a path-sensitive command (`rm`, `rmdir`, `shred`, ...) reached through `xargs`/`parallel` with no explicit target token is now blocked: its real targets arrive via stdin at runtime and can't be statically verified, so it's treated as dangerous rather than assumed safe. An explicit target (e.g. `xargs rm ./known-file`) is still checked normally against the working directory.
|
|
192
|
+
- **`cd` in command** (`cd /; rm etc/passwd`) — the analyzer now tracks `cd` within the same command chain (including `( … )` subshell scoping) and resolves subsequent relative paths against that effective directory, while still enforcing the *original* working directory as the safety boundary. `cd /; rm etc/passwd` now correctly resolves to `/etc/passwd` and is blocked. When a `cd` target itself can't be resolved statically (e.g. `cd "$VAR"`, `cd -`), any path-sensitive command that follows is blocked conservatively.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## How It Works
|
|
197
|
+
|
|
198
|
+
When pi attempts to execute a bash command, this extension intercepts the `tool_call` event and:
|
|
199
|
+
|
|
200
|
+
1. **Tokenizes** the command string using a shlex-like shell tokenizer that respects quotes and escapes.
|
|
201
|
+
2. **Analyzes** the token stream recursively, traversing wrappers, shell invocations, and pipelines.
|
|
202
|
+
3. **Blocks** if any destructive pattern is detected, returning a clear reason to the agent.
|
|
203
|
+
4. **Allows** safe commands to pass through without modification.
|
|
204
|
+
|
|
205
|
+
The agent never receives an interactive prompt — the block is final and must be handled by finding a safe alternative.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Contributing
|
|
210
|
+
|
|
211
|
+
Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
212
|
+
|
|
213
|
+
Quick start for contributors:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
git clone https://github.com/giuseppe-trisciuoglio/pi-prevent-destructive-commands.git
|
|
217
|
+
cd pi-prevent-destructive-commands
|
|
218
|
+
npm install
|
|
219
|
+
npm test
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Changelog
|
|
225
|
+
|
|
226
|
+
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Acknowledgments
|
|
237
|
+
|
|
238
|
+
- Inspired by and ported from Claude's [`prevent-destructive-commands.py`](https://github.com/giuseppe-trisciuoglio/developer-kit/blob/main/plugins/developer-kit-core/hooks/prevent-destructive-commands.py) hook.
|
|
239
|
+
- Built for the [pi coding agent](https://pi.dev) ecosystem.
|
package/icon.png
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@giuseppe.trisciuoglio/pi-prevent-destructive-commands",
|
|
3
|
+
"version": "1.0.0",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"pi",
|
|
7
|
+
"pi-extension",
|
|
8
|
+
"ai-agent",
|
|
9
|
+
"safety",
|
|
10
|
+
"destructive-commands",
|
|
11
|
+
"bash",
|
|
12
|
+
"shell",
|
|
13
|
+
"guard",
|
|
14
|
+
"security",
|
|
15
|
+
"rm",
|
|
16
|
+
"git",
|
|
17
|
+
"docker",
|
|
18
|
+
"aws"
|
|
19
|
+
],
|
|
20
|
+
"author": "Giuseppe Trisciuoglio",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/giuseppe-trisciuoglio/pi-prevent-destructive-commands.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/giuseppe-trisciuoglio/pi-prevent-destructive-commands/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/giuseppe-trisciuoglio/pi-prevent-destructive-commands#readme",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "npm run test:smoke && npm run test:migration-guard && npm run test:nx-guard && npm run test:e2e",
|
|
35
|
+
"test:smoke": "npx tsx test/smoke-test.ts",
|
|
36
|
+
"test:e2e": "npx tsx test/e2e-install-test.ts",
|
|
37
|
+
"test:migration-guard": "npx tsx test/migration-guard-test.ts",
|
|
38
|
+
"test:nx-guard": "npx tsx test/nx-guard-test.ts",
|
|
39
|
+
"lint": "tsc --noEmit",
|
|
40
|
+
"typecheck": "tsc --noEmit"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@earendil-works/pi-coding-agent": ">=0.1.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@earendil-works/pi-coding-agent": ">=0.1.0",
|
|
47
|
+
"typescript": "^5.4.0",
|
|
48
|
+
"tsx": "^4.7.0"
|
|
49
|
+
},
|
|
50
|
+
"pi": {
|
|
51
|
+
"name": "prevent-destructive-commands",
|
|
52
|
+
"displayName": "Prevent Destructive Commands",
|
|
53
|
+
"description": "Blocks destructive bash commands (rm, git reset, docker rm, AWS delete, etc.) before execution.",
|
|
54
|
+
"category": "security",
|
|
55
|
+
"extensions": [
|
|
56
|
+
"./src/index.ts"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
}
|