@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.
@@ -19,7 +19,8 @@ try {
19
19
  }
20
20
  const installedBinDir = path.dirname(installedScript);
21
21
  const packageDir = path.dirname(installedBinDir);
22
- const nodeModules = path.dirname(packageDir);
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
- // Only scan commands that fetch Notion pages
4
- const SCAN_PATTERNS = [
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
- module.exports = function createPromptInjectionGuard({ httpsRequest, anthropicKey, analytics }) {
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 SCAN_PATTERNS.some(p => lower.includes(p));
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() {}, // no startup work needed
207
+ init() { fetchPatternsFromServer(); },
139
208
  cleanup() {},
140
209
  };
141
210
  };
@@ -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
- const keyBlock = checkApiKey();
274
- if (keyBlock) {
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
- try {
301
- const keyBlock = checkApiKey();
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
- try {
361
- const keyBlock = checkApiKey();
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 Skill Scanner (non-blocking) ===
417
- setImmediate(() => { try { skillsGuard.init(); } catch {} });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contextfort-ai/openclaw-secure",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Runtime security guard for OpenClaw — blocks malicious commands before they execute",
5
5
  "bin": {
6
6
  "openclaw-secure": "./bin/openclaw-secure.js"