@stanchat/clawguard 2.1.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 +95 -0
- package/clawdbot.plugin.json +34 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +288 -0
- package/dist/cli.js.map +1 -0
- package/dist/consent.d.ts +13 -0
- package/dist/consent.d.ts.map +1 -0
- package/dist/consent.js +178 -0
- package/dist/consent.js.map +1 -0
- package/dist/hooks.d.ts +28 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +351 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +24 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +229 -0
- package/dist/plugin.js.map +1 -0
- package/dist/postinstall.d.ts +3 -0
- package/dist/postinstall.d.ts.map +1 -0
- package/dist/postinstall.js +82 -0
- package/dist/postinstall.js.map +1 -0
- package/hooks/before-tool-call/HOOK.md +22 -0
- package/hooks/before-tool-call/handler.ts +285 -0
- package/hooks/clawguard/HOOK.md +53 -0
- package/hooks/clawguard/handler.d.ts +17 -0
- package/hooks/clawguard/handler.d.ts.map +1 -0
- package/hooks/clawguard/handler.js +271 -0
- package/hooks/clawguard/handler.js.map +1 -0
- package/hooks/clawguard/handler.ts +326 -0
- package/package.json +49 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clawguard
|
|
3
|
+
description: "AI safety guardrails - monitors actions, blocks dangerous commands, requires approval for risky operations"
|
|
4
|
+
homepage: https://github.com/averecion/clawguard
|
|
5
|
+
metadata:
|
|
6
|
+
{
|
|
7
|
+
"clawdbot":
|
|
8
|
+
{
|
|
9
|
+
"emoji": "🛡️",
|
|
10
|
+
"events": ["command"],
|
|
11
|
+
"install": [{ "id": "community", "kind": "community", "label": "Community Hook" }],
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Clawguard
|
|
17
|
+
|
|
18
|
+
AI Safety for OpenClaw - monitors actions, blocks dangers, requires approval for risky operations.
|
|
19
|
+
|
|
20
|
+
## What It Does
|
|
21
|
+
|
|
22
|
+
- **Prompt Injection Detection**: Blocks attempts to hijack your agent via malicious prompts
|
|
23
|
+
- **Dangerous Command Blocking**: Prevents rm -rf, fork bombs, curl|bash, and other destructive operations
|
|
24
|
+
- **Approval Workflow**: Risky actions require your explicit approval via CLI or dashboard
|
|
25
|
+
- **Activity Logging**: Full audit trail at `~/.clawguard/logs/`
|
|
26
|
+
|
|
27
|
+
## Protection Levels
|
|
28
|
+
|
|
29
|
+
- **Relaxed**: Minimal blocking, auto-approves most actions
|
|
30
|
+
- **Balanced**: Asks before risky actions (recommended)
|
|
31
|
+
- **Strict**: Requires approval for everything
|
|
32
|
+
|
|
33
|
+
## Dashboard
|
|
34
|
+
|
|
35
|
+
Visual monitoring at `http://localhost:4321/clawguard`
|
|
36
|
+
|
|
37
|
+
## CLI Commands
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
clawdbot clawguard status # Protection status
|
|
41
|
+
clawdbot clawguard pending # Pending approvals
|
|
42
|
+
clawdbot clawguard approve # Approve an action
|
|
43
|
+
clawdbot clawguard deny # Deny an action
|
|
44
|
+
clawdbot clawguard dashboard # Open visual dashboard
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## First Run
|
|
48
|
+
|
|
49
|
+
On first use, Clawguard runs in **Show Mode** - observing 10 actions without blocking to demonstrate how it works. After Show Mode completes, full protection activates.
|
|
50
|
+
|
|
51
|
+
## Configuration
|
|
52
|
+
|
|
53
|
+
Config stored at `~/.clawguard/config.json`
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface ToolEvent {
|
|
2
|
+
type: string;
|
|
3
|
+
action?: string;
|
|
4
|
+
tool: string;
|
|
5
|
+
args: Record<string, unknown>;
|
|
6
|
+
sessionKey?: string;
|
|
7
|
+
sessionId?: string;
|
|
8
|
+
timestamp: Date;
|
|
9
|
+
messages: string[];
|
|
10
|
+
context: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
declare const clawguardHandler: (event: ToolEvent) => Promise<{
|
|
13
|
+
allowed: boolean;
|
|
14
|
+
reason: string;
|
|
15
|
+
} | void>;
|
|
16
|
+
export default clawguardHandler;
|
|
17
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["handler.ts"],"names":[],"mappings":"AAkBA,UAAU,SAAS;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAuJD,QAAA,MAAM,gBAAgB,UAAiB,SAAS,KAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAgHrG,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
const fs = __importStar(require("fs"));
|
|
27
|
+
const path = __importStar(require("path"));
|
|
28
|
+
const os = __importStar(require("os"));
|
|
29
|
+
const CLAWGUARD_DIR = path.join(os.homedir(), ".clawguard");
|
|
30
|
+
const CONFIG_FILE = path.join(CLAWGUARD_DIR, "config.json");
|
|
31
|
+
const LOG_DIR = path.join(CLAWGUARD_DIR, "logs");
|
|
32
|
+
const PENDING_DIR = path.join(CLAWGUARD_DIR, "pending");
|
|
33
|
+
const POLICY_FILE = path.join(CLAWGUARD_DIR, "policy.json");
|
|
34
|
+
const DEFAULT_POLICY = [
|
|
35
|
+
{ tool: "shell.exec", decision: "review", reason: "Terminal commands can modify your system" },
|
|
36
|
+
{ tool: "exec", decision: "review", reason: "Terminal commands can modify your system" },
|
|
37
|
+
{ tool: "fs.write", decision: "review", reason: "File writes can overwrite important data" },
|
|
38
|
+
{ tool: "fs.delete", decision: "blocked", reason: "File deletion is high-risk" },
|
|
39
|
+
{ tool: "fs.unlink", decision: "blocked", reason: "File deletion is high-risk" },
|
|
40
|
+
{ tool: "http.request", decision: "approved", reason: "Network requests allowed by default" },
|
|
41
|
+
{ tool: "env.read", decision: "blocked", reason: "Credential access is restricted" },
|
|
42
|
+
{ tool: "env.get", decision: "blocked", reason: "Credential access is restricted" },
|
|
43
|
+
{ tool: "process", decision: "review", reason: "Process management requires review" }
|
|
44
|
+
];
|
|
45
|
+
const DANGEROUS_PATTERNS = [
|
|
46
|
+
/rm\s+(-rf?|--recursive|--force)\s+[\/~]/i,
|
|
47
|
+
/rm\s+-[rf]{2}\s+/i,
|
|
48
|
+
/mkfs\./i,
|
|
49
|
+
/dd\s+if=.*of=\/dev/i,
|
|
50
|
+
/>\s*\/dev\/sd[a-z]/i,
|
|
51
|
+
/chmod\s+777\s+\//i,
|
|
52
|
+
/curl.*\|\s*(ba)?sh/i,
|
|
53
|
+
/wget.*\|\s*(ba)?sh/i,
|
|
54
|
+
/eval\s*\(/i,
|
|
55
|
+
/:(){.*}:/i,
|
|
56
|
+
/fork\s*bomb/i
|
|
57
|
+
];
|
|
58
|
+
const INJECTION_PATTERNS = [
|
|
59
|
+
/ignore\s+(previous|all|prior)\s+(instructions?|prompts?)/i,
|
|
60
|
+
/disregard\s+(your|the|all)\s+(instructions?|rules?|guidelines?)/i,
|
|
61
|
+
/you\s+are\s+now\s+(a|in|acting)/i,
|
|
62
|
+
/new\s+instructions?:/i,
|
|
63
|
+
/system\s*:\s*you/i,
|
|
64
|
+
/\[INST\]/i,
|
|
65
|
+
/<\|im_start\|>/i,
|
|
66
|
+
/jailbreak/i,
|
|
67
|
+
/bypass\s+(security|filter|restriction)/i
|
|
68
|
+
];
|
|
69
|
+
function ensureDir(dir) {
|
|
70
|
+
if (!fs.existsSync(dir))
|
|
71
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
function loadConfig() {
|
|
74
|
+
ensureDir(CLAWGUARD_DIR);
|
|
75
|
+
try {
|
|
76
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
77
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch { }
|
|
81
|
+
return { enabled: false, consented: false, showMode: false, showModeActionsRemaining: 10, protectionLevel: "balanced" };
|
|
82
|
+
}
|
|
83
|
+
function saveConfig(config) {
|
|
84
|
+
ensureDir(CLAWGUARD_DIR);
|
|
85
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
86
|
+
}
|
|
87
|
+
function loadPolicy() {
|
|
88
|
+
try {
|
|
89
|
+
if (fs.existsSync(POLICY_FILE)) {
|
|
90
|
+
return JSON.parse(fs.readFileSync(POLICY_FILE, "utf-8"));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch { }
|
|
94
|
+
return DEFAULT_POLICY;
|
|
95
|
+
}
|
|
96
|
+
function classifyTool(tool) {
|
|
97
|
+
const policy = loadPolicy();
|
|
98
|
+
return policy.find(r => tool.startsWith(r.tool) || tool === r.tool) || null;
|
|
99
|
+
}
|
|
100
|
+
function checkDangerousCommand(args) {
|
|
101
|
+
const argsStr = JSON.stringify(args);
|
|
102
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
103
|
+
if (pattern.test(argsStr)) {
|
|
104
|
+
return `Dangerous command pattern detected`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
function checkInjection(args) {
|
|
110
|
+
const argsStr = JSON.stringify(args);
|
|
111
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
112
|
+
if (pattern.test(argsStr)) {
|
|
113
|
+
return `Possible prompt injection detected`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
function logAction(tool, args, allowed, reason) {
|
|
119
|
+
ensureDir(LOG_DIR);
|
|
120
|
+
const logEntry = {
|
|
121
|
+
ts: new Date().toISOString(),
|
|
122
|
+
tool,
|
|
123
|
+
args,
|
|
124
|
+
allowed,
|
|
125
|
+
reason
|
|
126
|
+
};
|
|
127
|
+
const logFile = path.join(LOG_DIR, `${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
128
|
+
fs.appendFileSync(logFile, JSON.stringify(logEntry) + "\n");
|
|
129
|
+
}
|
|
130
|
+
function generateApprovalId() {
|
|
131
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
132
|
+
}
|
|
133
|
+
async function waitForApproval(id, timeoutMs = 30000) {
|
|
134
|
+
const filePath = path.join(PENDING_DIR, `${id}.json`);
|
|
135
|
+
const startTime = Date.now();
|
|
136
|
+
const pollInterval = 500;
|
|
137
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
138
|
+
try {
|
|
139
|
+
if (fs.existsSync(filePath)) {
|
|
140
|
+
const approval = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
141
|
+
if (approval.status !== "pending") {
|
|
142
|
+
return approval.status === "approved";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch { }
|
|
147
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
function createPendingApproval(tool, args, reason) {
|
|
152
|
+
ensureDir(PENDING_DIR);
|
|
153
|
+
const id = generateApprovalId();
|
|
154
|
+
const approval = {
|
|
155
|
+
id,
|
|
156
|
+
tool,
|
|
157
|
+
args,
|
|
158
|
+
reason,
|
|
159
|
+
createdAt: new Date().toISOString(),
|
|
160
|
+
status: "pending"
|
|
161
|
+
};
|
|
162
|
+
fs.writeFileSync(path.join(PENDING_DIR, `${id}.json`), JSON.stringify(approval, null, 2));
|
|
163
|
+
return id;
|
|
164
|
+
}
|
|
165
|
+
const clawguardHandler = async (event) => {
|
|
166
|
+
const config = loadConfig();
|
|
167
|
+
if (!config.enabled || !config.consented) {
|
|
168
|
+
return { allowed: true, reason: "Clawguard not enabled" };
|
|
169
|
+
}
|
|
170
|
+
const tool = event.tool;
|
|
171
|
+
const args = event.args || {};
|
|
172
|
+
const injection = checkInjection(args);
|
|
173
|
+
if (injection) {
|
|
174
|
+
if (config.showMode) {
|
|
175
|
+
event.messages.push(`🛡️ [SHOW MODE] Would block: ${injection}`);
|
|
176
|
+
config.showModeActionsRemaining--;
|
|
177
|
+
if (config.showModeActionsRemaining <= 0)
|
|
178
|
+
config.showMode = false;
|
|
179
|
+
saveConfig(config);
|
|
180
|
+
logAction(tool, args, true, `showMode:${injection}`);
|
|
181
|
+
return { allowed: true, reason: `Show mode: ${injection}` };
|
|
182
|
+
}
|
|
183
|
+
logAction(tool, args, false, injection);
|
|
184
|
+
event.messages.push(`🛡️ Blocked: ${injection}`);
|
|
185
|
+
return { allowed: false, reason: injection };
|
|
186
|
+
}
|
|
187
|
+
const dangerous = checkDangerousCommand(args);
|
|
188
|
+
if (dangerous) {
|
|
189
|
+
if (config.showMode) {
|
|
190
|
+
event.messages.push(`🛡️ [SHOW MODE] Would block: ${dangerous}`);
|
|
191
|
+
config.showModeActionsRemaining--;
|
|
192
|
+
if (config.showModeActionsRemaining <= 0)
|
|
193
|
+
config.showMode = false;
|
|
194
|
+
saveConfig(config);
|
|
195
|
+
logAction(tool, args, true, `showMode:${dangerous}`);
|
|
196
|
+
return { allowed: true, reason: `Show mode: ${dangerous}` };
|
|
197
|
+
}
|
|
198
|
+
logAction(tool, args, false, dangerous);
|
|
199
|
+
event.messages.push(`🛡️ Blocked: ${dangerous}`);
|
|
200
|
+
return { allowed: false, reason: dangerous };
|
|
201
|
+
}
|
|
202
|
+
const rule = classifyTool(tool);
|
|
203
|
+
if (!rule) {
|
|
204
|
+
logAction(tool, args, true, "No policy match");
|
|
205
|
+
if (config.showMode) {
|
|
206
|
+
config.showModeActionsRemaining--;
|
|
207
|
+
if (config.showModeActionsRemaining <= 0)
|
|
208
|
+
config.showMode = false;
|
|
209
|
+
saveConfig(config);
|
|
210
|
+
}
|
|
211
|
+
return { allowed: true, reason: "No policy match" };
|
|
212
|
+
}
|
|
213
|
+
if (rule.decision === "approved") {
|
|
214
|
+
logAction(tool, args, true, rule.reason);
|
|
215
|
+
if (config.showMode) {
|
|
216
|
+
config.showModeActionsRemaining--;
|
|
217
|
+
if (config.showModeActionsRemaining <= 0)
|
|
218
|
+
config.showMode = false;
|
|
219
|
+
saveConfig(config);
|
|
220
|
+
}
|
|
221
|
+
return { allowed: true, reason: rule.reason };
|
|
222
|
+
}
|
|
223
|
+
if (rule.decision === "blocked") {
|
|
224
|
+
if (config.showMode) {
|
|
225
|
+
event.messages.push(`🛡️ [SHOW MODE] Would block: ${tool} - ${rule.reason}`);
|
|
226
|
+
config.showModeActionsRemaining--;
|
|
227
|
+
if (config.showModeActionsRemaining <= 0)
|
|
228
|
+
config.showMode = false;
|
|
229
|
+
saveConfig(config);
|
|
230
|
+
logAction(tool, args, true, `showMode:blocked`);
|
|
231
|
+
return { allowed: true, reason: `Show mode: ${rule.reason}` };
|
|
232
|
+
}
|
|
233
|
+
logAction(tool, args, false, rule.reason);
|
|
234
|
+
event.messages.push(`🛡️ Blocked: ${rule.reason}`);
|
|
235
|
+
return { allowed: false, reason: rule.reason };
|
|
236
|
+
}
|
|
237
|
+
if (rule.decision === "review") {
|
|
238
|
+
if (config.protectionLevel === "relaxed") {
|
|
239
|
+
logAction(tool, args, true, "Relaxed mode - auto-approved");
|
|
240
|
+
if (config.showMode) {
|
|
241
|
+
config.showModeActionsRemaining--;
|
|
242
|
+
if (config.showModeActionsRemaining <= 0)
|
|
243
|
+
config.showMode = false;
|
|
244
|
+
saveConfig(config);
|
|
245
|
+
}
|
|
246
|
+
return { allowed: true, reason: "Relaxed mode - auto-approved" };
|
|
247
|
+
}
|
|
248
|
+
if (config.showMode) {
|
|
249
|
+
event.messages.push(`🛡️ [SHOW MODE] Would require approval: ${tool}`);
|
|
250
|
+
config.showModeActionsRemaining--;
|
|
251
|
+
if (config.showModeActionsRemaining <= 0)
|
|
252
|
+
config.showMode = false;
|
|
253
|
+
saveConfig(config);
|
|
254
|
+
logAction(tool, args, true, `showMode:review`);
|
|
255
|
+
return { allowed: true, reason: `Show mode: would require approval` };
|
|
256
|
+
}
|
|
257
|
+
const approvalId = createPendingApproval(tool, args, rule.reason);
|
|
258
|
+
event.messages.push(`🛡️ Approval required: ${tool} - ${rule.reason}`);
|
|
259
|
+
event.messages.push(` Run: clawdbot clawguard approve ${approvalId}`);
|
|
260
|
+
const approved = await waitForApproval(approvalId);
|
|
261
|
+
logAction(tool, args, approved, approved ? "manualApproval" : "denied/timeout");
|
|
262
|
+
if (!approved) {
|
|
263
|
+
event.messages.push(`🛡️ Action denied or timed out`);
|
|
264
|
+
}
|
|
265
|
+
return { allowed: approved, reason: approved ? "Manually approved" : "Denied or timed out" };
|
|
266
|
+
}
|
|
267
|
+
logAction(tool, args, true, "Default allow");
|
|
268
|
+
return { allowed: true, reason: "Default allow" };
|
|
269
|
+
};
|
|
270
|
+
exports.default = clawguardHandler;
|
|
271
|
+
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
|
|
5
|
+
const CLAWGUARD_DIR = path.join(os.homedir(), ".clawguard");
|
|
6
|
+
const CONFIG_FILE = path.join(CLAWGUARD_DIR, "config.json");
|
|
7
|
+
const LOG_DIR = path.join(CLAWGUARD_DIR, "logs");
|
|
8
|
+
const PENDING_DIR = path.join(CLAWGUARD_DIR, "pending");
|
|
9
|
+
const POLICY_FILE = path.join(CLAWGUARD_DIR, "policy.json");
|
|
10
|
+
|
|
11
|
+
interface ClawguardConfig {
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
consented: boolean;
|
|
14
|
+
showMode: boolean;
|
|
15
|
+
showModeActionsRemaining: number;
|
|
16
|
+
protectionLevel: "relaxed" | "balanced" | "strict";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ToolEvent {
|
|
20
|
+
type: string;
|
|
21
|
+
action?: string;
|
|
22
|
+
tool: string;
|
|
23
|
+
args: Record<string, unknown>;
|
|
24
|
+
sessionKey?: string;
|
|
25
|
+
sessionId?: string;
|
|
26
|
+
timestamp: Date;
|
|
27
|
+
messages: string[];
|
|
28
|
+
context: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface CommandEvent {
|
|
32
|
+
action: string;
|
|
33
|
+
sessionKey?: string;
|
|
34
|
+
senderId?: string;
|
|
35
|
+
source?: string;
|
|
36
|
+
timestamp?: Date;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface PolicyRule {
|
|
40
|
+
tool: string;
|
|
41
|
+
decision: "approved" | "blocked" | "review";
|
|
42
|
+
reason: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const DEFAULT_POLICY: PolicyRule[] = [
|
|
46
|
+
{ tool: "shell.exec", decision: "review", reason: "Terminal commands can modify your system" },
|
|
47
|
+
{ tool: "exec", decision: "review", reason: "Terminal commands can modify your system" },
|
|
48
|
+
{ tool: "fs.write", decision: "review", reason: "File writes can overwrite important data" },
|
|
49
|
+
{ tool: "fs.delete", decision: "blocked", reason: "File deletion is high-risk" },
|
|
50
|
+
{ tool: "fs.unlink", decision: "blocked", reason: "File deletion is high-risk" },
|
|
51
|
+
{ tool: "http.request", decision: "approved", reason: "Network requests allowed by default" },
|
|
52
|
+
{ tool: "env.read", decision: "blocked", reason: "Credential access is restricted" },
|
|
53
|
+
{ tool: "env.get", decision: "blocked", reason: "Credential access is restricted" },
|
|
54
|
+
{ tool: "process", decision: "review", reason: "Process management requires review" }
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const DANGEROUS_PATTERNS = [
|
|
58
|
+
/rm\s+(-rf?|--recursive|--force)\s+[\/~]/i,
|
|
59
|
+
/rm\s+-[rf]{2}\s+/i,
|
|
60
|
+
/mkfs\./i,
|
|
61
|
+
/dd\s+if=.*of=\/dev/i,
|
|
62
|
+
/>\s*\/dev\/sd[a-z]/i,
|
|
63
|
+
/chmod\s+777\s+\//i,
|
|
64
|
+
/curl.*\|\s*(ba)?sh/i,
|
|
65
|
+
/wget.*\|\s*(ba)?sh/i,
|
|
66
|
+
/eval\s*\(/i,
|
|
67
|
+
/:(){.*}:/i,
|
|
68
|
+
/fork\s*bomb/i
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const INJECTION_PATTERNS = [
|
|
72
|
+
/ignore\s+(previous|all|prior)\s+(instructions?|prompts?)/i,
|
|
73
|
+
/disregard\s+(your|the|all)\s+(instructions?|rules?|guidelines?)/i,
|
|
74
|
+
/you\s+are\s+now\s+(a|in|acting)/i,
|
|
75
|
+
/new\s+instructions?:/i,
|
|
76
|
+
/system\s*:\s*you/i,
|
|
77
|
+
/\[INST\]/i,
|
|
78
|
+
/<\|im_start\|>/i,
|
|
79
|
+
/jailbreak/i,
|
|
80
|
+
/bypass\s+(security|filter|restriction)/i
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
function ensureDir(dir: string): void {
|
|
84
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function loadConfig(): ClawguardConfig {
|
|
88
|
+
ensureDir(CLAWGUARD_DIR);
|
|
89
|
+
try {
|
|
90
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
91
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
|
|
92
|
+
}
|
|
93
|
+
} catch {}
|
|
94
|
+
return { enabled: false, consented: false, showMode: false, showModeActionsRemaining: 10, protectionLevel: "balanced" };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function saveConfig(config: ClawguardConfig): void {
|
|
98
|
+
ensureDir(CLAWGUARD_DIR);
|
|
99
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function loadPolicy(): PolicyRule[] {
|
|
103
|
+
try {
|
|
104
|
+
if (fs.existsSync(POLICY_FILE)) {
|
|
105
|
+
return JSON.parse(fs.readFileSync(POLICY_FILE, "utf-8"));
|
|
106
|
+
}
|
|
107
|
+
} catch {}
|
|
108
|
+
return DEFAULT_POLICY;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function classifyTool(tool: string): PolicyRule | null {
|
|
112
|
+
const policy = loadPolicy();
|
|
113
|
+
return policy.find(r => tool.startsWith(r.tool) || tool === r.tool) || null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function checkDangerousCommand(args: Record<string, unknown>): string | null {
|
|
117
|
+
const argsStr = JSON.stringify(args);
|
|
118
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
119
|
+
if (pattern.test(argsStr)) {
|
|
120
|
+
return `Dangerous command pattern detected`;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function checkInjection(args: Record<string, unknown>): string | null {
|
|
127
|
+
const argsStr = JSON.stringify(args);
|
|
128
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
129
|
+
if (pattern.test(argsStr)) {
|
|
130
|
+
return `Possible prompt injection detected`;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function logAction(tool: string, args: Record<string, unknown>, allowed: boolean, reason: string): void {
|
|
137
|
+
ensureDir(LOG_DIR);
|
|
138
|
+
const logEntry = {
|
|
139
|
+
ts: new Date().toISOString(),
|
|
140
|
+
tool,
|
|
141
|
+
args,
|
|
142
|
+
allowed,
|
|
143
|
+
reason
|
|
144
|
+
};
|
|
145
|
+
const logFile = path.join(LOG_DIR, `${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
146
|
+
fs.appendFileSync(logFile, JSON.stringify(logEntry) + "\n");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function generateApprovalId(): string {
|
|
150
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function waitForApproval(id: string, timeoutMs = 30000): Promise<boolean> {
|
|
154
|
+
const filePath = path.join(PENDING_DIR, `${id}.json`);
|
|
155
|
+
const startTime = Date.now();
|
|
156
|
+
const pollInterval = 500;
|
|
157
|
+
|
|
158
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
159
|
+
try {
|
|
160
|
+
if (fs.existsSync(filePath)) {
|
|
161
|
+
const approval = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
162
|
+
if (approval.status !== "pending") {
|
|
163
|
+
return approval.status === "approved";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch {}
|
|
167
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function createPendingApproval(tool: string, args: Record<string, unknown>, reason: string): string {
|
|
174
|
+
ensureDir(PENDING_DIR);
|
|
175
|
+
const id = generateApprovalId();
|
|
176
|
+
const approval = {
|
|
177
|
+
id,
|
|
178
|
+
tool,
|
|
179
|
+
args,
|
|
180
|
+
reason,
|
|
181
|
+
createdAt: new Date().toISOString(),
|
|
182
|
+
status: "pending"
|
|
183
|
+
};
|
|
184
|
+
fs.writeFileSync(path.join(PENDING_DIR, `${id}.json`), JSON.stringify(approval, null, 2));
|
|
185
|
+
return id;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function logCommand(event: CommandEvent): void {
|
|
189
|
+
ensureDir(LOG_DIR);
|
|
190
|
+
const logEntry = {
|
|
191
|
+
ts: new Date().toISOString(),
|
|
192
|
+
type: "command",
|
|
193
|
+
action: event.action,
|
|
194
|
+
sessionKey: event.sessionKey,
|
|
195
|
+
senderId: event.senderId,
|
|
196
|
+
source: event.source
|
|
197
|
+
};
|
|
198
|
+
const logFile = path.join(LOG_DIR, `${new Date().toISOString().split("T")[0]}.jsonl`);
|
|
199
|
+
fs.appendFileSync(logFile, JSON.stringify(logEntry) + "\n");
|
|
200
|
+
console.log(`🛡️ Clawguard: logged command event - ${event.action}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const clawguardHandler = async (event: ToolEvent | CommandEvent): Promise<{ allowed: boolean; reason: string } | void> => {
|
|
204
|
+
const config = loadConfig();
|
|
205
|
+
|
|
206
|
+
// Handle command events (slash commands like /new, /stop)
|
|
207
|
+
if (!("tool" in event) && "action" in event) {
|
|
208
|
+
const cmdEvent = event as CommandEvent;
|
|
209
|
+
console.log(`🛡️ Clawguard: received command event - ${cmdEvent.action}`);
|
|
210
|
+
logCommand(cmdEvent);
|
|
211
|
+
return; // Command events don't need blocking, just logging
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!config.enabled || !config.consented) {
|
|
215
|
+
return { allowed: true, reason: "Clawguard not enabled" };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const toolEvent = event as ToolEvent;
|
|
219
|
+
const tool = toolEvent.tool;
|
|
220
|
+
const args = toolEvent.args || {};
|
|
221
|
+
|
|
222
|
+
const injection = checkInjection(args);
|
|
223
|
+
if (injection) {
|
|
224
|
+
if (config.showMode) {
|
|
225
|
+
toolEvent.messages?.push(`🛡️ [SHOW MODE] Would block: ${injection}`);
|
|
226
|
+
config.showModeActionsRemaining--;
|
|
227
|
+
if (config.showModeActionsRemaining <= 0) config.showMode = false;
|
|
228
|
+
saveConfig(config);
|
|
229
|
+
logAction(tool, args, true, `showMode:${injection}`);
|
|
230
|
+
return { allowed: true, reason: `Show mode: ${injection}` };
|
|
231
|
+
}
|
|
232
|
+
logAction(tool, args, false, injection);
|
|
233
|
+
toolEvent.messages?.push(`🛡️ Blocked: ${injection}`);
|
|
234
|
+
return { allowed: false, reason: injection };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const dangerous = checkDangerousCommand(args);
|
|
238
|
+
if (dangerous) {
|
|
239
|
+
if (config.showMode) {
|
|
240
|
+
toolEvent.messages?.push(`🛡️ [SHOW MODE] Would block: ${dangerous}`);
|
|
241
|
+
config.showModeActionsRemaining--;
|
|
242
|
+
if (config.showModeActionsRemaining <= 0) config.showMode = false;
|
|
243
|
+
saveConfig(config);
|
|
244
|
+
logAction(tool, args, true, `showMode:${dangerous}`);
|
|
245
|
+
return { allowed: true, reason: `Show mode: ${dangerous}` };
|
|
246
|
+
}
|
|
247
|
+
logAction(tool, args, false, dangerous);
|
|
248
|
+
toolEvent.messages?.push(`🛡️ Blocked: ${dangerous}`);
|
|
249
|
+
return { allowed: false, reason: dangerous };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const rule = classifyTool(tool);
|
|
253
|
+
|
|
254
|
+
if (!rule) {
|
|
255
|
+
logAction(tool, args, true, "No policy match");
|
|
256
|
+
if (config.showMode) {
|
|
257
|
+
config.showModeActionsRemaining--;
|
|
258
|
+
if (config.showModeActionsRemaining <= 0) config.showMode = false;
|
|
259
|
+
saveConfig(config);
|
|
260
|
+
}
|
|
261
|
+
return { allowed: true, reason: "No policy match" };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (rule.decision === "approved") {
|
|
265
|
+
logAction(tool, args, true, rule.reason);
|
|
266
|
+
if (config.showMode) {
|
|
267
|
+
config.showModeActionsRemaining--;
|
|
268
|
+
if (config.showModeActionsRemaining <= 0) config.showMode = false;
|
|
269
|
+
saveConfig(config);
|
|
270
|
+
}
|
|
271
|
+
return { allowed: true, reason: rule.reason };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (rule.decision === "blocked") {
|
|
275
|
+
if (config.showMode) {
|
|
276
|
+
toolEvent.messages?.push(`🛡️ [SHOW MODE] Would block: ${tool} - ${rule.reason}`);
|
|
277
|
+
config.showModeActionsRemaining--;
|
|
278
|
+
if (config.showModeActionsRemaining <= 0) config.showMode = false;
|
|
279
|
+
saveConfig(config);
|
|
280
|
+
logAction(tool, args, true, `showMode:blocked`);
|
|
281
|
+
return { allowed: true, reason: `Show mode: ${rule.reason}` };
|
|
282
|
+
}
|
|
283
|
+
logAction(tool, args, false, rule.reason);
|
|
284
|
+
toolEvent.messages?.push(`🛡️ Blocked: ${rule.reason}`);
|
|
285
|
+
return { allowed: false, reason: rule.reason };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (rule.decision === "review") {
|
|
289
|
+
if (config.protectionLevel === "relaxed") {
|
|
290
|
+
logAction(tool, args, true, "Relaxed mode - auto-approved");
|
|
291
|
+
if (config.showMode) {
|
|
292
|
+
config.showModeActionsRemaining--;
|
|
293
|
+
if (config.showModeActionsRemaining <= 0) config.showMode = false;
|
|
294
|
+
saveConfig(config);
|
|
295
|
+
}
|
|
296
|
+
return { allowed: true, reason: "Relaxed mode - auto-approved" };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (config.showMode) {
|
|
300
|
+
toolEvent.messages?.push(`🛡️ [SHOW MODE] Would require approval: ${tool}`);
|
|
301
|
+
config.showModeActionsRemaining--;
|
|
302
|
+
if (config.showModeActionsRemaining <= 0) config.showMode = false;
|
|
303
|
+
saveConfig(config);
|
|
304
|
+
logAction(tool, args, true, `showMode:review`);
|
|
305
|
+
return { allowed: true, reason: `Show mode: would require approval` };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const approvalId = createPendingApproval(tool, args, rule.reason);
|
|
309
|
+
toolEvent.messages?.push(`🛡️ Approval required: ${tool} - ${rule.reason}`);
|
|
310
|
+
toolEvent.messages?.push(` Run: clawdbot clawguard approve ${approvalId}`);
|
|
311
|
+
|
|
312
|
+
const approved = await waitForApproval(approvalId);
|
|
313
|
+
logAction(tool, args, approved, approved ? "manualApproval" : "denied/timeout");
|
|
314
|
+
|
|
315
|
+
if (!approved) {
|
|
316
|
+
toolEvent.messages?.push(`🛡️ Action denied or timed out`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return { allowed: approved, reason: approved ? "Manually approved" : "Denied or timed out" };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
logAction(tool, args, true, "Default allow");
|
|
323
|
+
return { allowed: true, reason: "Default allow" };
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
export default clawguardHandler;
|