@sureshsankaran/opencode-destructive-check 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/README.md +64 -0
- package/package.json +35 -0
- package/src/index.ts +391 -0
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @sureshsankaran/opencode-destructive-check
|
|
2
|
+
|
|
3
|
+
An OpenCode plugin that automatically checks for destructive commands before any tool/bash call and asks for user permission before executing them.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Detects destructive commands across multiple categories
|
|
8
|
+
- Asks for permission for all severity levels (critical, high, medium)
|
|
9
|
+
- Works automatically for all sessions and agents
|
|
10
|
+
- Provides a status tool to check plugin statistics
|
|
11
|
+
|
|
12
|
+
## Detected Destructive Patterns
|
|
13
|
+
|
|
14
|
+
| Category | Examples | Severity |
|
|
15
|
+
| --------------- | ------------------------------------------------------------------- | -------- |
|
|
16
|
+
| **rmDangerous** | `rm -rf /`, `rm -rf ~`, `rm .git` | Critical |
|
|
17
|
+
| **sudo** | `sudo rm -rf /`, `sudo chmod`, `sudo chown` | Critical |
|
|
18
|
+
| **system** | `chmod 777 /`, `dd of=/dev/`, `mkfs`, `fdisk` | Critical |
|
|
19
|
+
| **git** | `git push --force`, `git reset --hard`, `git clean -f` | High |
|
|
20
|
+
| **database** | `DROP TABLE`, `DELETE FROM` (without WHERE), `TRUNCATE` | High |
|
|
21
|
+
| **container** | `kubectl delete namespace`, `docker rm -f`, `aws s3 rm --recursive` | High |
|
|
22
|
+
| **packages** | `npm cache clean --force`, `pip uninstall -y` | Medium |
|
|
23
|
+
| **network** | `iptables -F`, `ufw reset` | Medium |
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Add the plugin to your `opencode.json` or `.opencode/opencode.jsonc` config:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"plugin": ["@sureshsankaran/opencode-destructive-check"]
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## How It Works
|
|
36
|
+
|
|
37
|
+
1. **`tool.execute.before` hook**: Inspects every tool call before execution, logging warnings for dangerous patterns
|
|
38
|
+
2. **`permission.ask` hook**: Requires user confirmation for all detected destructive operations
|
|
39
|
+
3. **`tool.execute.after` hook**: Logs when the system blocks dangerous operations
|
|
40
|
+
|
|
41
|
+
When a destructive command is detected, the user will be prompted to approve or deny the operation, giving full control over whether to proceed.
|
|
42
|
+
|
|
43
|
+
## Available Tools
|
|
44
|
+
|
|
45
|
+
### `destructive-check-status`
|
|
46
|
+
|
|
47
|
+
Returns the current status of the plugin including:
|
|
48
|
+
|
|
49
|
+
- Number of commands checked
|
|
50
|
+
- Number of permissions requested
|
|
51
|
+
- Last matched destructive pattern
|
|
52
|
+
- Pattern categories and counts
|
|
53
|
+
|
|
54
|
+
## Dangerous Paths
|
|
55
|
+
|
|
56
|
+
The plugin also protects against file operations on dangerous paths:
|
|
57
|
+
|
|
58
|
+
- System directories: `/`, `/home`, `/etc`, `/var`, `/usr`, `/bin`, etc.
|
|
59
|
+
- User directories: `~`, `$HOME`
|
|
60
|
+
- Project files: `.git`, `.env`, `.ssh`, `package.json`, `Cargo.toml`, etc.
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sureshsankaran/opencode-destructive-check",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenCode plugin that checks for destructive commands before any tool/bash call and asks for permission",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"types": "src/index.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"opencode",
|
|
10
|
+
"opencode-plugin",
|
|
11
|
+
"security",
|
|
12
|
+
"destructive-commands",
|
|
13
|
+
"permission",
|
|
14
|
+
"safety"
|
|
15
|
+
],
|
|
16
|
+
"author": "Suresh Sankaran",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/anomalyco/opencode"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/anomalyco/opencode/issues"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"src"
|
|
27
|
+
],
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@opencode-ai/plugin": ">=1.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@opencode-ai/plugin": "^1.2.2",
|
|
33
|
+
"typescript": "^5.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
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
|
|
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
|
+
|
|
25
|
+
import type { Plugin } from "@opencode-ai/plugin"
|
|
26
|
+
|
|
27
|
+
// Destructive command patterns to check
|
|
28
|
+
const DESTRUCTIVE_PATTERNS = {
|
|
29
|
+
// File deletion - dangerous patterns
|
|
30
|
+
rmDangerous: [
|
|
31
|
+
/\brm\s+(-[rRf]+\s+)*[\/~]\s*$/i, // rm / or rm ~
|
|
32
|
+
/\brm\s+(-[rRf]+\s+)*\/\*/, // rm /*
|
|
33
|
+
/\brm\s+(-[rRf]+\s+)*~\/\*/, // rm ~/*
|
|
34
|
+
/\brm\s+(-[rRf]+\s+)*\$HOME\b/i, // rm $HOME
|
|
35
|
+
/\brm\s+(-[rRf]+\s+)*\/home\b/i, // rm /home
|
|
36
|
+
/\brm\s+(-[rRf]+\s+)*\/etc\b/i, // rm /etc
|
|
37
|
+
/\brm\s+(-[rRf]+\s+)*\/var\b/i, // rm /var
|
|
38
|
+
/\brm\s+(-[rRf]+\s+)*\/usr\b/i, // rm /usr
|
|
39
|
+
/\brm\s+(-[rRf]+\s+)*\/bin\b/i, // rm /bin
|
|
40
|
+
/\brm\s+(-[rRf]+\s+)*\/sbin\b/i, // rm /sbin
|
|
41
|
+
/\brm\s+(-[rRf]+\s+)*\/boot\b/i, // rm /boot
|
|
42
|
+
/\brm\s+(-[rRf]+\s+)*\/lib\b/i, // rm /lib
|
|
43
|
+
/\brm\s+(-[rRf]+\s+)*\/opt\b/i, // rm /opt
|
|
44
|
+
/\brm\s+(-[rRf]+\s+)*\/root\b/i, // rm /root
|
|
45
|
+
/\brm\s+(-[rRf]+\s+)*\/sys\b/i, // rm /sys
|
|
46
|
+
/\brm\s+(-[rRf]+\s+)*\/proc\b/i, // rm /proc
|
|
47
|
+
/\brm\s+(-[rRf]+\s+)*\/dev\b/i, // rm /dev
|
|
48
|
+
/\brm\s+(-[rRf]+\s+)*\/mnt\b/i, // rm /mnt
|
|
49
|
+
/\brm\s+(-[rRf]+\s+)*\/tmp\b/i, // rm /tmp
|
|
50
|
+
/\brm\s+(-[rRf]+\s+)*\.git\b/i, // rm .git
|
|
51
|
+
/\brm\s+(-[rRf]+\s+)*node_modules\b/i, // rm node_modules (dangerous in wrong dir)
|
|
52
|
+
],
|
|
53
|
+
|
|
54
|
+
// Git destructive operations
|
|
55
|
+
git: [
|
|
56
|
+
/\bgit\s+push\s+.*--force\b/i, // git push --force
|
|
57
|
+
/\bgit\s+push\s+.*-f\b/i, // git push -f
|
|
58
|
+
/\bgit\s+reset\s+--hard\b/i, // git reset --hard
|
|
59
|
+
/\bgit\s+clean\s+.*-f/i, // git clean -f
|
|
60
|
+
/\bgit\s+checkout\s+--\s+\./i, // git checkout -- .
|
|
61
|
+
/\bgit\s+stash\s+drop/i, // git stash drop
|
|
62
|
+
/\bgit\s+branch\s+.*-D\b/i, // git branch -D
|
|
63
|
+
/\bgit\s+reflog\s+expire/i, // git reflog expire
|
|
64
|
+
/\bgit\s+gc\s+--prune/i, // git gc --prune
|
|
65
|
+
],
|
|
66
|
+
|
|
67
|
+
// Database destructive operations
|
|
68
|
+
database: [
|
|
69
|
+
/\bDROP\s+(TABLE|DATABASE|SCHEMA|INDEX)\b/i,
|
|
70
|
+
/\bTRUNCATE\s+TABLE\b/i,
|
|
71
|
+
/\bDELETE\s+FROM\s+\S+\s*(;|\s*$)/i, // DELETE without WHERE
|
|
72
|
+
/\bALTER\s+TABLE\s+\S+\s+DROP\b/i,
|
|
73
|
+
],
|
|
74
|
+
|
|
75
|
+
// System destructive operations
|
|
76
|
+
system: [
|
|
77
|
+
/\bchmod\s+(-R\s+)?777\s+\//i, // chmod 777 /
|
|
78
|
+
/\bchown\s+(-R\s+)?\S+\s+\//i, // chown on root
|
|
79
|
+
/\bdd\s+.*of=\/dev\//i, // dd to device
|
|
80
|
+
/\bmkfs\b/i, // Format filesystem
|
|
81
|
+
/\bformat\s+[a-z]:/i, // Windows format
|
|
82
|
+
/\bfdisk\b/i, // Partition tool
|
|
83
|
+
/\bparted\b/i, // Partition tool
|
|
84
|
+
],
|
|
85
|
+
|
|
86
|
+
// Elevated privileges with destructive commands
|
|
87
|
+
sudo: [
|
|
88
|
+
/\bsudo\s+rm\s+(-[rRf]+\s+)*\//i, // sudo rm on root
|
|
89
|
+
/\bsudo\s+chmod\b/i, // sudo chmod
|
|
90
|
+
/\bsudo\s+chown\b/i, // sudo chown
|
|
91
|
+
/\bsudo\s+dd\b/i, // sudo dd
|
|
92
|
+
/\bsudo\s+mkfs\b/i, // sudo mkfs
|
|
93
|
+
],
|
|
94
|
+
|
|
95
|
+
// Container/cloud destructive operations
|
|
96
|
+
container: [
|
|
97
|
+
/\bkubectl\s+delete\s+(namespace|ns|pod|deployment|service)\b/i,
|
|
98
|
+
/\bdocker\s+rm\s+.*-f/i, // docker rm -f
|
|
99
|
+
/\bdocker\s+system\s+prune\s+.*-a/i, // docker system prune -a
|
|
100
|
+
/\bdocker\s+volume\s+rm\b/i, // docker volume rm
|
|
101
|
+
/\baws\s+s3\s+rm\s+.*--recursive\b/i, // aws s3 rm --recursive
|
|
102
|
+
/\baws\s+ec2\s+terminate-instances\b/i, // terminate EC2
|
|
103
|
+
/\bgcloud\s+.*delete\b/i, // gcloud delete operations
|
|
104
|
+
],
|
|
105
|
+
|
|
106
|
+
// Package manager destructive operations
|
|
107
|
+
packages: [
|
|
108
|
+
/\bnpm\s+cache\s+clean\s+--force\b/i, // npm cache clean --force
|
|
109
|
+
/\byarn\s+cache\s+clean\b/i, // yarn cache clean
|
|
110
|
+
/\bpip\s+uninstall\s+.*-y\b/i, // pip uninstall -y (auto-confirm)
|
|
111
|
+
/\bbrew\s+uninstall\s+--force\b/i, // brew uninstall --force
|
|
112
|
+
],
|
|
113
|
+
|
|
114
|
+
// Network destructive operations
|
|
115
|
+
network: [
|
|
116
|
+
/\biptables\s+.*-F\b/i, // Flush iptables
|
|
117
|
+
/\biptables\s+.*--flush\b/i, // Flush iptables
|
|
118
|
+
/\bufw\s+reset\b/i, // Reset firewall
|
|
119
|
+
],
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// File paths that are dangerous to delete/modify
|
|
123
|
+
const DANGEROUS_PATHS = [
|
|
124
|
+
"/",
|
|
125
|
+
"/*",
|
|
126
|
+
"/home",
|
|
127
|
+
"/etc",
|
|
128
|
+
"/var",
|
|
129
|
+
"/usr",
|
|
130
|
+
"/bin",
|
|
131
|
+
"/sbin",
|
|
132
|
+
"/boot",
|
|
133
|
+
"/lib",
|
|
134
|
+
"/opt",
|
|
135
|
+
"/root",
|
|
136
|
+
"/sys",
|
|
137
|
+
"/proc",
|
|
138
|
+
"/dev",
|
|
139
|
+
"~",
|
|
140
|
+
"~/",
|
|
141
|
+
"$HOME",
|
|
142
|
+
".git",
|
|
143
|
+
".env",
|
|
144
|
+
".ssh",
|
|
145
|
+
"package.json",
|
|
146
|
+
"package-lock.json",
|
|
147
|
+
"yarn.lock",
|
|
148
|
+
"bun.lockb",
|
|
149
|
+
"Cargo.toml",
|
|
150
|
+
"go.mod",
|
|
151
|
+
"pyproject.toml",
|
|
152
|
+
"requirements.txt",
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
type DestructiveMatch = {
|
|
156
|
+
category: string
|
|
157
|
+
pattern: string
|
|
158
|
+
severity: "critical" | "high" | "medium"
|
|
159
|
+
command: string
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check if a command matches any destructive pattern
|
|
163
|
+
function checkCommand(command: string): DestructiveMatch | null {
|
|
164
|
+
for (const [category, patterns] of Object.entries(DESTRUCTIVE_PATTERNS)) {
|
|
165
|
+
for (const pattern of patterns) {
|
|
166
|
+
if (pattern.test(command)) {
|
|
167
|
+
const severity = getSeverity(category)
|
|
168
|
+
return {
|
|
169
|
+
category,
|
|
170
|
+
pattern: pattern.toString(),
|
|
171
|
+
severity,
|
|
172
|
+
command,
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return null
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Determine severity based on category
|
|
181
|
+
function getSeverity(category: string): "critical" | "high" | "medium" {
|
|
182
|
+
if (category === "rmDangerous" || category === "sudo" || category === "system") {
|
|
183
|
+
return "critical"
|
|
184
|
+
}
|
|
185
|
+
if (category === "git" || category === "database" || category === "container") {
|
|
186
|
+
return "high"
|
|
187
|
+
}
|
|
188
|
+
return "medium"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check if a file path is dangerous
|
|
192
|
+
function isDangerousPath(path: string): boolean {
|
|
193
|
+
const normalized = path.replace(/\\/g, "/").toLowerCase()
|
|
194
|
+
return DANGEROUS_PATHS.some((dangerous) => {
|
|
195
|
+
const normalizedDangerous = dangerous.toLowerCase()
|
|
196
|
+
return (
|
|
197
|
+
normalized === normalizedDangerous ||
|
|
198
|
+
normalized.startsWith(normalizedDangerous + "/") ||
|
|
199
|
+
normalized.endsWith("/" + normalizedDangerous)
|
|
200
|
+
)
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Statistics tracking
|
|
205
|
+
type Stats = {
|
|
206
|
+
checked: number
|
|
207
|
+
permissionsRequested: number
|
|
208
|
+
lastMatch?: DestructiveMatch
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Plugin state per session
|
|
212
|
+
const sessions: Record<string, Stats> = {}
|
|
213
|
+
|
|
214
|
+
function getStats(sessionID: string): Stats {
|
|
215
|
+
if (!sessions[sessionID]) {
|
|
216
|
+
sessions[sessionID] = { checked: 0, permissionsRequested: 0 }
|
|
217
|
+
}
|
|
218
|
+
return sessions[sessionID]
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Global stats
|
|
222
|
+
const globalStats: Stats = { checked: 0, permissionsRequested: 0 }
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Destructive Command Check Plugin
|
|
226
|
+
*/
|
|
227
|
+
const destructiveCheck: Plugin = async () => {
|
|
228
|
+
return {
|
|
229
|
+
// Tool for checking plugin status
|
|
230
|
+
tool: {
|
|
231
|
+
"destructive-check-status": {
|
|
232
|
+
description: "Get the status of the destructive command check plugin",
|
|
233
|
+
args: {},
|
|
234
|
+
async execute(_args: {}, ctx: { sessionID: string }) {
|
|
235
|
+
const stats = getStats(ctx.sessionID)
|
|
236
|
+
return JSON.stringify(
|
|
237
|
+
{
|
|
238
|
+
enabled: true,
|
|
239
|
+
session: {
|
|
240
|
+
id: ctx.sessionID,
|
|
241
|
+
...stats,
|
|
242
|
+
},
|
|
243
|
+
global: globalStats,
|
|
244
|
+
patterns: {
|
|
245
|
+
categories: Object.keys(DESTRUCTIVE_PATTERNS),
|
|
246
|
+
total: Object.values(DESTRUCTIVE_PATTERNS).flat().length,
|
|
247
|
+
},
|
|
248
|
+
dangerousPaths: DANGEROUS_PATHS.length,
|
|
249
|
+
},
|
|
250
|
+
null,
|
|
251
|
+
2,
|
|
252
|
+
)
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
// Check before any tool executes - logs warnings for awareness
|
|
258
|
+
async ["tool.execute.before"](
|
|
259
|
+
hookInput: { tool: string; sessionID: string; callID: string },
|
|
260
|
+
output: { args: Record<string, unknown> },
|
|
261
|
+
): Promise<void> {
|
|
262
|
+
const stats = getStats(hookInput.sessionID)
|
|
263
|
+
stats.checked++
|
|
264
|
+
globalStats.checked++
|
|
265
|
+
|
|
266
|
+
const tool = hookInput.tool.toLowerCase()
|
|
267
|
+
const args = output.args
|
|
268
|
+
|
|
269
|
+
// Check bash/shell commands
|
|
270
|
+
if (tool === "bash" || tool === "shell" || tool === "execute") {
|
|
271
|
+
const command = (args?.command as string) || ""
|
|
272
|
+
if (command) {
|
|
273
|
+
const match = checkCommand(command)
|
|
274
|
+
if (match) {
|
|
275
|
+
console.warn(
|
|
276
|
+
`[destructive-check] Detected ${match.severity.toUpperCase()} destructive command - permission will be requested`,
|
|
277
|
+
)
|
|
278
|
+
console.warn(` Category: ${match.category}`)
|
|
279
|
+
console.warn(` Command: ${match.command.slice(0, 100)}${match.command.length > 100 ? "..." : ""}`)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Check file write/delete operations
|
|
285
|
+
if (tool === "write" || tool === "edit" || tool === "delete" || tool === "remove") {
|
|
286
|
+
const filePath = (args?.filePath as string) || (args?.path as string) || ""
|
|
287
|
+
if (filePath && isDangerousPath(filePath)) {
|
|
288
|
+
console.warn(`[destructive-check] Dangerous file operation detected - permission will be requested`)
|
|
289
|
+
console.warn(` Tool: ${tool}`)
|
|
290
|
+
console.warn(` Path: ${filePath}`)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Check git operations via tool
|
|
295
|
+
if (tool === "git") {
|
|
296
|
+
const subcommand = (args?.subcommand as string) || (args?.command as string) || ""
|
|
297
|
+
const fullCommand = `git ${subcommand}`
|
|
298
|
+
const match = checkCommand(fullCommand)
|
|
299
|
+
if (match) {
|
|
300
|
+
console.warn(`[destructive-check] Destructive git operation detected - permission will be requested`)
|
|
301
|
+
console.warn(` Severity: ${match.severity.toUpperCase()}`)
|
|
302
|
+
console.warn(` Command: ${fullCommand}`)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
// Permission hook to require user confirmation for destructive operations
|
|
308
|
+
async ["permission.ask"](
|
|
309
|
+
input: {
|
|
310
|
+
id: string
|
|
311
|
+
type: string
|
|
312
|
+
pattern?: string | string[]
|
|
313
|
+
sessionID: string
|
|
314
|
+
messageID: string
|
|
315
|
+
callID?: string
|
|
316
|
+
title: string
|
|
317
|
+
metadata: Record<string, unknown>
|
|
318
|
+
time: { created: number }
|
|
319
|
+
},
|
|
320
|
+
output: { status: "ask" | "deny" | "allow" },
|
|
321
|
+
): Promise<void> {
|
|
322
|
+
const stats = getStats(input.sessionID)
|
|
323
|
+
|
|
324
|
+
// Check if this is a bash/command execution permission
|
|
325
|
+
if (input.type === "bash" || input.type === "command" || input.type === "shell") {
|
|
326
|
+
const command = (input.metadata?.command as string) || input.title || ""
|
|
327
|
+
|
|
328
|
+
if (command) {
|
|
329
|
+
const match = checkCommand(command)
|
|
330
|
+
if (match) {
|
|
331
|
+
stats.permissionsRequested++
|
|
332
|
+
globalStats.permissionsRequested++
|
|
333
|
+
stats.lastMatch = match
|
|
334
|
+
|
|
335
|
+
console.warn(
|
|
336
|
+
`[destructive-check] PERMISSION REQUIRED: ${match.severity.toUpperCase()} destructive command detected`,
|
|
337
|
+
)
|
|
338
|
+
console.warn(` Category: ${match.category}`)
|
|
339
|
+
console.warn(` Severity: ${match.severity}`)
|
|
340
|
+
console.warn(` Command: ${command.slice(0, 100)}${command.length > 100 ? "..." : ""}`)
|
|
341
|
+
|
|
342
|
+
// Ask for permission for all severity levels
|
|
343
|
+
output.status = "ask"
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Check file operations
|
|
350
|
+
if (input.type === "write" || input.type === "edit" || input.type === "delete") {
|
|
351
|
+
const patterns = Array.isArray(input.pattern) ? input.pattern : input.pattern ? [input.pattern] : []
|
|
352
|
+
for (const p of patterns) {
|
|
353
|
+
if (isDangerousPath(p)) {
|
|
354
|
+
stats.permissionsRequested++
|
|
355
|
+
globalStats.permissionsRequested++
|
|
356
|
+
console.warn(`[destructive-check] PERMISSION REQUIRED: Dangerous file operation detected`)
|
|
357
|
+
console.warn(` Path: ${p}`)
|
|
358
|
+
|
|
359
|
+
// Ask for permission for dangerous file operations
|
|
360
|
+
output.status = "ask"
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
// After tool execution - log results for destructive operations
|
|
368
|
+
async ["tool.execute.after"](
|
|
369
|
+
hookInput: { tool: string; sessionID: string; callID: string },
|
|
370
|
+
result: { title: string; output: string; metadata: Record<string, unknown> },
|
|
371
|
+
): Promise<void> {
|
|
372
|
+
const tool = hookInput.tool.toLowerCase()
|
|
373
|
+
|
|
374
|
+
// Log completion of potentially dangerous operations
|
|
375
|
+
if (tool === "bash" || tool === "shell") {
|
|
376
|
+
const output = result.output || ""
|
|
377
|
+
// Check for error messages that might indicate dangerous operation attempted
|
|
378
|
+
if (
|
|
379
|
+
output.includes("Permission denied") ||
|
|
380
|
+
output.includes("Operation not permitted") ||
|
|
381
|
+
output.includes("cannot remove") ||
|
|
382
|
+
output.includes("rm: refusing")
|
|
383
|
+
) {
|
|
384
|
+
console.log(`[destructive-check] Dangerous operation was blocked by system`)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export default destructiveCheck
|