@lobehub/lobehub 2.0.0-next.93 → 2.0.0-next.95
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/.github/workflows/issue-auto-comments.yml +0 -19
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/package.json +1 -1
- package/packages/agent-runtime/src/core/InterventionChecker.ts +85 -0
- package/packages/agent-runtime/src/core/__tests__/InterventionChecker.test.ts +492 -22
- package/packages/agent-runtime/src/core/defaultSecurityBlacklist.ts +335 -0
- package/packages/agent-runtime/src/core/index.ts +1 -0
- package/packages/agent-runtime/src/types/state.ts +10 -1
- package/packages/model-bank/src/aiModels/xai.ts +85 -6
- package/packages/types/src/tool/intervention.ts +38 -0
- package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +25 -0
- package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +28 -0
- package/src/features/ModelSwitchPanel/index.tsx +15 -13
- package/src/store/chat/agents/GeneralChatAgent.ts +22 -8
|
@@ -1,20 +1,35 @@
|
|
|
1
|
-
import type { HumanInterventionConfig } from '@lobechat/types';
|
|
1
|
+
import type { HumanInterventionConfig, SecurityBlacklistConfig } from '@lobechat/types';
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
3
|
|
|
4
4
|
import { InterventionChecker } from '../InterventionChecker';
|
|
5
|
+
import { DEFAULT_SECURITY_BLACKLIST } from '../defaultSecurityBlacklist';
|
|
5
6
|
|
|
6
7
|
describe('InterventionChecker', () => {
|
|
7
8
|
describe('shouldIntervene', () => {
|
|
8
9
|
it('should return never when config is undefined', () => {
|
|
9
|
-
const result = InterventionChecker.shouldIntervene({
|
|
10
|
+
const result = InterventionChecker.shouldIntervene({
|
|
11
|
+
config: undefined,
|
|
12
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
13
|
+
toolArgs: {},
|
|
14
|
+
});
|
|
10
15
|
expect(result).toBe('never');
|
|
11
16
|
});
|
|
12
17
|
|
|
13
18
|
it('should return the policy when config is a simple string', () => {
|
|
14
|
-
expect(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
expect(
|
|
20
|
+
InterventionChecker.shouldIntervene({
|
|
21
|
+
config: 'never',
|
|
22
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
23
|
+
toolArgs: {},
|
|
24
|
+
}),
|
|
25
|
+
).toBe('never');
|
|
26
|
+
expect(
|
|
27
|
+
InterventionChecker.shouldIntervene({
|
|
28
|
+
config: 'required',
|
|
29
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
30
|
+
toolArgs: {},
|
|
31
|
+
}),
|
|
32
|
+
).toBe('required');
|
|
18
33
|
});
|
|
19
34
|
|
|
20
35
|
it('should match rules in order and return first match', () => {
|
|
@@ -24,14 +39,26 @@ describe('InterventionChecker', () => {
|
|
|
24
39
|
{ policy: 'required' }, // Default rule
|
|
25
40
|
];
|
|
26
41
|
|
|
27
|
-
expect(InterventionChecker.shouldIntervene({ config, toolArgs: { command: 'ls:' } })).toBe(
|
|
28
|
-
'never',
|
|
29
|
-
);
|
|
30
42
|
expect(
|
|
31
|
-
InterventionChecker.shouldIntervene({
|
|
43
|
+
InterventionChecker.shouldIntervene({
|
|
44
|
+
config,
|
|
45
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
46
|
+
toolArgs: { command: 'ls:' },
|
|
47
|
+
}),
|
|
48
|
+
).toBe('never');
|
|
49
|
+
expect(
|
|
50
|
+
InterventionChecker.shouldIntervene({
|
|
51
|
+
config,
|
|
52
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
53
|
+
toolArgs: { command: 'git commit:' },
|
|
54
|
+
}),
|
|
32
55
|
).toBe('required');
|
|
33
56
|
expect(
|
|
34
|
-
InterventionChecker.shouldIntervene({
|
|
57
|
+
InterventionChecker.shouldIntervene({
|
|
58
|
+
config,
|
|
59
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
60
|
+
toolArgs: { command: 'rm -rf /' },
|
|
61
|
+
}),
|
|
35
62
|
).toBe('required');
|
|
36
63
|
});
|
|
37
64
|
|
|
@@ -40,6 +67,7 @@ describe('InterventionChecker', () => {
|
|
|
40
67
|
|
|
41
68
|
const result = InterventionChecker.shouldIntervene({
|
|
42
69
|
config,
|
|
70
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
43
71
|
toolArgs: { command: 'rm -rf /' },
|
|
44
72
|
});
|
|
45
73
|
expect(result).toBe('required');
|
|
@@ -61,6 +89,7 @@ describe('InterventionChecker', () => {
|
|
|
61
89
|
expect(
|
|
62
90
|
InterventionChecker.shouldIntervene({
|
|
63
91
|
config,
|
|
92
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
64
93
|
toolArgs: {
|
|
65
94
|
command: 'git add:.',
|
|
66
95
|
path: '/Users/project/file.ts',
|
|
@@ -72,6 +101,7 @@ describe('InterventionChecker', () => {
|
|
|
72
101
|
expect(
|
|
73
102
|
InterventionChecker.shouldIntervene({
|
|
74
103
|
config,
|
|
104
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
75
105
|
toolArgs: {
|
|
76
106
|
command: 'git add:.',
|
|
77
107
|
path: '/tmp/file.ts',
|
|
@@ -88,6 +118,7 @@ describe('InterventionChecker', () => {
|
|
|
88
118
|
|
|
89
119
|
const result = InterventionChecker.shouldIntervene({
|
|
90
120
|
config,
|
|
121
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
91
122
|
toolArgs: { command: 'anything' },
|
|
92
123
|
});
|
|
93
124
|
expect(result).toBe('required');
|
|
@@ -218,6 +249,393 @@ describe('InterventionChecker', () => {
|
|
|
218
249
|
});
|
|
219
250
|
});
|
|
220
251
|
|
|
252
|
+
describe('checkSecurityBlacklist', () => {
|
|
253
|
+
it('should return not blocked when blacklist is empty', () => {
|
|
254
|
+
const result = InterventionChecker.checkSecurityBlacklist([], { command: 'rm -rf /' });
|
|
255
|
+
expect(result.blocked).toBe(false);
|
|
256
|
+
expect(result.reason).toBeUndefined();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe('with DEFAULT_SECURITY_BLACKLIST', () => {
|
|
260
|
+
it('should block dangerous rm -rf ~/ command', () => {
|
|
261
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
262
|
+
command: 'rm -rf ~/',
|
|
263
|
+
});
|
|
264
|
+
expect(result.blocked).toBe(true);
|
|
265
|
+
expect(result.reason).toBe('Recursive deletion of home directory is extremely dangerous');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should block rm -rf on macOS home directory', () => {
|
|
269
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
270
|
+
command: 'rm -rf /Users/alice',
|
|
271
|
+
});
|
|
272
|
+
expect(result.blocked).toBe(true);
|
|
273
|
+
expect(result.reason).toBe('Recursive deletion of home directory is extremely dangerous');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should block rm -rf on Linux home directory', () => {
|
|
277
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
278
|
+
command: 'rm -rf /home/alice',
|
|
279
|
+
});
|
|
280
|
+
expect(result.blocked).toBe(true);
|
|
281
|
+
expect(result.reason).toBe('Recursive deletion of home directory is extremely dangerous');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should block rm -rf with $HOME variable', () => {
|
|
285
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
286
|
+
command: 'rm -rf $HOME',
|
|
287
|
+
});
|
|
288
|
+
expect(result.blocked).toBe(true);
|
|
289
|
+
expect(result.reason).toBe('Recursive deletion of home directory is extremely dangerous');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should block rm -rf / command', () => {
|
|
293
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
294
|
+
command: 'rm -rf /',
|
|
295
|
+
});
|
|
296
|
+
expect(result.blocked).toBe(true);
|
|
297
|
+
expect(result.reason).toBe('Recursive deletion of root directory will destroy the system');
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('should allow safe rm commands', () => {
|
|
301
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
302
|
+
command: 'rm -rf /tmp/test-folder',
|
|
303
|
+
});
|
|
304
|
+
expect(result.blocked).toBe(false);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should block fork bomb', () => {
|
|
308
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
309
|
+
command: ':(){ :|:& };:',
|
|
310
|
+
});
|
|
311
|
+
expect(result.blocked).toBe(true);
|
|
312
|
+
expect(result.reason).toBe('Fork bomb can crash the system');
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should block dangerous dd commands to disk devices', () => {
|
|
316
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
317
|
+
command: 'dd if=/dev/zero of=/dev/sda',
|
|
318
|
+
});
|
|
319
|
+
expect(result.blocked).toBe(true);
|
|
320
|
+
expect(result.reason).toBe('Writing random data to disk devices can destroy data');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should block reading .env files via command', () => {
|
|
324
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
325
|
+
command: 'cat .env',
|
|
326
|
+
});
|
|
327
|
+
expect(result.blocked).toBe(true);
|
|
328
|
+
expect(result.reason).toBe(
|
|
329
|
+
'Reading .env files may leak sensitive credentials and API keys',
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should block reading .env files via path', () => {
|
|
334
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
335
|
+
path: '/project/.env.local',
|
|
336
|
+
});
|
|
337
|
+
expect(result.blocked).toBe(true);
|
|
338
|
+
expect(result.reason).toBe(
|
|
339
|
+
'Reading .env files may leak sensitive credentials and API keys',
|
|
340
|
+
);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('should block reading SSH private keys via command', () => {
|
|
344
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
345
|
+
command: 'cat ~/.ssh/id_rsa',
|
|
346
|
+
});
|
|
347
|
+
expect(result.blocked).toBe(true);
|
|
348
|
+
expect(result.reason).toBe('Reading SSH private keys can compromise system security');
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('should block reading SSH private keys via path', () => {
|
|
352
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
353
|
+
path: '/home/user/.ssh/id_ed25519',
|
|
354
|
+
});
|
|
355
|
+
expect(result.blocked).toBe(true);
|
|
356
|
+
expect(result.reason).toBe('Reading SSH private keys can compromise system security');
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('should allow reading SSH public keys', () => {
|
|
360
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
361
|
+
command: 'cat ~/.ssh/id_rsa.pub',
|
|
362
|
+
});
|
|
363
|
+
expect(result.blocked).toBe(false);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should block reading AWS credentials via command', () => {
|
|
367
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
368
|
+
command: 'cat ~/.aws/credentials',
|
|
369
|
+
});
|
|
370
|
+
expect(result.blocked).toBe(true);
|
|
371
|
+
expect(result.reason).toBe('Accessing AWS credentials can leak cloud access keys');
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('should block reading AWS credentials via path', () => {
|
|
375
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
376
|
+
path: '/home/user/.aws/credentials',
|
|
377
|
+
});
|
|
378
|
+
expect(result.blocked).toBe(true);
|
|
379
|
+
expect(result.reason).toBe('Accessing AWS credentials can leak cloud access keys');
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('should block reading Docker config', () => {
|
|
383
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
384
|
+
command: 'less ~/.docker/config.json',
|
|
385
|
+
});
|
|
386
|
+
expect(result.blocked).toBe(true);
|
|
387
|
+
expect(result.reason).toBe('Reading Docker config may expose registry credentials');
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('should block reading Kubernetes config', () => {
|
|
391
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
392
|
+
path: '/home/user/.kube/config',
|
|
393
|
+
});
|
|
394
|
+
expect(result.blocked).toBe(true);
|
|
395
|
+
expect(result.reason).toBe('Reading Kubernetes config may expose cluster credentials');
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('should block reading Git credentials', () => {
|
|
399
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
400
|
+
command: 'cat ~/.git-credentials',
|
|
401
|
+
});
|
|
402
|
+
expect(result.blocked).toBe(true);
|
|
403
|
+
expect(result.reason).toBe('Reading Git credentials file may leak access tokens');
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('should block reading npm token file', () => {
|
|
407
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
408
|
+
path: '/home/user/.npmrc',
|
|
409
|
+
});
|
|
410
|
+
expect(result.blocked).toBe(true);
|
|
411
|
+
expect(result.reason).toBe(
|
|
412
|
+
'Reading npm token file may expose package registry credentials',
|
|
413
|
+
);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('should block reading shell history files', () => {
|
|
417
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
418
|
+
command: 'cat ~/.bash_history',
|
|
419
|
+
});
|
|
420
|
+
expect(result.blocked).toBe(true);
|
|
421
|
+
expect(result.reason).toBe(
|
|
422
|
+
'Reading history files may expose sensitive commands and credentials',
|
|
423
|
+
);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('should block reading GCP credentials', () => {
|
|
427
|
+
const result = InterventionChecker.checkSecurityBlacklist(DEFAULT_SECURITY_BLACKLIST, {
|
|
428
|
+
path: '/home/user/.config/gcloud/application_default_credentials.json',
|
|
429
|
+
});
|
|
430
|
+
expect(result.blocked).toBe(true);
|
|
431
|
+
expect(result.reason).toBe('Reading GCP credentials may leak cloud service account keys');
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
describe('with custom blacklist', () => {
|
|
436
|
+
it('should work with multiple parameter matching', () => {
|
|
437
|
+
const blacklist: SecurityBlacklistConfig = [
|
|
438
|
+
{
|
|
439
|
+
description: 'Dangerous operation on system files',
|
|
440
|
+
match: {
|
|
441
|
+
command: { pattern: 'rm.*', type: 'regex' },
|
|
442
|
+
path: '/etc/*',
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
];
|
|
446
|
+
|
|
447
|
+
// Both match - should block
|
|
448
|
+
expect(
|
|
449
|
+
InterventionChecker.checkSecurityBlacklist(blacklist, {
|
|
450
|
+
command: 'rm -rf',
|
|
451
|
+
path: '/etc/passwd',
|
|
452
|
+
}).blocked,
|
|
453
|
+
).toBe(true);
|
|
454
|
+
|
|
455
|
+
// Only command matches - should not block
|
|
456
|
+
expect(
|
|
457
|
+
InterventionChecker.checkSecurityBlacklist(blacklist, {
|
|
458
|
+
command: 'rm -rf',
|
|
459
|
+
path: '/tmp/file',
|
|
460
|
+
}).blocked,
|
|
461
|
+
).toBe(false);
|
|
462
|
+
|
|
463
|
+
// Only path matches - should not block
|
|
464
|
+
expect(
|
|
465
|
+
InterventionChecker.checkSecurityBlacklist(blacklist, {
|
|
466
|
+
command: 'cat',
|
|
467
|
+
path: '/etc/passwd',
|
|
468
|
+
}).blocked,
|
|
469
|
+
).toBe(false);
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
describe('shouldIntervene with security blacklist', () => {
|
|
475
|
+
describe('with default blacklist behavior', () => {
|
|
476
|
+
it('should block dangerous commands even in auto-run mode', () => {
|
|
477
|
+
// Even with config set to 'never', default blacklist should override
|
|
478
|
+
const result = InterventionChecker.shouldIntervene({
|
|
479
|
+
config: 'never',
|
|
480
|
+
// Not passing securityBlacklist - should use DEFAULT_SECURITY_BLACKLIST
|
|
481
|
+
toolArgs: { command: 'rm -rf ~/' },
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
expect(result).toBe('required');
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('should block dangerous commands even with no config', () => {
|
|
488
|
+
// Even with no config (which normally means 'never'), default blacklist should override
|
|
489
|
+
const result = InterventionChecker.shouldIntervene({
|
|
490
|
+
config: undefined,
|
|
491
|
+
// Not passing securityBlacklist - should use DEFAULT_SECURITY_BLACKLIST
|
|
492
|
+
toolArgs: { command: 'rm -rf /' },
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
expect(result).toBe('required');
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('should allow safe commands to follow normal intervention rules', () => {
|
|
499
|
+
// Safe command should follow normal config
|
|
500
|
+
const result = InterventionChecker.shouldIntervene({
|
|
501
|
+
config: 'never',
|
|
502
|
+
// Not passing securityBlacklist - should use DEFAULT_SECURITY_BLACKLIST
|
|
503
|
+
toolArgs: { command: 'ls -la' },
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
expect(result).toBe('never');
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('should block reading sensitive files', () => {
|
|
510
|
+
// Test with actual default blacklist for sensitive file reading
|
|
511
|
+
const result = InterventionChecker.shouldIntervene({
|
|
512
|
+
config: 'never',
|
|
513
|
+
// Not passing securityBlacklist - should use DEFAULT_SECURITY_BLACKLIST
|
|
514
|
+
toolArgs: { command: 'cat .env' },
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
expect(result).toBe('required');
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
describe('with custom blacklist replacement', () => {
|
|
522
|
+
it('should use custom blacklist instead of default when provided', () => {
|
|
523
|
+
const customBlacklist: SecurityBlacklistConfig = [
|
|
524
|
+
{
|
|
525
|
+
description: 'Block all npm commands in production',
|
|
526
|
+
match: {
|
|
527
|
+
command: { pattern: 'npm.*', type: 'regex' },
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
];
|
|
531
|
+
|
|
532
|
+
// Custom blacklist blocks npm but not rm
|
|
533
|
+
expect(
|
|
534
|
+
InterventionChecker.shouldIntervene({
|
|
535
|
+
config: 'never',
|
|
536
|
+
securityBlacklist: customBlacklist,
|
|
537
|
+
toolArgs: { command: 'npm install' },
|
|
538
|
+
}),
|
|
539
|
+
).toBe('required');
|
|
540
|
+
|
|
541
|
+
// rm is not in custom blacklist, should follow config
|
|
542
|
+
expect(
|
|
543
|
+
InterventionChecker.shouldIntervene({
|
|
544
|
+
config: 'never',
|
|
545
|
+
securityBlacklist: customBlacklist,
|
|
546
|
+
toolArgs: { command: 'rm -rf ~/' },
|
|
547
|
+
}),
|
|
548
|
+
).toBe('never');
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it('should support extending default blacklist with custom rules', () => {
|
|
552
|
+
const extendedBlacklist: SecurityBlacklistConfig = [
|
|
553
|
+
...DEFAULT_SECURITY_BLACKLIST,
|
|
554
|
+
{
|
|
555
|
+
description: 'Block access to production database',
|
|
556
|
+
match: {
|
|
557
|
+
command: { pattern: '.*psql.*production.*', type: 'regex' },
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
];
|
|
561
|
+
|
|
562
|
+
// Default rule still works
|
|
563
|
+
expect(
|
|
564
|
+
InterventionChecker.shouldIntervene({
|
|
565
|
+
config: 'never',
|
|
566
|
+
securityBlacklist: extendedBlacklist,
|
|
567
|
+
toolArgs: { command: 'rm -rf ~/' },
|
|
568
|
+
}),
|
|
569
|
+
).toBe('required');
|
|
570
|
+
|
|
571
|
+
// Custom rule works
|
|
572
|
+
expect(
|
|
573
|
+
InterventionChecker.shouldIntervene({
|
|
574
|
+
config: 'never',
|
|
575
|
+
securityBlacklist: extendedBlacklist,
|
|
576
|
+
toolArgs: { command: 'psql -h production.db' },
|
|
577
|
+
}),
|
|
578
|
+
).toBe('required');
|
|
579
|
+
|
|
580
|
+
// Safe commands pass
|
|
581
|
+
expect(
|
|
582
|
+
InterventionChecker.shouldIntervene({
|
|
583
|
+
config: 'never',
|
|
584
|
+
securityBlacklist: extendedBlacklist,
|
|
585
|
+
toolArgs: { command: 'psql -h localhost' },
|
|
586
|
+
}),
|
|
587
|
+
).toBe('never');
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it('should allow disabling security blacklist by passing empty array', () => {
|
|
591
|
+
// Dangerous command should not be blocked when blacklist is empty
|
|
592
|
+
const result = InterventionChecker.shouldIntervene({
|
|
593
|
+
config: 'never',
|
|
594
|
+
securityBlacklist: [], // Explicitly disable blacklist
|
|
595
|
+
toolArgs: { command: 'rm -rf ~/' },
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
expect(result).toBe('never');
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it('should support project-specific blacklist rules', () => {
|
|
602
|
+
const projectBlacklist: SecurityBlacklistConfig = [
|
|
603
|
+
{
|
|
604
|
+
description: 'Block modifying package.json in CI',
|
|
605
|
+
match: {
|
|
606
|
+
path: { pattern: '.*/package\\.json$', type: 'regex' },
|
|
607
|
+
command: { pattern: '(vim|nano|vi|emacs|code|sed).*', type: 'regex' },
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
];
|
|
611
|
+
|
|
612
|
+
// Should block editing package.json
|
|
613
|
+
expect(
|
|
614
|
+
InterventionChecker.shouldIntervene({
|
|
615
|
+
config: 'never',
|
|
616
|
+
securityBlacklist: projectBlacklist,
|
|
617
|
+
toolArgs: {
|
|
618
|
+
command: 'vim package.json',
|
|
619
|
+
path: '/project/package.json',
|
|
620
|
+
},
|
|
621
|
+
}),
|
|
622
|
+
).toBe('required');
|
|
623
|
+
|
|
624
|
+
// Should allow reading package.json
|
|
625
|
+
expect(
|
|
626
|
+
InterventionChecker.shouldIntervene({
|
|
627
|
+
config: 'never',
|
|
628
|
+
securityBlacklist: projectBlacklist,
|
|
629
|
+
toolArgs: {
|
|
630
|
+
command: 'cat package.json',
|
|
631
|
+
path: '/project/package.json',
|
|
632
|
+
},
|
|
633
|
+
}),
|
|
634
|
+
).toBe('never');
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
|
|
221
639
|
describe('Integration scenarios', () => {
|
|
222
640
|
it('should handle Bash tool scenario', () => {
|
|
223
641
|
const config: HumanInterventionConfig = [
|
|
@@ -229,24 +647,44 @@ describe('InterventionChecker', () => {
|
|
|
229
647
|
];
|
|
230
648
|
|
|
231
649
|
// Safe commands - never
|
|
232
|
-
expect(
|
|
233
|
-
|
|
234
|
-
|
|
650
|
+
expect(
|
|
651
|
+
InterventionChecker.shouldIntervene({
|
|
652
|
+
config,
|
|
653
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
654
|
+
toolArgs: { command: 'ls:' },
|
|
655
|
+
}),
|
|
656
|
+
).toBe('never');
|
|
235
657
|
|
|
236
658
|
// Git commands - require
|
|
237
659
|
expect(
|
|
238
|
-
InterventionChecker.shouldIntervene({
|
|
660
|
+
InterventionChecker.shouldIntervene({
|
|
661
|
+
config,
|
|
662
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
663
|
+
toolArgs: { command: 'git add:.' },
|
|
664
|
+
}),
|
|
239
665
|
).toBe('required');
|
|
240
666
|
expect(
|
|
241
|
-
InterventionChecker.shouldIntervene({
|
|
667
|
+
InterventionChecker.shouldIntervene({
|
|
668
|
+
config,
|
|
669
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
670
|
+
toolArgs: { command: 'git commit:-m' },
|
|
671
|
+
}),
|
|
242
672
|
).toBe('required');
|
|
243
673
|
|
|
244
674
|
// Dangerous commands - require
|
|
245
|
-
expect(InterventionChecker.shouldIntervene({ config, toolArgs: { command: 'rm:-rf' } })).toBe(
|
|
246
|
-
'required',
|
|
247
|
-
);
|
|
248
675
|
expect(
|
|
249
|
-
InterventionChecker.shouldIntervene({
|
|
676
|
+
InterventionChecker.shouldIntervene({
|
|
677
|
+
config,
|
|
678
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
679
|
+
toolArgs: { command: 'rm:-rf' },
|
|
680
|
+
}),
|
|
681
|
+
).toBe('required');
|
|
682
|
+
expect(
|
|
683
|
+
InterventionChecker.shouldIntervene({
|
|
684
|
+
config,
|
|
685
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
686
|
+
toolArgs: { command: 'npm install' },
|
|
687
|
+
}),
|
|
250
688
|
).toBe('required');
|
|
251
689
|
});
|
|
252
690
|
|
|
@@ -260,13 +698,18 @@ describe('InterventionChecker', () => {
|
|
|
260
698
|
expect(
|
|
261
699
|
InterventionChecker.shouldIntervene({
|
|
262
700
|
config,
|
|
701
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
263
702
|
toolArgs: { path: '/Users/project/file.ts' },
|
|
264
703
|
}),
|
|
265
704
|
).toBe('never');
|
|
266
705
|
|
|
267
706
|
// Outside project - require
|
|
268
707
|
expect(
|
|
269
|
-
InterventionChecker.shouldIntervene({
|
|
708
|
+
InterventionChecker.shouldIntervene({
|
|
709
|
+
config,
|
|
710
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
711
|
+
toolArgs: { path: '/tmp/file.ts' },
|
|
712
|
+
}),
|
|
270
713
|
).toBe('required');
|
|
271
714
|
});
|
|
272
715
|
|
|
@@ -274,8 +717,35 @@ describe('InterventionChecker', () => {
|
|
|
274
717
|
const config: HumanInterventionConfig = 'required';
|
|
275
718
|
|
|
276
719
|
expect(
|
|
277
|
-
InterventionChecker.shouldIntervene({
|
|
720
|
+
InterventionChecker.shouldIntervene({
|
|
721
|
+
config,
|
|
722
|
+
securityBlacklist: [], // Disable blacklist for this test
|
|
723
|
+
toolArgs: { url: 'https://example.com' },
|
|
724
|
+
}),
|
|
278
725
|
).toBe('required');
|
|
279
726
|
});
|
|
727
|
+
|
|
728
|
+
it('should handle security blacklist overriding user config', () => {
|
|
729
|
+
const config: HumanInterventionConfig = 'never';
|
|
730
|
+
const blacklist: SecurityBlacklistConfig = DEFAULT_SECURITY_BLACKLIST;
|
|
731
|
+
|
|
732
|
+
// Dangerous command blocked even with 'never' config
|
|
733
|
+
expect(
|
|
734
|
+
InterventionChecker.shouldIntervene({
|
|
735
|
+
config,
|
|
736
|
+
securityBlacklist: blacklist,
|
|
737
|
+
toolArgs: { command: 'rm -rf /' },
|
|
738
|
+
}),
|
|
739
|
+
).toBe('required');
|
|
740
|
+
|
|
741
|
+
// Safe command follows config
|
|
742
|
+
expect(
|
|
743
|
+
InterventionChecker.shouldIntervene({
|
|
744
|
+
config,
|
|
745
|
+
securityBlacklist: blacklist,
|
|
746
|
+
toolArgs: { command: 'ls -la' },
|
|
747
|
+
}),
|
|
748
|
+
).toBe('never');
|
|
749
|
+
});
|
|
280
750
|
});
|
|
281
751
|
});
|