@sureshsankaran/destructive-check-plugin 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/LICENSE +21 -0
- package/README.md +173 -0
- package/package.json +44 -0
- package/src/index.ts +494 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenCode
|
|
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,173 @@
|
|
|
1
|
+
# @opencode-ai/destructive-check-plugin
|
|
2
|
+
|
|
3
|
+
OpenCode plugin that automatically detects destructive commands and requires user permission before execution.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
This plugin protects against potentially harmful operations by detecting:
|
|
8
|
+
|
|
9
|
+
- **File deletion**: `rm -rf`, `rm -fr` with dangerous paths (/, /home, /etc, etc.)
|
|
10
|
+
- **Git operations**: `git push --force`, `git reset --hard`, `git clean -f`
|
|
11
|
+
- **Database commands**: `DROP TABLE`, `DELETE FROM`, `TRUNCATE`
|
|
12
|
+
- **System operations**: `chmod 777`, `dd`, `mkfs`, `format`
|
|
13
|
+
- **Elevated privileges**: `sudo rm`, `sudo chmod`, `sudo dd`
|
|
14
|
+
- **Container/Cloud**: `kubectl delete`, `docker rm -f`, `aws s3 rm --recursive`
|
|
15
|
+
- **Package managers**: `npm cache clean --force`, `pip uninstall -y`
|
|
16
|
+
- **Network**: `iptables -F`, `ufw reset`
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
### 1. Install the package
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bun add @opencode-ai/destructive-check-plugin
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. Enable in your OpenCode configuration
|
|
27
|
+
|
|
28
|
+
Add to your `opencode.json` or `.opencode/opencode.json`:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"plugin": ["@opencode-ai/destructive-check-plugin"]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or use a file path:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"plugin": ["file:///path/to/node_modules/@opencode-ai/destructive-check-plugin/dist/index.js"]
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
Once installed, the plugin automatically:
|
|
47
|
+
|
|
48
|
+
1. **Monitors** all tool calls and bash commands
|
|
49
|
+
2. **Detects** destructive patterns using regex matching
|
|
50
|
+
3. **Warns** in the console when a destructive command is detected
|
|
51
|
+
4. **Requests permission** from the user before executing dangerous operations
|
|
52
|
+
|
|
53
|
+
### Severity Levels
|
|
54
|
+
|
|
55
|
+
- **🔴 CRITICAL**: File deletion on system paths, sudo operations, system-level changes
|
|
56
|
+
- **🟠 HIGH**: Git history rewriting, database modifications, container deletions
|
|
57
|
+
- **🟡 MEDIUM**: Package manager operations, network configuration changes
|
|
58
|
+
|
|
59
|
+
### Example Output
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
[destructive-check] 🔴 CRITICAL destructive command detected
|
|
63
|
+
Category: Dangerous File Deletion
|
|
64
|
+
Command: rm -rf /
|
|
65
|
+
⚠️ This operation could cause data loss or system damage!
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Check Plugin Status
|
|
69
|
+
|
|
70
|
+
Use the built-in tool to check plugin status:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// In OpenCode
|
|
74
|
+
destructive - check - status
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"enabled": true,
|
|
82
|
+
"session": {
|
|
83
|
+
"id": "session-123",
|
|
84
|
+
"checked": 42,
|
|
85
|
+
"permissionsRequested": 3
|
|
86
|
+
},
|
|
87
|
+
"global": {
|
|
88
|
+
"checked": 156,
|
|
89
|
+
"permissionsRequested": 12
|
|
90
|
+
},
|
|
91
|
+
"patterns": {
|
|
92
|
+
"categories": ["rmDangerous", "git", "database", ...],
|
|
93
|
+
"total": 45
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Detected Patterns
|
|
99
|
+
|
|
100
|
+
### File Deletion
|
|
101
|
+
|
|
102
|
+
- `rm /`, `rm -rf /home`, `rm -rf /etc`
|
|
103
|
+
- `rm .git`, `rm package.json`
|
|
104
|
+
- `rm $HOME`, `rm ~/*`
|
|
105
|
+
|
|
106
|
+
### Git Operations
|
|
107
|
+
|
|
108
|
+
- `git push --force`, `git push -f`
|
|
109
|
+
- `git reset --hard`, `git reset HEAD~1`
|
|
110
|
+
- `git clean -f`, `git stash drop`
|
|
111
|
+
- `git branch -D`
|
|
112
|
+
|
|
113
|
+
### Database
|
|
114
|
+
|
|
115
|
+
- `DROP TABLE`, `DROP DATABASE`
|
|
116
|
+
- `DELETE FROM table` (without WHERE)
|
|
117
|
+
- `TRUNCATE TABLE`
|
|
118
|
+
|
|
119
|
+
### System
|
|
120
|
+
|
|
121
|
+
- `chmod 777 /`, `chown user /`
|
|
122
|
+
- `dd of=/dev/sda`
|
|
123
|
+
- `mkfs`, `format`, `fdisk`
|
|
124
|
+
|
|
125
|
+
### Containers/Cloud
|
|
126
|
+
|
|
127
|
+
- `kubectl delete namespace`
|
|
128
|
+
- `docker rm -f`, `docker system prune -a`
|
|
129
|
+
- `aws ec2 terminate-instances`
|
|
130
|
+
- `gcloud compute instances delete`
|
|
131
|
+
|
|
132
|
+
## Development
|
|
133
|
+
|
|
134
|
+
### Build
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
bun run build
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Test Locally
|
|
141
|
+
|
|
142
|
+
Link the package locally:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
cd packages/destructive-check-plugin
|
|
146
|
+
bun link
|
|
147
|
+
cd /your/opencode/project
|
|
148
|
+
bun link @opencode-ai/destructive-check-plugin
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Add to your `opencode.json`:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"plugin": ["@opencode-ai/destructive-check-plugin"]
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## How It Works
|
|
160
|
+
|
|
161
|
+
The plugin uses three hooks:
|
|
162
|
+
|
|
163
|
+
1. **`tool.execute.before`**: Logs warnings when destructive commands are detected
|
|
164
|
+
2. **`permission.ask`**: Intercepts permission requests and flags destructive operations
|
|
165
|
+
3. **`tool.execute.after`**: Logs completion and any system blocks
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT
|
|
170
|
+
|
|
171
|
+
## Contributing
|
|
172
|
+
|
|
173
|
+
Issues and pull requests are welcome at [github.com/anomalyco/opencode](https://github.com/anomalyco/opencode).
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sureshsankaran/destructive-check-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenCode plugin that detects and requires user permission for destructive commands before execution",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"types": "src/index.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": [
|
|
9
|
+
"src",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"opencode",
|
|
16
|
+
"opencode-plugin",
|
|
17
|
+
"destructive-commands",
|
|
18
|
+
"safety",
|
|
19
|
+
"security",
|
|
20
|
+
"git",
|
|
21
|
+
"rm",
|
|
22
|
+
"database",
|
|
23
|
+
"docker",
|
|
24
|
+
"kubernetes"
|
|
25
|
+
],
|
|
26
|
+
"author": "OpenCode",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/anomalyco/opencode.git",
|
|
31
|
+
"directory": "packages/destructive-check-plugin"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/anomalyco/opencode/tree/main/packages/destructive-check-plugin#readme",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/anomalyco/opencode/issues"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@opencode-ai/plugin": "*"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Destructive Command Check Plugin for OpenCode
|
|
3
|
+
*
|
|
4
|
+
* Automatically checks for destructive commands before any tool/bash call.
|
|
5
|
+
* This plugin runs for all sessions and agents, protecting against potentially
|
|
6
|
+
* harmful operations by asking for user permission.
|
|
7
|
+
*
|
|
8
|
+
* Destructive patterns detected:
|
|
9
|
+
* - rm -rf, rm -fr, rm -r with dangerous paths
|
|
10
|
+
* - git push --force, git reset (--hard/--soft/--mixed) moving HEAD
|
|
11
|
+
* - DROP TABLE, DELETE FROM, TRUNCATE
|
|
12
|
+
* - chmod 777, chown on system dirs
|
|
13
|
+
* - dd commands
|
|
14
|
+
* - format/mkfs commands
|
|
15
|
+
* - sudo rm, sudo chmod, sudo chown
|
|
16
|
+
* - kubectl delete, docker rm -f
|
|
17
|
+
* - aws s3 rm --recursive
|
|
18
|
+
*
|
|
19
|
+
* The plugin will:
|
|
20
|
+
* 1. Detect destructive patterns in bash commands
|
|
21
|
+
* 2. Detect destructive file operations (deleting important files)
|
|
22
|
+
* 3. Ask for user permission before executing dangerous operations
|
|
23
|
+
*
|
|
24
|
+
* Installation:
|
|
25
|
+
* Add to your opencode.json config:
|
|
26
|
+
* {
|
|
27
|
+
* "plugin": ["file:///path/to/.opencode/plugins/destructive-check.ts"]
|
|
28
|
+
* }
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import type { Plugin } from "@opencode-ai/plugin"
|
|
32
|
+
|
|
33
|
+
// Destructive command patterns to check
|
|
34
|
+
const DESTRUCTIVE_PATTERNS = {
|
|
35
|
+
// File deletion - dangerous patterns
|
|
36
|
+
rmDangerous: [
|
|
37
|
+
/\brm\s+(-[rRf]+\s+)*[\/~]\s*$/i, // rm / or rm ~
|
|
38
|
+
/\brm\s+(-[rRf]+\s+)*\/\*/, // rm /*
|
|
39
|
+
/\brm\s+(-[rRf]+\s+)*~\/\*/, // rm ~/*
|
|
40
|
+
/\brm\s+(-[rRf]+\s+)*\$HOME\b/i, // rm $HOME
|
|
41
|
+
/\brm\s+(-[rRf]+\s+)*\/home\b/i, // rm /home
|
|
42
|
+
/\brm\s+(-[rRf]+\s+)*\/etc\b/i, // rm /etc
|
|
43
|
+
/\brm\s+(-[rRf]+\s+)*\/var\b/i, // rm /var
|
|
44
|
+
/\brm\s+(-[rRf]+\s+)*\/usr\b/i, // rm /usr
|
|
45
|
+
/\brm\s+(-[rRf]+\s+)*\/bin\b/i, // rm /bin
|
|
46
|
+
/\brm\s+(-[rRf]+\s+)*\/sbin\b/i, // rm /sbin
|
|
47
|
+
/\brm\s+(-[rRf]+\s+)*\/boot\b/i, // rm /boot
|
|
48
|
+
/\brm\s+(-[rRf]+\s+)*\/lib\b/i, // rm /lib
|
|
49
|
+
/\brm\s+(-[rRf]+\s+)*\/opt\b/i, // rm /opt
|
|
50
|
+
/\brm\s+(-[rRf]+\s+)*\/root\b/i, // rm /root
|
|
51
|
+
/\brm\s+(-[rRf]+\s+)*\/sys\b/i, // rm /sys
|
|
52
|
+
/\brm\s+(-[rRf]+\s+)*\/proc\b/i, // rm /proc
|
|
53
|
+
/\brm\s+(-[rRf]+\s+)*\/dev\b/i, // rm /dev
|
|
54
|
+
/\brm\s+(-[rRf]+\s+)*\/mnt\b/i, // rm /mnt
|
|
55
|
+
/\brm\s+(-[rRf]+\s+)*\/tmp\b/i, // rm /tmp
|
|
56
|
+
/\brm\s+(-[rRf]+\s+)*\.git\b/i, // rm .git
|
|
57
|
+
/\brm\s+(-[rRf]+\s+)*node_modules\b/i, // rm node_modules (dangerous in wrong dir)
|
|
58
|
+
],
|
|
59
|
+
|
|
60
|
+
// Git destructive operations
|
|
61
|
+
git: [
|
|
62
|
+
/\bgit\s+push\s+.*--force\b/i, // git push --force
|
|
63
|
+
/\bgit\s+push\s+.*-f\b/i, // git push -f
|
|
64
|
+
// git reset patterns: catch both HEAD movement (rewrites history) and --hard (discards changes)
|
|
65
|
+
/\bgit\s+reset\s+(--hard|--soft|--mixed)?\s*(HEAD|@)[\~\^]/i, // git reset moving HEAD (rewrites commit history)
|
|
66
|
+
/\bgit\s+reset\s+--hard\b/i, // git reset --hard (discards working directory changes)
|
|
67
|
+
/\bgit\s+clean\s+.*-f/i, // git clean -f
|
|
68
|
+
/\bgit\s+checkout\s+--\s+\./i, // git checkout -- .
|
|
69
|
+
/\bgit\s+stash\s+drop/i, // git stash drop
|
|
70
|
+
/\bgit\s+branch\s+.*-D\b/i, // git branch -D
|
|
71
|
+
/\bgit\s+reflog\s+expire/i, // git reflog expire
|
|
72
|
+
/\bgit\s+gc\s+--prune/i, // git gc --prune
|
|
73
|
+
],
|
|
74
|
+
|
|
75
|
+
// Database destructive operations
|
|
76
|
+
database: [
|
|
77
|
+
/\bDROP\s+(TABLE|DATABASE|SCHEMA|INDEX)\b/i,
|
|
78
|
+
/\bTRUNCATE\s+TABLE\b/i,
|
|
79
|
+
/\bDELETE\s+FROM\s+\S+\s*(;|\s*$)/i, // DELETE without WHERE
|
|
80
|
+
/\bALTER\s+TABLE\s+\S+\s+DROP\b/i,
|
|
81
|
+
],
|
|
82
|
+
|
|
83
|
+
// System destructive operations
|
|
84
|
+
system: [
|
|
85
|
+
/\bchmod\s+(-R\s+)?777\s+\//i, // chmod 777 /
|
|
86
|
+
/\bchown\s+(-R\s+)?\S+\s+\//i, // chown on root
|
|
87
|
+
/\bdd\s+.*of=\/dev\//i, // dd to device
|
|
88
|
+
/\bmkfs\b/i, // Format filesystem
|
|
89
|
+
/\bformat\s+[a-z]:/i, // Windows format
|
|
90
|
+
/\bfdisk\b/i, // Partition tool
|
|
91
|
+
/\bparted\b/i, // Partition tool
|
|
92
|
+
],
|
|
93
|
+
|
|
94
|
+
// Elevated privileges with destructive commands
|
|
95
|
+
sudo: [
|
|
96
|
+
/\bsudo\s+rm\s+(-[rRf]+\s+)*\//i, // sudo rm on root
|
|
97
|
+
/\bsudo\s+chmod\b/i, // sudo chmod
|
|
98
|
+
/\bsudo\s+chown\b/i, // sudo chown
|
|
99
|
+
/\bsudo\s+dd\b/i, // sudo dd
|
|
100
|
+
/\bsudo\s+mkfs\b/i, // sudo mkfs
|
|
101
|
+
],
|
|
102
|
+
|
|
103
|
+
// Container/cloud destructive operations
|
|
104
|
+
container: [
|
|
105
|
+
/\bkubectl\s+delete\s+(namespace|ns|pod|deployment|service)\b/i,
|
|
106
|
+
/\bdocker\s+rm\s+.*-f/i, // docker rm -f
|
|
107
|
+
/\bdocker\s+system\s+prune\s+.*-a/i, // docker system prune -a
|
|
108
|
+
/\bdocker\s+volume\s+rm\b/i, // docker volume rm
|
|
109
|
+
/\baws\s+s3\s+rm\s+.*--recursive\b/i, // aws s3 rm --recursive
|
|
110
|
+
/\baws\s+ec2\s+terminate-instances\b/i, // terminate EC2
|
|
111
|
+
/\bgcloud\s+.*delete\b/i, // gcloud delete operations
|
|
112
|
+
],
|
|
113
|
+
|
|
114
|
+
// Package manager destructive operations
|
|
115
|
+
packages: [
|
|
116
|
+
/\bnpm\s+cache\s+clean\s+--force\b/i, // npm cache clean --force
|
|
117
|
+
/\byarn\s+cache\s+clean\b/i, // yarn cache clean
|
|
118
|
+
/\bpip\s+uninstall\s+.*-y\b/i, // pip uninstall -y (auto-confirm)
|
|
119
|
+
/\bbrew\s+uninstall\s+--force\b/i, // brew uninstall --force
|
|
120
|
+
],
|
|
121
|
+
|
|
122
|
+
// Network destructive operations
|
|
123
|
+
network: [
|
|
124
|
+
/\biptables\s+.*-F\b/i, // Flush iptables
|
|
125
|
+
/\biptables\s+.*--flush\b/i, // Flush iptables
|
|
126
|
+
/\bufw\s+reset\b/i, // Reset firewall
|
|
127
|
+
],
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// File paths that are dangerous to delete/modify
|
|
131
|
+
const DANGEROUS_PATHS = [
|
|
132
|
+
"/",
|
|
133
|
+
"/*",
|
|
134
|
+
"/home",
|
|
135
|
+
"/etc",
|
|
136
|
+
"/var",
|
|
137
|
+
"/usr",
|
|
138
|
+
"/bin",
|
|
139
|
+
"/sbin",
|
|
140
|
+
"/boot",
|
|
141
|
+
"/lib",
|
|
142
|
+
"/opt",
|
|
143
|
+
"/root",
|
|
144
|
+
"/sys",
|
|
145
|
+
"/proc",
|
|
146
|
+
"/dev",
|
|
147
|
+
"~",
|
|
148
|
+
"~/",
|
|
149
|
+
"$HOME",
|
|
150
|
+
".git",
|
|
151
|
+
".env",
|
|
152
|
+
".ssh",
|
|
153
|
+
"package.json",
|
|
154
|
+
"package-lock.json",
|
|
155
|
+
"yarn.lock",
|
|
156
|
+
"bun.lockb",
|
|
157
|
+
"Cargo.toml",
|
|
158
|
+
"go.mod",
|
|
159
|
+
"pyproject.toml",
|
|
160
|
+
"requirements.txt",
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
type DestructiveMatch = {
|
|
164
|
+
category: string
|
|
165
|
+
pattern: string
|
|
166
|
+
severity: "critical" | "high" | "medium"
|
|
167
|
+
command: string
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check if a command matches any destructive pattern
|
|
171
|
+
function checkCommand(command: string): DestructiveMatch | null {
|
|
172
|
+
for (const [category, patterns] of Object.entries(DESTRUCTIVE_PATTERNS)) {
|
|
173
|
+
for (const pattern of patterns) {
|
|
174
|
+
if (pattern.test(command)) {
|
|
175
|
+
const severity = getSeverity(category)
|
|
176
|
+
return {
|
|
177
|
+
category,
|
|
178
|
+
pattern: pattern.toString(),
|
|
179
|
+
severity,
|
|
180
|
+
command,
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return null
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Determine severity based on category
|
|
189
|
+
function getSeverity(category: string): "critical" | "high" | "medium" {
|
|
190
|
+
if (category === "rmDangerous" || category === "sudo" || category === "system") {
|
|
191
|
+
return "critical"
|
|
192
|
+
}
|
|
193
|
+
if (category === "git" || category === "database" || category === "container") {
|
|
194
|
+
return "high"
|
|
195
|
+
}
|
|
196
|
+
return "medium"
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Get human-readable label for category
|
|
200
|
+
function getCategoryLabel(category: string): string {
|
|
201
|
+
const labels: Record<string, string> = {
|
|
202
|
+
rmDangerous: "Dangerous File Deletion",
|
|
203
|
+
git: "Destructive Git Operation",
|
|
204
|
+
database: "Database Modification",
|
|
205
|
+
system: "System-Level Change",
|
|
206
|
+
sudo: "Elevated Privilege Operation",
|
|
207
|
+
container: "Container/Cloud Operation",
|
|
208
|
+
packages: "Package Management",
|
|
209
|
+
network: "Network Configuration",
|
|
210
|
+
}
|
|
211
|
+
return labels[category] || category
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check if a file path is dangerous
|
|
215
|
+
function isDangerousPath(path: string): boolean {
|
|
216
|
+
const normalized = path.replace(/\\/g, "/").toLowerCase()
|
|
217
|
+
return DANGEROUS_PATHS.some((dangerous) => {
|
|
218
|
+
const normalizedDangerous = dangerous.toLowerCase()
|
|
219
|
+
return (
|
|
220
|
+
normalized === normalizedDangerous ||
|
|
221
|
+
normalized.startsWith(normalizedDangerous + "/") ||
|
|
222
|
+
normalized.endsWith("/" + normalizedDangerous)
|
|
223
|
+
)
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Statistics tracking
|
|
228
|
+
type Stats = {
|
|
229
|
+
checked: number
|
|
230
|
+
permissionsRequested: number
|
|
231
|
+
lastMatch?: DestructiveMatch
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Plugin state per session
|
|
235
|
+
const sessions: Record<string, Stats> = {}
|
|
236
|
+
|
|
237
|
+
function getStats(sessionID: string): Stats {
|
|
238
|
+
if (!sessions[sessionID]) {
|
|
239
|
+
sessions[sessionID] = { checked: 0, permissionsRequested: 0 }
|
|
240
|
+
}
|
|
241
|
+
return sessions[sessionID]
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Global stats
|
|
245
|
+
const global: Stats = { checked: 0, permissionsRequested: 0 }
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Destructive Command Check Plugin
|
|
249
|
+
*/
|
|
250
|
+
async function destructiveCheck(_input: {
|
|
251
|
+
client: any
|
|
252
|
+
project: any
|
|
253
|
+
worktree: string
|
|
254
|
+
directory: string
|
|
255
|
+
serverUrl: any
|
|
256
|
+
$: any
|
|
257
|
+
}) {
|
|
258
|
+
return {
|
|
259
|
+
// Tool for checking plugin status
|
|
260
|
+
tool: {
|
|
261
|
+
"destructive-check-status": {
|
|
262
|
+
description: "Get the status of the destructive command check plugin",
|
|
263
|
+
args: {},
|
|
264
|
+
async execute(_args: {}, ctx: { sessionID: string }) {
|
|
265
|
+
const stats = getStats(ctx.sessionID)
|
|
266
|
+
return JSON.stringify(
|
|
267
|
+
{
|
|
268
|
+
enabled: true,
|
|
269
|
+
session: {
|
|
270
|
+
id: ctx.sessionID,
|
|
271
|
+
...stats,
|
|
272
|
+
},
|
|
273
|
+
global: global,
|
|
274
|
+
patterns: {
|
|
275
|
+
categories: Object.keys(DESTRUCTIVE_PATTERNS),
|
|
276
|
+
total: Object.values(DESTRUCTIVE_PATTERNS).flat().length,
|
|
277
|
+
},
|
|
278
|
+
dangerousPaths: DANGEROUS_PATHS.length,
|
|
279
|
+
},
|
|
280
|
+
null,
|
|
281
|
+
2,
|
|
282
|
+
)
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
// Check before any tool executes - logs warnings for awareness
|
|
288
|
+
async ["tool.execute.before"](
|
|
289
|
+
hookInput: { tool: string; sessionID: string; callID: string },
|
|
290
|
+
output: { args: Record<string, unknown> },
|
|
291
|
+
): Promise<void> {
|
|
292
|
+
const stats = getStats(hookInput.sessionID)
|
|
293
|
+
stats.checked++
|
|
294
|
+
global.checked++
|
|
295
|
+
|
|
296
|
+
const tool = hookInput.tool.toLowerCase()
|
|
297
|
+
const args = output.args
|
|
298
|
+
|
|
299
|
+
// Check bash/shell commands
|
|
300
|
+
if (tool === "bash" || tool === "shell" || tool === "execute") {
|
|
301
|
+
const command = (args?.command as string) || ""
|
|
302
|
+
if (command) {
|
|
303
|
+
const match = checkCommand(command)
|
|
304
|
+
if (match) {
|
|
305
|
+
console.warn(
|
|
306
|
+
`[destructive-check] Detected ${match.severity.toUpperCase()} destructive command - permission will be requested`,
|
|
307
|
+
)
|
|
308
|
+
console.warn(` Category: ${match.category}`)
|
|
309
|
+
console.warn(` Command: ${match.command.slice(0, 100)}${match.command.length > 100 ? "..." : ""}`)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Check file write/delete operations
|
|
315
|
+
if (tool === "write" || tool === "edit" || tool === "delete" || tool === "remove") {
|
|
316
|
+
const filePath = (args?.filePath as string) || (args?.path as string) || ""
|
|
317
|
+
if (filePath && isDangerousPath(filePath)) {
|
|
318
|
+
console.warn(`[destructive-check] Dangerous file operation detected - permission will be requested`)
|
|
319
|
+
console.warn(` Tool: ${tool}`)
|
|
320
|
+
console.warn(` Path: ${filePath}`)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Check git operations via tool
|
|
325
|
+
if (tool === "git") {
|
|
326
|
+
const subcommand = (args?.subcommand as string) || (args?.command as string) || ""
|
|
327
|
+
const fullCommand = `git ${subcommand}`
|
|
328
|
+
const match = checkCommand(fullCommand)
|
|
329
|
+
if (match) {
|
|
330
|
+
console.warn(`[destructive-check] Destructive git operation detected - permission will be requested`)
|
|
331
|
+
console.warn(` Severity: ${match.severity.toUpperCase()}`)
|
|
332
|
+
console.warn(` Command: ${fullCommand}`)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
// Permission hook to require user confirmation for destructive operations
|
|
338
|
+
async ["permission.ask"](
|
|
339
|
+
input: {
|
|
340
|
+
id: string
|
|
341
|
+
// Old permission system uses 'type', new system uses 'permission'
|
|
342
|
+
type?: string
|
|
343
|
+
permission?: string
|
|
344
|
+
pattern?: string | string[]
|
|
345
|
+
patterns?: string[]
|
|
346
|
+
sessionID: string
|
|
347
|
+
messageID?: string
|
|
348
|
+
callID?: string
|
|
349
|
+
title?: string
|
|
350
|
+
metadata: Record<string, unknown>
|
|
351
|
+
time?: { created: number }
|
|
352
|
+
},
|
|
353
|
+
output: { status: "ask" | "deny" | "allow"; metadata?: Record<string, unknown> },
|
|
354
|
+
): Promise<void> {
|
|
355
|
+
const stats = getStats(input.sessionID)
|
|
356
|
+
|
|
357
|
+
// Get permission type from either old or new system
|
|
358
|
+
const permissionType = input.permission || input.type || ""
|
|
359
|
+
|
|
360
|
+
// Check if this is a bash/command execution permission
|
|
361
|
+
if (permissionType === "bash" || permissionType === "command" || permissionType === "shell") {
|
|
362
|
+
// Get commands from patterns (new system) or metadata/title (old system)
|
|
363
|
+
const patterns =
|
|
364
|
+
input.patterns || (Array.isArray(input.pattern) ? input.pattern : input.pattern ? [input.pattern] : [])
|
|
365
|
+
|
|
366
|
+
// Check each pattern individually for destructive commands
|
|
367
|
+
for (const pattern of patterns) {
|
|
368
|
+
const match = checkCommand(pattern)
|
|
369
|
+
if (match) {
|
|
370
|
+
stats.permissionsRequested++
|
|
371
|
+
global.permissionsRequested++
|
|
372
|
+
stats.lastMatch = match
|
|
373
|
+
|
|
374
|
+
// Add metadata to the permission request for UI display
|
|
375
|
+
const severityEmoji = match.severity === "critical" ? "🔴" : match.severity === "high" ? "🟠" : "🟡"
|
|
376
|
+
const categoryLabel = getCategoryLabel(match.category)
|
|
377
|
+
|
|
378
|
+
// Enhance metadata with destructive command information
|
|
379
|
+
if (!output.metadata) output.metadata = {}
|
|
380
|
+
output.metadata.destructive = {
|
|
381
|
+
severity: match.severity,
|
|
382
|
+
category: match.category,
|
|
383
|
+
categoryLabel,
|
|
384
|
+
command: pattern,
|
|
385
|
+
warning: `${severityEmoji} ${match.severity.toUpperCase()}: ${categoryLabel}`,
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
console.warn(
|
|
389
|
+
`[destructive-check] ${severityEmoji} ${match.severity.toUpperCase()} destructive command detected`,
|
|
390
|
+
)
|
|
391
|
+
console.warn(` Category: ${categoryLabel}`)
|
|
392
|
+
console.warn(` Command: ${pattern.slice(0, 100)}${pattern.length > 100 ? "..." : ""}`)
|
|
393
|
+
console.warn(` ⚠️ This operation could cause data loss or system damage!`)
|
|
394
|
+
|
|
395
|
+
// Ask for permission for all severity levels
|
|
396
|
+
output.status = "ask"
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Also check joined patterns and metadata as fallback
|
|
402
|
+
const command = patterns.join(" ") || (input.metadata?.command as string) || input.title || ""
|
|
403
|
+
if (command && patterns.length === 0) {
|
|
404
|
+
const match = checkCommand(command)
|
|
405
|
+
if (match) {
|
|
406
|
+
stats.permissionsRequested++
|
|
407
|
+
global.permissionsRequested++
|
|
408
|
+
stats.lastMatch = match
|
|
409
|
+
|
|
410
|
+
// Add metadata to the permission request for UI display
|
|
411
|
+
const severityEmoji = match.severity === "critical" ? "🔴" : match.severity === "high" ? "🟠" : "🟡"
|
|
412
|
+
const categoryLabel = getCategoryLabel(match.category)
|
|
413
|
+
|
|
414
|
+
// Enhance metadata with destructive command information
|
|
415
|
+
if (!output.metadata) output.metadata = {}
|
|
416
|
+
output.metadata.destructive = {
|
|
417
|
+
severity: match.severity,
|
|
418
|
+
category: match.category,
|
|
419
|
+
categoryLabel,
|
|
420
|
+
command,
|
|
421
|
+
warning: `${severityEmoji} ${match.severity.toUpperCase()}: ${categoryLabel}`,
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
console.warn(
|
|
425
|
+
`[destructive-check] ${severityEmoji} ${match.severity.toUpperCase()} destructive command detected`,
|
|
426
|
+
)
|
|
427
|
+
console.warn(` Category: ${categoryLabel}`)
|
|
428
|
+
console.warn(` Command: ${command.slice(0, 100)}${command.length > 100 ? "..." : ""}`)
|
|
429
|
+
console.warn(` ⚠️ This operation could cause data loss or system damage!`)
|
|
430
|
+
|
|
431
|
+
// Ask for permission for all severity levels
|
|
432
|
+
output.status = "ask"
|
|
433
|
+
return
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Check file operations
|
|
439
|
+
if (permissionType === "write" || permissionType === "edit" || permissionType === "delete") {
|
|
440
|
+
const patterns =
|
|
441
|
+
input.patterns || (Array.isArray(input.pattern) ? input.pattern : input.pattern ? [input.pattern] : [])
|
|
442
|
+
for (const p of patterns) {
|
|
443
|
+
if (isDangerousPath(p)) {
|
|
444
|
+
stats.permissionsRequested++
|
|
445
|
+
global.permissionsRequested++
|
|
446
|
+
|
|
447
|
+
// Add metadata for dangerous file operations
|
|
448
|
+
if (!output.metadata) output.metadata = {}
|
|
449
|
+
output.metadata.destructive = {
|
|
450
|
+
severity: "critical",
|
|
451
|
+
category: "file",
|
|
452
|
+
categoryLabel: "Dangerous File Operation",
|
|
453
|
+
path: p,
|
|
454
|
+
warning: "🔴 CRITICAL: Dangerous file operation",
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
console.warn(`[destructive-check] 🔴 CRITICAL: Dangerous file operation detected`)
|
|
458
|
+
console.warn(` Operation: ${permissionType}`)
|
|
459
|
+
console.warn(` Path: ${p}`)
|
|
460
|
+
console.warn(` ⚠️ This operation could cause data loss or system damage!`)
|
|
461
|
+
|
|
462
|
+
// Ask for permission for dangerous file operations
|
|
463
|
+
output.status = "ask"
|
|
464
|
+
return
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
|
|
470
|
+
// After tool execution - log results for destructive operations
|
|
471
|
+
async ["tool.execute.after"](
|
|
472
|
+
hookInput: { tool: string; sessionID: string; callID: string },
|
|
473
|
+
result: { title: string; output: string; metadata: Record<string, unknown> },
|
|
474
|
+
): Promise<void> {
|
|
475
|
+
const tool = hookInput.tool.toLowerCase()
|
|
476
|
+
|
|
477
|
+
// Log completion of potentially dangerous operations
|
|
478
|
+
if (tool === "bash" || tool === "shell") {
|
|
479
|
+
const output = result.output || ""
|
|
480
|
+
// Check for error messages that might indicate dangerous operation attempted
|
|
481
|
+
if (
|
|
482
|
+
output.includes("Permission denied") ||
|
|
483
|
+
output.includes("Operation not permitted") ||
|
|
484
|
+
output.includes("cannot remove") ||
|
|
485
|
+
output.includes("rm: refusing")
|
|
486
|
+
) {
|
|
487
|
+
console.log(`[destructive-check] Dangerous operation was blocked by system`)
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export default destructiveCheck
|