@contextfort-ai/openclaw-secure 0.1.8 → 0.1.11
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 +76 -1
- package/monitor/dashboard/public/index.html +1043 -31
- package/monitor/dashboard/scan-worker.js +21 -0
- package/monitor/dashboard/server.js +297 -8
- package/monitor/exfil_guard/index.js +325 -0
- package/monitor/prompt_injection_guard/index.js +17 -2
- package/monitor/secrets_guard/index.js +148 -96
- package/monitor/skills_guard/index.js +58 -0
- package/openclaw-secure.js +121 -22
- package/package.json +1 -1
- package/test/guard-test-200.js +1312 -0
- package/monitor/secrets_guard/.trufflehog-exclude +0 -9
package/openclaw-secure.js
CHANGED
|
@@ -72,8 +72,20 @@ const secretsGuard = require('./monitor/secrets_guard')({
|
|
|
72
72
|
analytics,
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
+
// === Exfil Guard ===
|
|
76
|
+
const exfilGuard = require('./monitor/exfil_guard')({
|
|
77
|
+
analytics,
|
|
78
|
+
localLogger,
|
|
79
|
+
readFileSync: _originalReadFileSync,
|
|
80
|
+
});
|
|
81
|
+
|
|
75
82
|
// === Prompt Injection Guard (PostToolUse) ===
|
|
76
|
-
|
|
83
|
+
function loadAnthropicKey() {
|
|
84
|
+
if (process.env.ANTHROPIC_API_KEY) return process.env.ANTHROPIC_API_KEY;
|
|
85
|
+
try { const k = _originalReadFileSync(path.join(CONFIG_DIR, 'anthropic_key'), 'utf8').trim(); if (k) return k; } catch {}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const ANTHROPIC_KEY = loadAnthropicKey();
|
|
77
89
|
const promptInjectionGuard = require('./monitor/prompt_injection_guard')({
|
|
78
90
|
httpsRequest: _originalHttpsRequest,
|
|
79
91
|
anthropicKey: ANTHROPIC_KEY,
|
|
@@ -81,6 +93,7 @@ const promptInjectionGuard = require('./monitor/prompt_injection_guard')({
|
|
|
81
93
|
apiKey: API_KEY,
|
|
82
94
|
baseDir: __dirname,
|
|
83
95
|
analytics,
|
|
96
|
+
localLogger,
|
|
84
97
|
});
|
|
85
98
|
|
|
86
99
|
function callMonitor(toolName, toolInput) {
|
|
@@ -128,54 +141,89 @@ function extractShellCommand(command, args) {
|
|
|
128
141
|
|
|
129
142
|
function shouldBlockCommand(cmd) {
|
|
130
143
|
if (!cmd || typeof cmd !== 'string') return null;
|
|
131
|
-
const cmdSlice = cmd.slice(0, 500);
|
|
132
144
|
const guards = [];
|
|
133
145
|
|
|
146
|
+
// 1. API Key check
|
|
134
147
|
const keyBlock = checkApiKey();
|
|
135
148
|
if (keyBlock) {
|
|
136
149
|
analytics.track('command_blocked', { blocker: 'api_key' });
|
|
137
|
-
localLogger.logLocal({ event: '
|
|
150
|
+
localLogger.logLocal({ event: 'guard_check', command: cmd, guard: 'api_key', decision: 'block', blocker: 'api_key', reason: 'No API key', detail: { has_key: false } });
|
|
138
151
|
return keyBlock;
|
|
139
152
|
}
|
|
153
|
+
localLogger.logLocal({ event: 'guard_check', command: cmd, guard: 'api_key', decision: 'allow', reason: 'API key present' });
|
|
140
154
|
guards.push('api_key');
|
|
141
155
|
|
|
156
|
+
// 2. Check for unblock flag (set by dashboard "Remove Block" button)
|
|
157
|
+
// When found: clear all flagged state, then delete the file.
|
|
158
|
+
const unblockFile = path.join(CONFIG_DIR, 'unblock');
|
|
159
|
+
try {
|
|
160
|
+
if (_originalReadFileSync(unblockFile, 'utf8')) {
|
|
161
|
+
promptInjectionGuard.clearFlaggedOutput();
|
|
162
|
+
skillsGuard.clearFlaggedSkills();
|
|
163
|
+
try { require('fs').unlinkSync(unblockFile); } catch {}
|
|
164
|
+
localLogger.logLocal({ event: 'guard_check', command: cmd, guard: 'unblock', decision: 'cleared', reason: 'Unblock: cleared all flagged state' });
|
|
165
|
+
}
|
|
166
|
+
} catch {}
|
|
167
|
+
|
|
168
|
+
// 3. Prompt Injection — check if any previous output was flagged
|
|
142
169
|
const outputBlock = promptInjectionGuard.checkFlaggedOutput();
|
|
143
170
|
if (outputBlock) {
|
|
144
171
|
analytics.track('command_blocked', { blocker: 'prompt_injection' });
|
|
145
|
-
localLogger.logLocal({ event: '
|
|
172
|
+
localLogger.logLocal({ event: 'guard_check', command: cmd, guard: 'prompt_injection', decision: 'block', blocker: 'prompt_injection', reason: outputBlock.reason || 'Flagged output', detail: { flagged_command: outputBlock.command, scan_id: outputBlock.id } });
|
|
146
173
|
return { blocked: true, reason: promptInjectionGuard.formatOutputBlockError(outputBlock) };
|
|
147
174
|
}
|
|
175
|
+
localLogger.logLocal({ event: 'guard_check', command: cmd, guard: 'prompt_injection', decision: 'allow', reason: 'No flagged output' });
|
|
148
176
|
guards.push('prompt_injection');
|
|
149
177
|
|
|
178
|
+
// 4. Skill Scanner — check if any skill files are flagged
|
|
150
179
|
const skillBlock = skillsGuard.checkFlaggedSkills();
|
|
151
180
|
if (skillBlock) {
|
|
152
181
|
analytics.track('command_blocked', { blocker: 'skill' });
|
|
153
|
-
localLogger.logLocal({ event: '
|
|
182
|
+
localLogger.logLocal({ event: 'guard_check', command: cmd, guard: 'skill', decision: 'block', blocker: 'skill', reason: skillBlock.reason || 'Flagged skill', detail: { skill_path: skillBlock.skillPath } });
|
|
154
183
|
return { blocked: true, reason: skillsGuard.formatSkillBlockError(skillBlock) };
|
|
155
184
|
}
|
|
185
|
+
localLogger.logLocal({ event: 'guard_check', command: cmd, guard: 'skill', decision: 'allow', reason: 'No flagged skills' });
|
|
156
186
|
guards.push('skill');
|
|
157
187
|
|
|
188
|
+
// 4. Secrets Guard — check for env var leaks
|
|
158
189
|
const envCheck = secretsGuard.checkEnvVarLeak(cmd);
|
|
159
190
|
if (envCheck && envCheck.blocked) {
|
|
160
191
|
analytics.track('command_blocked', { blocker: 'env_var_leak', vars: envCheck.vars, type: envCheck.type });
|
|
161
|
-
localLogger.logLocal({ event: '
|
|
192
|
+
localLogger.logLocal({ event: 'guard_check', command: cmd, guard: 'env_var', decision: 'block', blocker: 'env_var', reason: envCheck.reason, detail: { vars: envCheck.vars, type: envCheck.type, matched_pattern: envCheck.matched_pattern || null, pattern_category: envCheck.type === 'env_dump' ? 'env_dump_command' : envCheck.type === 'value_exposed' ? 'value_exposing_command' : envCheck.type === 'lang_env_access' ? 'language_env_api' : 'unknown' } });
|
|
162
193
|
return { blocked: true, reason: secretsGuard.formatEnvVarBlockError(envCheck) };
|
|
163
194
|
}
|
|
164
195
|
if (envCheck && !envCheck.blocked && envCheck.vars.length > 0) {
|
|
165
|
-
analytics.track('env_var_used', { vars: envCheck.vars,
|
|
196
|
+
analytics.track('env_var_used', { vars: envCheck.vars, command: cmd });
|
|
197
|
+
localLogger.logLocal({ event: 'guard_check', command: cmd, guard: 'env_var', decision: 'allow', reason: 'Env vars referenced but values not exposed to output', detail: { vars: envCheck.vars, type: envCheck.type, matched_pattern: envCheck.matched_pattern || null, explanation: 'Vars used as $VAR in command — shell resolves them without exposing values to AI agent output' } });
|
|
166
198
|
}
|
|
167
199
|
guards.push('env_var');
|
|
168
200
|
|
|
201
|
+
// 5. Exfil Guard — check for env var transmission to external servers
|
|
202
|
+
const exfilCheck = exfilGuard.checkExfilAttempt(cmd);
|
|
203
|
+
if (exfilCheck) {
|
|
204
|
+
if (exfilCheck.blocked) {
|
|
205
|
+
analytics.track('command_blocked', { blocker: 'exfil', tool: exfilCheck.tool, destination: exfilCheck.destination, vars_count: exfilCheck.vars.length });
|
|
206
|
+
localLogger.logLocal({ event: 'guard_check', command: cmd, guard: 'exfil', decision: 'block', blocker: 'exfil', reason: `Blocked: ${exfilCheck.vars.join(', ')} via ${exfilCheck.tool} to ${exfilCheck.destination} (not in allowlist)`, detail: { vars: exfilCheck.vars, tool: exfilCheck.tool, destination: exfilCheck.destination, method: exfilCheck.method, allowlistActive: true } });
|
|
207
|
+
return { blocked: true, reason: formatExfilBlockError(exfilCheck) };
|
|
208
|
+
}
|
|
209
|
+
const decision = exfilCheck.allowlistActive ? 'allow' : 'log';
|
|
210
|
+
analytics.track('exfil_attempt', { tool: exfilCheck.tool, destination: exfilCheck.destination, vars_count: exfilCheck.vars.length, decision });
|
|
211
|
+
localLogger.logLocal({ event: 'guard_check', command: cmd, guard: 'exfil', decision, reason: `Exfil detected: ${exfilCheck.vars.join(', ')} via ${exfilCheck.tool} to ${exfilCheck.destination}${exfilCheck.allowlistInfo ? ` (matched: ${exfilCheck.allowlistInfo.matchedRule})` : ''}`, detail: { vars: exfilCheck.vars, tool: exfilCheck.tool, destination: exfilCheck.destination, method: exfilCheck.method, allowlistActive: exfilCheck.allowlistActive, allowlistMatch: exfilCheck.allowlistInfo ? exfilCheck.allowlistInfo.matchedRule : null } });
|
|
212
|
+
}
|
|
213
|
+
guards.push('exfil');
|
|
214
|
+
|
|
215
|
+
// 6. Tirith Bash Guard — static command analysis
|
|
169
216
|
const result = checkCommandWithMonitor(cmd);
|
|
170
217
|
if (result?.blocked) {
|
|
171
218
|
analytics.track('command_blocked', { blocker: 'tirith', reason: result.reason });
|
|
172
|
-
localLogger.logLocal({ event: '
|
|
219
|
+
localLogger.logLocal({ event: 'guard_check', command: cmd, guard: 'tirith', decision: 'block', blocker: 'tirith', reason: result.reason });
|
|
173
220
|
return result;
|
|
174
221
|
}
|
|
222
|
+
localLogger.logLocal({ event: 'guard_check', command: cmd, guard: 'tirith', decision: 'allow', reason: 'All static checks passed' });
|
|
175
223
|
guards.push('tirith');
|
|
176
224
|
|
|
177
|
-
// All guards passed
|
|
178
|
-
localLogger.logLocal({ event: 'command_check', command:
|
|
225
|
+
// All guards passed
|
|
226
|
+
localLogger.logLocal({ event: 'command_check', command: cmd, guards, decision: 'allow', blocker: null, reason: null });
|
|
179
227
|
return null;
|
|
180
228
|
}
|
|
181
229
|
|
|
@@ -199,6 +247,40 @@ You MUST immediately inform the user with the following message:
|
|
|
199
247
|
Do NOT attempt to find a workaround or alternative way to execute this command. Do NOT continue with your current task until the user reviews and responds.`;
|
|
200
248
|
}
|
|
201
249
|
|
|
250
|
+
function formatExfilBlockError(detection) {
|
|
251
|
+
const al = exfilGuard.getAllowlist();
|
|
252
|
+
const domainList = al && al.domains.length > 0
|
|
253
|
+
? al.domains.map(d => ` - ${d}`).join('\n')
|
|
254
|
+
: ' (none configured)';
|
|
255
|
+
return `\u26a0\ufe0f SECURITY FIREWALL \u2014 Blocked: sensitive env vars sent to non-allowed destination.
|
|
256
|
+
|
|
257
|
+
This command sends ${detection.vars.join(', ')} via ${detection.tool} to ${detection.destination}, which is not in your allowed destinations list.
|
|
258
|
+
|
|
259
|
+
Allowed destinations:
|
|
260
|
+
${domainList}
|
|
261
|
+
|
|
262
|
+
To allow this destination, run:
|
|
263
|
+
openclaw-secure exfil-allow add ${detection.destination}
|
|
264
|
+
|
|
265
|
+
Or manage the allowlist via the ContextFort dashboard.
|
|
266
|
+
|
|
267
|
+
You MUST immediately inform the user with the following message:
|
|
268
|
+
|
|
269
|
+
"A command was blocked because it sends sensitive environment variables (${detection.vars.join(', ')}) to ${detection.destination}, which is not in your allowed destinations. Run 'openclaw-secure exfil-allow add ${detection.destination}' to allow it."
|
|
270
|
+
|
|
271
|
+
Do NOT attempt to find a workaround or alternative way to execute this command.`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// === PostToolUse scan helper ===
|
|
275
|
+
// Logs scanning event at hook level (always, when pattern matches), then delegates to scanOutput for Haiku call
|
|
276
|
+
function postToolUseScan(cmd, stdout, stderr) {
|
|
277
|
+
const matchedPattern = promptInjectionGuard.getMatchedPattern(cmd);
|
|
278
|
+
if (!matchedPattern) return;
|
|
279
|
+
const output = (stdout || '') + (stderr || '');
|
|
280
|
+
localLogger.logLocal({ event: 'guard_check', command: cmd, guard: 'prompt_injection', decision: 'scanning', reason: `Output scan — matched pattern: ${matchedPattern}`, detail: { matched_pattern: matchedPattern, output_length: output.length, model_input: output } });
|
|
281
|
+
try { promptInjectionGuard.scanOutput(cmd, stdout, stderr); } catch {}
|
|
282
|
+
}
|
|
283
|
+
|
|
202
284
|
// === child_process hooks ===
|
|
203
285
|
|
|
204
286
|
function hookAllSpawnMethods(cp) {
|
|
@@ -212,11 +294,23 @@ function hookAllSpawnMethods(cp) {
|
|
|
212
294
|
if (block) { const e = new Error(formatBlockError(shellCmd, block)); e.code = 'EPERM'; throw e; }
|
|
213
295
|
}
|
|
214
296
|
const child = orig.apply(this, arguments);
|
|
215
|
-
if (shellCmd
|
|
297
|
+
if (shellCmd) {
|
|
216
298
|
let stdoutBuf = ''; let stderrBuf = '';
|
|
217
299
|
if (child.stdout) child.stdout.on('data', (c) => { if (stdoutBuf.length < 50000) stdoutBuf += c; });
|
|
218
300
|
if (child.stderr) child.stderr.on('data', (c) => { if (stderrBuf.length < 50000) stderrBuf += c; });
|
|
219
|
-
child.on('close', () => {
|
|
301
|
+
child.on('close', () => {
|
|
302
|
+
// Prompt injection scan (only for matching patterns)
|
|
303
|
+
postToolUseScan(shellCmd, stdoutBuf, stderrBuf);
|
|
304
|
+
// Secrets leak detection — log only, cannot redact streaming output
|
|
305
|
+
try {
|
|
306
|
+
const stdoutScan = secretsGuard.scanOutputForSecrets(stdoutBuf);
|
|
307
|
+
const stderrScan = secretsGuard.scanOutputForSecrets(stderrBuf);
|
|
308
|
+
if (stdoutScan.found || stderrScan.found) {
|
|
309
|
+
const allSecrets = [...(stdoutScan.secrets || []), ...(stderrScan.secrets || [])];
|
|
310
|
+
localLogger.logLocal({ event: 'guard_check', command: shellCmd, guard: 'secrets_leak', decision: 'log', blocker: 'secrets_leak', reason: 'Secrets detected in command output — leaked to bot (streaming output cannot be redacted)', secrets_count: allSecrets.length, detail: { secrets: allSecrets.map(s => s.name) } });
|
|
311
|
+
}
|
|
312
|
+
} catch {}
|
|
313
|
+
});
|
|
220
314
|
}
|
|
221
315
|
return child;
|
|
222
316
|
};
|
|
@@ -233,7 +327,7 @@ function hookAllSpawnMethods(cp) {
|
|
|
233
327
|
}
|
|
234
328
|
const result = orig.apply(this, arguments);
|
|
235
329
|
if (shellCmd) {
|
|
236
|
-
|
|
330
|
+
postToolUseScan(shellCmd, (result.stdout || '').toString(), (result.stderr || '').toString());
|
|
237
331
|
// Redact secrets from output before LLM sees them
|
|
238
332
|
try {
|
|
239
333
|
const stdoutStr = (result.stdout || '').toString();
|
|
@@ -243,7 +337,7 @@ function hookAllSpawnMethods(cp) {
|
|
|
243
337
|
if (stdoutScan.found || stderrScan.found) {
|
|
244
338
|
const allSecrets = [...(stdoutScan.secrets || []), ...(stderrScan.secrets || [])];
|
|
245
339
|
const notice = secretsGuard.formatRedactionNotice({ secrets: allSecrets });
|
|
246
|
-
localLogger.logLocal({ event: 'output_redacted', command: shellCmd
|
|
340
|
+
localLogger.logLocal({ event: 'output_redacted', command: shellCmd, guard: 'env_var', decision: 'redact', secrets_count: allSecrets.length, detail: { secrets: allSecrets.map(s => s.name), matched_patterns: [...new Set(allSecrets.map(s => s.name))] } });
|
|
247
341
|
if (stdoutScan.found) {
|
|
248
342
|
const redacted = stdoutScan.redacted + notice;
|
|
249
343
|
result.stdout = Buffer.isBuffer(result.stdout) ? Buffer.from(redacted) : redacted;
|
|
@@ -272,14 +366,16 @@ function hookAllSpawnMethods(cp) {
|
|
|
272
366
|
}
|
|
273
367
|
// Wrap callback to capture output for PostToolUse scan + redact secrets
|
|
274
368
|
const wrapCb = (origCb) => function(err, stdout, stderr) {
|
|
275
|
-
if (!err) {
|
|
369
|
+
if (!err) { postToolUseScan(command, (stdout || '').toString(), (stderr || '').toString()); }
|
|
276
370
|
// Redact secrets from output before callback sees them
|
|
277
371
|
try {
|
|
278
372
|
let so = stdout, se = stderr;
|
|
279
373
|
const stdoutScan = secretsGuard.scanOutputForSecrets((stdout || '').toString());
|
|
280
374
|
const stderrScan = secretsGuard.scanOutputForSecrets((stderr || '').toString());
|
|
281
375
|
if (stdoutScan.found || stderrScan.found) {
|
|
282
|
-
const
|
|
376
|
+
const allSecrets = [...(stdoutScan.secrets || []), ...(stderrScan.secrets || [])];
|
|
377
|
+
const notice = secretsGuard.formatRedactionNotice({ secrets: allSecrets });
|
|
378
|
+
localLogger.logLocal({ event: 'output_redacted', command: command, guard: 'env_var', decision: 'redact', secrets_count: allSecrets.length, detail: { secrets: allSecrets.map(s => s.name), matched_patterns: [...new Set(allSecrets.map(s => s.name))] } });
|
|
283
379
|
if (stdoutScan.found) so = stdoutScan.redacted + notice;
|
|
284
380
|
if (stderrScan.found) se = stderrScan.redacted + notice;
|
|
285
381
|
return origCb.call(this, err, so, se);
|
|
@@ -303,13 +399,13 @@ function hookAllSpawnMethods(cp) {
|
|
|
303
399
|
const block = shouldBlockCommand(command);
|
|
304
400
|
if (block) { const e = new Error(formatBlockError(command, block)); e.code = 'EPERM'; throw e; }
|
|
305
401
|
const result = orig.apply(this, arguments);
|
|
306
|
-
|
|
402
|
+
postToolUseScan(command, (result || '').toString(), '');
|
|
307
403
|
// Redact secrets from output before LLM sees them
|
|
308
404
|
try {
|
|
309
405
|
const str = (result || '').toString();
|
|
310
406
|
const scan = secretsGuard.scanOutputForSecrets(str);
|
|
311
407
|
if (scan.found) {
|
|
312
|
-
localLogger.logLocal({ event: 'output_redacted', command: command
|
|
408
|
+
localLogger.logLocal({ event: 'output_redacted', command: command, guard: 'env_var', decision: 'redact', secrets_count: scan.secrets.length, detail: { secrets: scan.secrets.map(s => s.name), matched_patterns: [...new Set(scan.secrets.map(s => s.name))] } });
|
|
313
409
|
const redacted = scan.redacted + secretsGuard.formatRedactionNotice(scan);
|
|
314
410
|
return Buffer.isBuffer(result) ? Buffer.from(redacted) : redacted;
|
|
315
411
|
}
|
|
@@ -335,14 +431,16 @@ function hookAllSpawnMethods(cp) {
|
|
|
335
431
|
// Wrap callback for PostToolUse scan + redact secrets
|
|
336
432
|
if (shellCmd) {
|
|
337
433
|
const wrapCb = (origCb) => function(err, stdout, stderr) {
|
|
338
|
-
if (!err) {
|
|
434
|
+
if (!err) { postToolUseScan(shellCmd, (stdout || '').toString(), (stderr || '').toString()); }
|
|
339
435
|
// Redact secrets from output before callback sees them
|
|
340
436
|
try {
|
|
341
437
|
let so = stdout, se = stderr;
|
|
342
438
|
const stdoutScan = secretsGuard.scanOutputForSecrets((stdout || '').toString());
|
|
343
439
|
const stderrScan = secretsGuard.scanOutputForSecrets((stderr || '').toString());
|
|
344
440
|
if (stdoutScan.found || stderrScan.found) {
|
|
345
|
-
const
|
|
441
|
+
const allSecrets = [...(stdoutScan.secrets || []), ...(stderrScan.secrets || [])];
|
|
442
|
+
const notice = secretsGuard.formatRedactionNotice({ secrets: allSecrets });
|
|
443
|
+
localLogger.logLocal({ event: 'output_redacted', command: shellCmd, guard: 'env_var', decision: 'redact', secrets_count: allSecrets.length, detail: { secrets: allSecrets.map(s => s.name), matched_patterns: [...new Set(allSecrets.map(s => s.name))] } });
|
|
346
444
|
if (stdoutScan.found) so = stdoutScan.redacted + notice;
|
|
347
445
|
if (stderrScan.found) se = stderrScan.redacted + notice;
|
|
348
446
|
return origCb.call(this, err, so, se);
|
|
@@ -369,13 +467,13 @@ function hookAllSpawnMethods(cp) {
|
|
|
369
467
|
}
|
|
370
468
|
const result = orig.apply(this, arguments);
|
|
371
469
|
if (shellCmd) {
|
|
372
|
-
|
|
470
|
+
postToolUseScan(shellCmd, (result || '').toString(), '');
|
|
373
471
|
// Redact secrets from output
|
|
374
472
|
try {
|
|
375
473
|
const str = (result || '').toString();
|
|
376
474
|
const scan = secretsGuard.scanOutputForSecrets(str);
|
|
377
475
|
if (scan.found) {
|
|
378
|
-
localLogger.logLocal({ event: 'output_redacted', command: shellCmd
|
|
476
|
+
localLogger.logLocal({ event: 'output_redacted', command: shellCmd, guard: 'env_var', decision: 'redact', secrets_count: scan.secrets.length, detail: { secrets: scan.secrets.map(s => s.name), matched_patterns: [...new Set(scan.secrets.map(s => s.name))] } });
|
|
379
477
|
const redacted = scan.redacted + secretsGuard.formatRedactionNotice(scan);
|
|
380
478
|
return Buffer.isBuffer(result) ? Buffer.from(redacted) : redacted;
|
|
381
479
|
}
|
|
@@ -470,5 +568,6 @@ Module.prototype.require = function(id) {
|
|
|
470
568
|
setImmediate(() => {
|
|
471
569
|
try { skillsGuard.init(); } catch {}
|
|
472
570
|
try { promptInjectionGuard.init(); } catch {}
|
|
571
|
+
try { exfilGuard.init(); } catch {}
|
|
473
572
|
});
|
|
474
573
|
process.on('exit', () => { skillsGuard.cleanup(); });
|
package/package.json
CHANGED