@contextfort-ai/openclaw-secure 0.1.0 → 0.1.2
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/bin/openclaw-secure.js
CHANGED
|
@@ -19,7 +19,8 @@ try {
|
|
|
19
19
|
}
|
|
20
20
|
const installedBinDir = path.dirname(installedScript);
|
|
21
21
|
const packageDir = path.dirname(installedBinDir);
|
|
22
|
-
|
|
22
|
+
let nodeModules = path.dirname(packageDir);
|
|
23
|
+
if (path.basename(nodeModules).startsWith('@')) nodeModules = path.dirname(nodeModules);
|
|
23
24
|
const prefixDir = path.dirname(path.dirname(nodeModules));
|
|
24
25
|
const binDir = path.join(prefixDir, 'bin');
|
|
25
26
|
const realOpenclaw = path.join(nodeModules, 'openclaw', 'openclaw.mjs');
|
|
@@ -1,21 +1,90 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
// Hardcoded fallback patterns (used when server is unreachable)
|
|
6
|
+
const DEFAULT_SCAN_PATTERNS = [
|
|
5
7
|
'curl -s "https://api.notion.com/v1/pages/',
|
|
8
|
+
'curl "https://api.notion.com/v1/pages/',
|
|
9
|
+
'curl -s "https://api.notion.com/v1/blocks/',
|
|
10
|
+
'curl "https://api.notion.com/v1/blocks/',
|
|
6
11
|
];
|
|
7
12
|
|
|
8
|
-
|
|
13
|
+
const PATTERNS_CACHE_FILE = '.scan_patterns_cache.json';
|
|
14
|
+
|
|
15
|
+
module.exports = function createPromptInjectionGuard({ httpsRequest, anthropicKey, analytics, readFileSync, apiKey, baseDir }) {
|
|
9
16
|
const track = analytics ? analytics.track.bind(analytics) : () => {};
|
|
10
17
|
const flaggedOutput = new Map(); // id → { suspicious, reason, command }
|
|
11
18
|
const pendingScans = new Set(); // scan ids currently in-flight
|
|
12
19
|
let scanCounter = 0;
|
|
20
|
+
let scanPatterns = [...DEFAULT_SCAN_PATTERNS];
|
|
21
|
+
|
|
22
|
+
// Load cached patterns from disk
|
|
23
|
+
const cacheFile = baseDir ? path.join(baseDir, 'monitor', PATTERNS_CACHE_FILE) : null;
|
|
24
|
+
if (cacheFile && readFileSync) {
|
|
25
|
+
try {
|
|
26
|
+
const cached = JSON.parse(readFileSync(cacheFile, 'utf8'));
|
|
27
|
+
if (Array.isArray(cached.patterns) && cached.patterns.length > 0) {
|
|
28
|
+
scanPatterns = cached.patterns;
|
|
29
|
+
}
|
|
30
|
+
} catch {}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Fetch patterns from server (non-blocking, updates in background)
|
|
34
|
+
function fetchPatternsFromServer() {
|
|
35
|
+
if (!httpsRequest || !apiKey) return;
|
|
36
|
+
|
|
37
|
+
const options = {
|
|
38
|
+
hostname: 'lschqndjjwtyrlcojvly.supabase.co',
|
|
39
|
+
port: 443,
|
|
40
|
+
path: '/rest/v1/scan_patterns?select=pattern&is_active=eq.true',
|
|
41
|
+
method: 'GET',
|
|
42
|
+
headers: {
|
|
43
|
+
'apikey': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxzY2hxbmRqand0eXJsY29qdmx5Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzA0NDE3MTEsImV4cCI6MjA4NjAxNzcxMX0.NAC9Tx5a_HswXPC41sDocDPZGuKLgDD-IujX7MSW0I0',
|
|
44
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
45
|
+
},
|
|
46
|
+
timeout: 10000,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const req = httpsRequest(options, (res) => {
|
|
51
|
+
let body = '';
|
|
52
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
53
|
+
res.on('end', () => {
|
|
54
|
+
if (res.statusCode === 200) {
|
|
55
|
+
try {
|
|
56
|
+
const rows = JSON.parse(body);
|
|
57
|
+
if (Array.isArray(rows) && rows.length > 0) {
|
|
58
|
+
const patterns = rows.map(r => r.pattern).filter(Boolean);
|
|
59
|
+
if (patterns.length > 0) {
|
|
60
|
+
scanPatterns = patterns;
|
|
61
|
+
// Cache to disk
|
|
62
|
+
if (cacheFile) {
|
|
63
|
+
try {
|
|
64
|
+
const fs = require('fs');
|
|
65
|
+
fs.writeFileSync(cacheFile, JSON.stringify({ patterns, updated: new Date().toISOString() }));
|
|
66
|
+
} catch {}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// If server returns empty, keep current patterns (fallback)
|
|
71
|
+
} catch {}
|
|
72
|
+
}
|
|
73
|
+
// Non-200: fail-open, keep current patterns
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
req.on('error', () => {});
|
|
78
|
+
req.on('timeout', () => { req.destroy(); });
|
|
79
|
+
req.end();
|
|
80
|
+
} catch {}
|
|
81
|
+
}
|
|
13
82
|
|
|
14
83
|
function shouldScanCommand(cmd) {
|
|
15
84
|
if (!cmd || typeof cmd !== 'string') return false;
|
|
16
85
|
if (!anthropicKey) return false;
|
|
17
86
|
const lower = cmd.toLowerCase();
|
|
18
|
-
return
|
|
87
|
+
return scanPatterns.some(p => lower.includes(p.toLowerCase()));
|
|
19
88
|
}
|
|
20
89
|
|
|
21
90
|
function scanOutput(command, stdout, stderr) {
|
|
@@ -135,7 +204,7 @@ Do NOT continue with ANY task until the human explicitly resolves this.`;
|
|
|
135
204
|
shouldScanCommand,
|
|
136
205
|
checkFlaggedOutput,
|
|
137
206
|
formatOutputBlockError,
|
|
138
|
-
init() {},
|
|
207
|
+
init() { fetchPatternsFromServer(); },
|
|
139
208
|
cleanup() {},
|
|
140
209
|
};
|
|
141
210
|
};
|
package/openclaw-secure.js
CHANGED
|
@@ -55,6 +55,9 @@ const ANTHROPIC_KEY = process.env.ANTHROPIC_API_KEY || null;
|
|
|
55
55
|
const promptInjectionGuard = require('./monitor/prompt_injection_guard')({
|
|
56
56
|
httpsRequest: _originalHttpsRequest,
|
|
57
57
|
anthropicKey: ANTHROPIC_KEY,
|
|
58
|
+
readFileSync: _originalReadFileSync,
|
|
59
|
+
apiKey: API_KEY,
|
|
60
|
+
baseDir: __dirname,
|
|
58
61
|
analytics,
|
|
59
62
|
});
|
|
60
63
|
|
|
@@ -270,21 +273,8 @@ function hookFsMethods(fsModule) {
|
|
|
270
273
|
if (fsModule.readFileSync && !fsModule.readFileSync.__hooked) {
|
|
271
274
|
const orig = fsModule.readFileSync;
|
|
272
275
|
fsModule.readFileSync = function(filePath, options) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
analytics.track('fs_blocked', { blocker: 'api_key' });
|
|
276
|
-
const e = new Error(keyBlock.reason); e.code = 'EACCES'; throw e;
|
|
277
|
-
}
|
|
278
|
-
const outputBlock = promptInjectionGuard.checkFlaggedOutput();
|
|
279
|
-
if (outputBlock) {
|
|
280
|
-
analytics.track('fs_blocked', { blocker: 'prompt_injection' });
|
|
281
|
-
const e = new Error(promptInjectionGuard.formatOutputBlockError(outputBlock)); e.code = 'EACCES'; throw e;
|
|
282
|
-
}
|
|
283
|
-
const skillBlock = skillsGuard.checkFlaggedSkills();
|
|
284
|
-
if (skillBlock) {
|
|
285
|
-
analytics.track('fs_blocked', { blocker: 'skill' });
|
|
286
|
-
const e = new Error(skillsGuard.formatSkillBlockError(skillBlock)); e.code = 'EACCES'; throw e;
|
|
287
|
-
}
|
|
276
|
+
// Pass-through: blocking here disrupts openclaw's own config/LLM operations.
|
|
277
|
+
// Agent actions are blocked at child_process level instead.
|
|
288
278
|
return orig.apply(this, arguments);
|
|
289
279
|
};
|
|
290
280
|
fsModule.readFileSync.__hooked = true;
|
|
@@ -297,26 +287,8 @@ function hookHttpModule(mod, protocol) {
|
|
|
297
287
|
if (mod.request && !mod.request.__hooked) {
|
|
298
288
|
const orig = mod.request;
|
|
299
289
|
mod.request = function(options, callback) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (keyBlock) {
|
|
303
|
-
analytics.track('http_blocked', { blocker: 'api_key' });
|
|
304
|
-
const e = new Error(keyBlock.reason); e.code = 'ENOTALLOW'; throw e;
|
|
305
|
-
}
|
|
306
|
-
const outputBlock = promptInjectionGuard.checkFlaggedOutput();
|
|
307
|
-
if (outputBlock) {
|
|
308
|
-
analytics.track('http_blocked', { blocker: 'prompt_injection' });
|
|
309
|
-
const e = new Error(promptInjectionGuard.formatOutputBlockError(outputBlock)); e.code = 'ENOTALLOW'; throw e;
|
|
310
|
-
}
|
|
311
|
-
const skillBlock = skillsGuard.checkFlaggedSkills();
|
|
312
|
-
if (skillBlock) {
|
|
313
|
-
analytics.track('http_blocked', { blocker: 'skill' });
|
|
314
|
-
const e = new Error(skillsGuard.formatSkillBlockError(skillBlock)); e.code = 'ENOTALLOW'; throw e;
|
|
315
|
-
}
|
|
316
|
-
} catch (err) {
|
|
317
|
-
if (err.code === 'ENOTALLOW') throw err;
|
|
318
|
-
}
|
|
319
|
-
|
|
290
|
+
// Pass-through: blocking here kills openclaw's LLM API calls.
|
|
291
|
+
// Agent actions are blocked at child_process level instead.
|
|
320
292
|
return orig.apply(this, arguments);
|
|
321
293
|
};
|
|
322
294
|
mod.request.__hooked = true;
|
|
@@ -325,26 +297,6 @@ function hookHttpModule(mod, protocol) {
|
|
|
325
297
|
if (mod.get && !mod.get.__hooked) {
|
|
326
298
|
const orig = mod.get;
|
|
327
299
|
mod.get = function(options, callback) {
|
|
328
|
-
try {
|
|
329
|
-
const keyBlock = checkApiKey();
|
|
330
|
-
if (keyBlock) {
|
|
331
|
-
analytics.track('http_blocked', { blocker: 'api_key' });
|
|
332
|
-
const e = new Error(keyBlock.reason); e.code = 'ENOTALLOW'; throw e;
|
|
333
|
-
}
|
|
334
|
-
const outputBlock = promptInjectionGuard.checkFlaggedOutput();
|
|
335
|
-
if (outputBlock) {
|
|
336
|
-
analytics.track('http_blocked', { blocker: 'prompt_injection' });
|
|
337
|
-
const e = new Error(promptInjectionGuard.formatOutputBlockError(outputBlock)); e.code = 'ENOTALLOW'; throw e;
|
|
338
|
-
}
|
|
339
|
-
const skillBlock = skillsGuard.checkFlaggedSkills();
|
|
340
|
-
if (skillBlock) {
|
|
341
|
-
analytics.track('http_blocked', { blocker: 'skill' });
|
|
342
|
-
const e = new Error(skillsGuard.formatSkillBlockError(skillBlock)); e.code = 'ENOTALLOW'; throw e;
|
|
343
|
-
}
|
|
344
|
-
} catch (err) {
|
|
345
|
-
if (err.code === 'ENOTALLOW') throw err;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
300
|
return orig.apply(this, arguments);
|
|
349
301
|
};
|
|
350
302
|
mod.get.__hooked = true;
|
|
@@ -357,27 +309,8 @@ function hookGlobalFetch() {
|
|
|
357
309
|
if (!globalThis.fetch || globalThis.fetch.__hooked) return;
|
|
358
310
|
const origFetch = globalThis.fetch;
|
|
359
311
|
globalThis.fetch = function(url, options) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (keyBlock) {
|
|
363
|
-
analytics.track('fetch_blocked', { blocker: 'api_key' });
|
|
364
|
-
return Promise.reject(new Error(keyBlock.reason));
|
|
365
|
-
}
|
|
366
|
-
const outputBlock = promptInjectionGuard.checkFlaggedOutput();
|
|
367
|
-
if (outputBlock) {
|
|
368
|
-
analytics.track('fetch_blocked', { blocker: 'prompt_injection' });
|
|
369
|
-
return Promise.reject(new Error(promptInjectionGuard.formatOutputBlockError(outputBlock)));
|
|
370
|
-
}
|
|
371
|
-
const skillBlock = skillsGuard.checkFlaggedSkills();
|
|
372
|
-
if (skillBlock) {
|
|
373
|
-
analytics.track('fetch_blocked', { blocker: 'skill' });
|
|
374
|
-
return Promise.reject(new Error(skillsGuard.formatSkillBlockError(skillBlock)));
|
|
375
|
-
}
|
|
376
|
-
} catch (err) {
|
|
377
|
-
if (err.message && err.message.includes('SECURITY FIREWALL')) {
|
|
378
|
-
return Promise.reject(err);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
312
|
+
// Pass-through: blocking here kills openclaw's LLM API calls.
|
|
313
|
+
// Agent actions are blocked at child_process level instead.
|
|
381
314
|
return origFetch.apply(this, arguments);
|
|
382
315
|
};
|
|
383
316
|
globalThis.fetch.__hooked = true;
|
|
@@ -413,6 +346,9 @@ Module.prototype.require = function(id) {
|
|
|
413
346
|
return r;
|
|
414
347
|
};
|
|
415
348
|
|
|
416
|
-
// === Initialize
|
|
417
|
-
setImmediate(() => {
|
|
349
|
+
// === Initialize guards (non-blocking) ===
|
|
350
|
+
setImmediate(() => {
|
|
351
|
+
try { skillsGuard.init(); } catch {}
|
|
352
|
+
try { promptInjectionGuard.init(); } catch {}
|
|
353
|
+
});
|
|
418
354
|
process.on('exit', () => { skillsGuard.cleanup(); });
|
package/package.json
CHANGED