@contextfort-ai/openclaw-secure 0.1.9 → 0.1.12
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 +65 -0
- package/bin/openclaw-secure.js +71 -0
- package/monitor/dashboard/public/index.html +1125 -42
- package/monitor/dashboard/scan-worker.js +21 -0
- package/monitor/dashboard/server.js +258 -7
- package/monitor/exfil_guard/index.js +79 -7
- package/monitor/plugin_guard/index.js +194 -0
- package/monitor/prompt_injection_guard/index.js +17 -2
- package/monitor/secrets_guard/index.js +148 -96
- package/monitor/skills_guard/index.js +170 -30
- package/openclaw-secure.js +178 -37
- package/package.json +1 -1
- package/test/guard-test-200.js +1312 -0
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# AntiVirus for ClawdBot: Prompt Injection Prevention (ashwin@contextfort.ai)
|
|
2
|
+
|
|
3
|
+
Runtime controls for OpenClaw that intercepts child_process calls, enforces approval for external commands via Telegram, and detects prompt injection in command outputs.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
https://github.com/user-attachments/assets/93064c17-9644-403b-8328-1da8014dae89
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
The runtime control live on Telegram.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
./start-gateway-with-hook.sh
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- `~/.claude/hooks/.env` with `ANTHROPIC_API_KEY=sk-ant-...`
|
|
23
|
+
- Telegram channel configured in OpenClaw
|
|
24
|
+
|
|
25
|
+
## How It Works
|
|
26
|
+
|
|
27
|
+
1. Hooks Node.js `child_process` module at startup
|
|
28
|
+
2. Every spawn/exec call is intercepted before execution
|
|
29
|
+
3. Read-only commands pass through immediately
|
|
30
|
+
4. External/write commands require human approval on Telegram
|
|
31
|
+
5. Command outputs are checked for prompt injection
|
|
32
|
+
6. If injection detected, next external command is blocked with warning
|
|
33
|
+
|
|
34
|
+
## Code References
|
|
35
|
+
|
|
36
|
+
| What | Line |
|
|
37
|
+
|------|------|
|
|
38
|
+
| Command categories | `spawn-hook.js:104-108` |
|
|
39
|
+
| Notion read-only detection | `spawn-hook.js:117-122` |
|
|
40
|
+
| GitHub CLI read-only detection | `spawn-hook.js:124-130` |
|
|
41
|
+
| Notion content extraction | `spawn-hook.js:141-156` |
|
|
42
|
+
| Claude injection check | `spawn-hook.js:168-195` |
|
|
43
|
+
| Main intercept logic | `spawn-hook.js:230-280` |
|
|
44
|
+
|
|
45
|
+
## Command Categories
|
|
46
|
+
|
|
47
|
+
**SKIP_USER_CONFIRMATION** (line 106) - Read-only, no external writes:
|
|
48
|
+
- System info: `whoami`, `pwd`, `hostname`, `uname`, `sw_vers`
|
|
49
|
+
- File reads: `ls`, `cat`, `head`, `tail`, `file`, `wc`
|
|
50
|
+
- Network info: `arp`, `ifconfig`, `networksetup`, `scutil`
|
|
51
|
+
- Notion/GitHub: GET requests, search queries, list/view subcommands
|
|
52
|
+
|
|
53
|
+
**SKIP_RESPONSE_CHECK** (line 105) - Output cannot be attacker-influenced:
|
|
54
|
+
- `whoami`, `pwd`, `echo`, `hostname`, `uname`
|
|
55
|
+
|
|
56
|
+
**INTERNAL_COMMANDS** (line 107) - Always pass through, even with injection warning:
|
|
57
|
+
- All local system commands that don't touch external services
|
|
58
|
+
|
|
59
|
+
## Debug Mode
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
SPAWN_GATE_DEBUG=1 ./start-gateway-with-hook.sh
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Logs to `spawn-gate.log`, audit trail in `spawn-audit.jsonl`.
|
package/bin/openclaw-secure.js
CHANGED
|
@@ -254,6 +254,77 @@ if (args[0] === 'scan' || args[0] === 'solve') {
|
|
|
254
254
|
return; // don't fall through — raw mode is async
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
if (args[0] === 'exfil-allow') {
|
|
258
|
+
const ALLOWLIST_FILE = path.join(CONFIG_DIR, 'exfil_allowlist.json');
|
|
259
|
+
const sub = args[1];
|
|
260
|
+
|
|
261
|
+
function readAllowlist() {
|
|
262
|
+
try { return JSON.parse(fs.readFileSync(ALLOWLIST_FILE, 'utf8')); }
|
|
263
|
+
catch { return { enabled: false, domains: [] }; }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function writeAllowlist(data) {
|
|
267
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
268
|
+
fs.writeFileSync(ALLOWLIST_FILE, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (sub === 'list') {
|
|
272
|
+
const al = readAllowlist();
|
|
273
|
+
console.log(`\n Exfil destination allowlist: ${al.enabled ? '\x1b[32menabled\x1b[0m (blocking non-allowed)' : '\x1b[33mdisabled\x1b[0m (log-only mode)'}`);
|
|
274
|
+
if (al.domains.length === 0) {
|
|
275
|
+
console.log(' No domains configured.\n');
|
|
276
|
+
} else {
|
|
277
|
+
console.log(' Allowed destinations:');
|
|
278
|
+
for (const d of al.domains) console.log(` - ${d}`);
|
|
279
|
+
console.log();
|
|
280
|
+
}
|
|
281
|
+
process.exit(0);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (sub === 'add') {
|
|
285
|
+
const domain = args[2];
|
|
286
|
+
if (!domain) { console.error('Usage: openclaw-secure exfil-allow add <domain>'); process.exit(1); }
|
|
287
|
+
const al = readAllowlist();
|
|
288
|
+
if (!al.domains.includes(domain)) al.domains.push(domain);
|
|
289
|
+
al.enabled = true;
|
|
290
|
+
writeAllowlist(al);
|
|
291
|
+
console.log(` Added ${domain} to exfil allowlist. Blocking mode enabled.`);
|
|
292
|
+
console.log(' Restart your openclaw session for changes to take effect.');
|
|
293
|
+
process.exit(0);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (sub === 'remove') {
|
|
297
|
+
const domain = args[2];
|
|
298
|
+
if (!domain) { console.error('Usage: openclaw-secure exfil-allow remove <domain>'); process.exit(1); }
|
|
299
|
+
const al = readAllowlist();
|
|
300
|
+
al.domains = al.domains.filter(d => d !== domain);
|
|
301
|
+
writeAllowlist(al);
|
|
302
|
+
console.log(` Removed ${domain} from exfil allowlist.`);
|
|
303
|
+
if (al.domains.length === 0) console.log(' No domains left — consider disabling with: openclaw-secure exfil-allow disable');
|
|
304
|
+
process.exit(0);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (sub === 'enable') {
|
|
308
|
+
const al = readAllowlist();
|
|
309
|
+
al.enabled = true;
|
|
310
|
+
writeAllowlist(al);
|
|
311
|
+
console.log(' Exfil allowlist blocking enabled. Only allowlisted destinations will be permitted.');
|
|
312
|
+
console.log(' Restart your openclaw session for changes to take effect.');
|
|
313
|
+
process.exit(0);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (sub === 'disable') {
|
|
317
|
+
const al = readAllowlist();
|
|
318
|
+
al.enabled = false;
|
|
319
|
+
writeAllowlist(al);
|
|
320
|
+
console.log(' Exfil allowlist blocking disabled. All exfil detections will be log-only.');
|
|
321
|
+
process.exit(0);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
console.error('Usage: openclaw-secure exfil-allow <list|add|remove|enable|disable> [domain]');
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
|
|
257
328
|
if (args[0] === 'disable') {
|
|
258
329
|
try {
|
|
259
330
|
const original = fs.readFileSync(backupLink, 'utf8').trim();
|