sapper-ai 0.6.2 → 0.8.0

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.
Files changed (57) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +416 -0
  4. package/dist/guard/ScanCache.d.ts +42 -0
  5. package/dist/guard/ScanCache.d.ts.map +1 -0
  6. package/dist/guard/ScanCache.js +261 -0
  7. package/dist/guard/WarningStore.d.ts +33 -0
  8. package/dist/guard/WarningStore.d.ts.map +1 -0
  9. package/dist/guard/WarningStore.js +305 -0
  10. package/dist/guard/getDefaultPolicy.d.ts +3 -0
  11. package/dist/guard/getDefaultPolicy.d.ts.map +1 -0
  12. package/dist/guard/getDefaultPolicy.js +23 -0
  13. package/dist/guard/hooks/guardCheck.d.ts +12 -0
  14. package/dist/guard/hooks/guardCheck.d.ts.map +1 -0
  15. package/dist/guard/hooks/guardCheck.js +137 -0
  16. package/dist/guard/hooks/guardScan.d.ts +27 -0
  17. package/dist/guard/hooks/guardScan.d.ts.map +1 -0
  18. package/dist/guard/hooks/guardScan.js +242 -0
  19. package/dist/guard/scanSingleSkill.d.ts +11 -0
  20. package/dist/guard/scanSingleSkill.d.ts.map +1 -0
  21. package/dist/guard/scanSingleSkill.js +82 -0
  22. package/dist/guard/setup.d.ts +44 -0
  23. package/dist/guard/setup.d.ts.map +1 -0
  24. package/dist/guard/setup.js +296 -0
  25. package/dist/guard/types.d.ts +43 -0
  26. package/dist/guard/types.d.ts.map +1 -0
  27. package/dist/guard/types.js +2 -0
  28. package/dist/openclaw/contextAdapter.d.ts +8 -0
  29. package/dist/openclaw/contextAdapter.d.ts.map +1 -0
  30. package/dist/openclaw/contextAdapter.js +30 -0
  31. package/dist/openclaw/detect.d.ts +22 -0
  32. package/dist/openclaw/detect.d.ts.map +1 -0
  33. package/dist/openclaw/detect.js +217 -0
  34. package/dist/openclaw/docker/DockerSandbox.d.ts +59 -0
  35. package/dist/openclaw/docker/DockerSandbox.d.ts.map +1 -0
  36. package/dist/openclaw/docker/DockerSandbox.js +372 -0
  37. package/dist/openclaw/docker/HoneytokenGenerator.d.ts +20 -0
  38. package/dist/openclaw/docker/HoneytokenGenerator.d.ts.map +1 -0
  39. package/dist/openclaw/docker/HoneytokenGenerator.js +224 -0
  40. package/dist/openclaw/docker/OpenClawTestRunner.d.ts +26 -0
  41. package/dist/openclaw/docker/OpenClawTestRunner.d.ts.map +1 -0
  42. package/dist/openclaw/docker/OpenClawTestRunner.js +93 -0
  43. package/dist/openclaw/docker/TrafficAnalyzer.d.ts +16 -0
  44. package/dist/openclaw/docker/TrafficAnalyzer.d.ts.map +1 -0
  45. package/dist/openclaw/docker/TrafficAnalyzer.js +260 -0
  46. package/dist/openclaw/scanner.d.ts +74 -0
  47. package/dist/openclaw/scanner.d.ts.map +1 -0
  48. package/dist/openclaw/scanner.js +452 -0
  49. package/dist/postinstall.d.ts.map +1 -1
  50. package/dist/postinstall.js +40 -1
  51. package/dist/utils/fs.d.ts.map +1 -1
  52. package/dist/utils/fs.js +7 -1
  53. package/package.json +9 -5
  54. package/src/openclaw/docker/Dockerfile +23 -0
  55. package/src/openclaw/docker/docker-compose.yml +65 -0
  56. package/src/openclaw/docker/install-ca.sh +30 -0
  57. package/src/openclaw/docker/test-runner.sh +125 -0
package/dist/cli.d.ts CHANGED
@@ -1,3 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  export declare function runCli(argv?: string[]): Promise<number>;
3
+ type GuardModuleLoader = (modulePath: string) => Promise<Record<string, unknown>>;
4
+ export declare function __setGuardModuleLoaderForTests(loader: GuardModuleLoader | null): void;
5
+ export {};
3
6
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAiBA,wBAAsB,MAAM,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CAmFpF"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAoBA,wBAAsB,MAAM,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CA+HpF;AA4WD,KAAK,iBAAiB,GAAG,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AASjF,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,GAAG,IAAI,CAErF"}
package/dist/cli.js CHANGED
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.runCli = runCli;
41
+ exports.__setGuardModuleLoaderForTests = __setGuardModuleLoaderForTests;
41
42
  const node_fs_1 = require("node:fs");
42
43
  const node_os_1 = require("node:os");
43
44
  const node_path_1 = require("node:path");
@@ -49,7 +50,10 @@ const harden_1 = require("./harden");
49
50
  const quarantine_1 = require("./quarantine");
50
51
  const wrapConfig_1 = require("./mcp/wrapConfig");
51
52
  const scan_1 = require("./scan");
53
+ const detect_1 = require("./openclaw/detect");
54
+ const scanner_1 = require("./openclaw/scanner");
52
55
  const env_1 = require("./utils/env");
56
+ const setup_1 = require("./guard/setup");
53
57
  async function runCli(argv = process.argv.slice(2)) {
54
58
  if (argv[0] === '--help' || argv[0] === '-h') {
55
59
  printUsage();
@@ -83,6 +87,41 @@ async function runCli(argv = process.argv.slice(2)) {
83
87
  }
84
88
  return scanExitCode;
85
89
  }
90
+ if (argv[0] === 'openclaw') {
91
+ if (argv[1] === '--help' || argv[1] === '-h') {
92
+ printUsage();
93
+ return 0;
94
+ }
95
+ if (argv.length > 1) {
96
+ printUsage();
97
+ return 1;
98
+ }
99
+ return runOpenClawWizard();
100
+ }
101
+ if (argv[0] === 'setup') {
102
+ if (argv[1] === '--help' || argv[1] === '-h') {
103
+ printUsage();
104
+ return 0;
105
+ }
106
+ const parsed = parseSetupArgs(argv.slice(1));
107
+ if (!parsed) {
108
+ printUsage();
109
+ return 1;
110
+ }
111
+ return runSetupCommand(parsed);
112
+ }
113
+ if (argv[0] === 'guard') {
114
+ if (argv[1] === '--help' || argv[1] === '-h') {
115
+ printUsage();
116
+ return 0;
117
+ }
118
+ const parsed = parseGuardArgs(argv.slice(1));
119
+ if (!parsed) {
120
+ printUsage();
121
+ return 1;
122
+ }
123
+ return runGuardCommand(parsed);
124
+ }
86
125
  if (argv[0] === 'harden') {
87
126
  const parsed = parseHardenArgs(argv.slice(1));
88
127
  if (!parsed) {
@@ -135,9 +174,19 @@ Usage:
135
174
  sapper-ai scan --harden After scan, offer to apply recommended hardening
136
175
  sapper-ai scan --no-open Skip opening report in browser
137
176
  sapper-ai scan --no-save Skip saving scan results to ~/.sapperai/scans/
177
+ sapper-ai openclaw OpenClaw skill security scanner
138
178
  sapper-ai harden Plan recommended setup changes (no writes)
139
179
  sapper-ai harden --apply Apply recommended project changes
140
180
  sapper-ai harden --include-system Include system changes (home directory)
181
+ sapper-ai setup Register Claude Code hooks for Skill Guard
182
+ sapper-ai setup --remove Remove only sapper-ai Skill Guard hooks
183
+ sapper-ai setup --status Show current Skill Guard hook registration status
184
+ sapper-ai guard scan Run SessionStart guard scan hook
185
+ sapper-ai guard check Run UserPromptSubmit guard check hook
186
+ sapper-ai guard dismiss <name> Dismiss warning by skill name
187
+ sapper-ai guard rescan Clear guard cache, then scan again
188
+ sapper-ai guard cache list Show guard cache entries
189
+ sapper-ai guard cache clear Clear guard cache entries
141
190
  sapper-ai mcp wrap-config Wrap MCP servers to run behind sapperai-proxy (defaults to Claude Code config)
142
191
  sapper-ai mcp unwrap-config Undo MCP wrapping
143
192
  sapper-ai quarantine list List quarantined files
@@ -281,6 +330,239 @@ function parseHardenArgs(argv) {
281
330
  mcpVersion,
282
331
  };
283
332
  }
333
+ function parseSetupArgs(argv) {
334
+ if (argv.length === 0) {
335
+ return { command: 'setup_register' };
336
+ }
337
+ if (argv.length > 1) {
338
+ return null;
339
+ }
340
+ if (argv[0] === '--remove') {
341
+ return { command: 'setup_remove' };
342
+ }
343
+ if (argv[0] === '--status') {
344
+ return { command: 'setup_status' };
345
+ }
346
+ return null;
347
+ }
348
+ function printSetupStatus() {
349
+ const status = (0, setup_1.getStatus)();
350
+ console.log('Skill Guard setup status:');
351
+ console.log(` Claude directory: ${displayPath(status.claudeDirPath)} ${status.claudeDirExists ? '(found)' : '(missing)'}`);
352
+ console.log(` Settings file: ${displayPath(status.settingsPath)} ${status.settingsExists ? '(found)' : '(missing)'}`);
353
+ for (const commandStatus of status.commands) {
354
+ const state = commandStatus.registered ? 'registered' : 'not registered';
355
+ console.log(` ${commandStatus.event}: ${state} (${commandStatus.matchCount})`);
356
+ }
357
+ }
358
+ async function runSetupCommand(args) {
359
+ if (args.command === 'setup_status') {
360
+ printSetupStatus();
361
+ return 0;
362
+ }
363
+ if (args.command === 'setup_register') {
364
+ const result = (0, setup_1.registerHooks)();
365
+ if (!result.ok) {
366
+ console.error(`Claude directory not found: ${displayPath(result.status.claudeDirPath)}`);
367
+ return 1;
368
+ }
369
+ if (result.action === 'already_registered') {
370
+ console.log('Skill Guard hooks are already registered.');
371
+ return 0;
372
+ }
373
+ console.log(`Registered Skill Guard hooks in ${displayPath(result.status.settingsPath)}.`);
374
+ return 0;
375
+ }
376
+ const result = (0, setup_1.removeHooks)();
377
+ if (!result.ok) {
378
+ console.error(`Claude directory not found: ${displayPath(result.status.claudeDirPath)}`);
379
+ return 1;
380
+ }
381
+ if (result.removedCount === 0) {
382
+ console.log('No sapper-ai Skill Guard hooks were registered.');
383
+ return 0;
384
+ }
385
+ console.log(`Removed ${result.removedCount} sapper-ai Skill Guard hook(s).`);
386
+ return 0;
387
+ }
388
+ function parseGuardArgs(argv) {
389
+ const subcommand = argv[0];
390
+ if (!subcommand) {
391
+ return null;
392
+ }
393
+ if (subcommand === 'scan') {
394
+ return argv.length === 1 ? { command: 'guard_scan' } : null;
395
+ }
396
+ if (subcommand === 'check') {
397
+ return argv.length === 1 ? { command: 'guard_check' } : null;
398
+ }
399
+ if (subcommand === 'dismiss') {
400
+ if (argv.length !== 2)
401
+ return null;
402
+ const name = argv[1]?.trim();
403
+ if (!name)
404
+ return null;
405
+ return { command: 'guard_dismiss', name };
406
+ }
407
+ if (subcommand === 'rescan') {
408
+ return argv.length === 1 ? { command: 'guard_rescan' } : null;
409
+ }
410
+ if (subcommand === 'cache') {
411
+ if (argv.length !== 2)
412
+ return null;
413
+ if (argv[1] === 'list')
414
+ return { command: 'guard_cache_list' };
415
+ if (argv[1] === 'clear')
416
+ return { command: 'guard_cache_clear' };
417
+ return null;
418
+ }
419
+ return null;
420
+ }
421
+ function resolveNamedExport(moduleExports, names, label) {
422
+ for (const name of names) {
423
+ const candidate = moduleExports[name];
424
+ if (typeof candidate === 'function') {
425
+ return candidate;
426
+ }
427
+ }
428
+ throw new Error(`Missing export for ${label}`);
429
+ }
430
+ const defaultGuardModuleLoader = async (modulePath) => {
431
+ const fullModulePath = `./guard/${modulePath}`;
432
+ return (await Promise.resolve(`${fullModulePath}`).then(s => __importStar(require(s))));
433
+ };
434
+ let guardModuleLoader = defaultGuardModuleLoader;
435
+ function __setGuardModuleLoaderForTests(loader) {
436
+ guardModuleLoader = loader ?? defaultGuardModuleLoader;
437
+ }
438
+ async function loadGuardModule(modulePath) {
439
+ if (modulePath.includes('..')) {
440
+ throw new Error(`Invalid guard module path: ${modulePath}`);
441
+ }
442
+ return guardModuleLoader(modulePath);
443
+ }
444
+ function toExitCode(value) {
445
+ if (typeof value === 'number' && Number.isInteger(value)) {
446
+ return value;
447
+ }
448
+ if (isObject(value) && typeof value.exitCode === 'number' && Number.isInteger(value.exitCode)) {
449
+ return value.exitCode;
450
+ }
451
+ return 0;
452
+ }
453
+ function isObject(value) {
454
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
455
+ }
456
+ async function runGuardScanHook() {
457
+ const moduleExports = await loadGuardModule('hooks/guardScan');
458
+ const hook = resolveNamedExport(moduleExports, ['guardScan', 'runGuardScan', 'default'], 'guard scan hook');
459
+ const result = await hook();
460
+ return toExitCode(result);
461
+ }
462
+ async function runGuardCheckHook() {
463
+ const moduleExports = await loadGuardModule('hooks/guardCheck');
464
+ const hook = resolveNamedExport(moduleExports, ['guardCheck', 'runGuardCheck', 'default'], 'guard check hook');
465
+ const result = await hook();
466
+ return toExitCode(result);
467
+ }
468
+ async function createGuardClassInstance(modulePath, classNames) {
469
+ const moduleExports = await loadGuardModule(modulePath);
470
+ const Constructor = resolveNamedExport(moduleExports, classNames, modulePath);
471
+ const GuardConstructor = Constructor;
472
+ return new GuardConstructor();
473
+ }
474
+ async function clearGuardCache() {
475
+ const scanCache = await createGuardClassInstance('ScanCache', ['ScanCache', 'default']);
476
+ const clearMethod = scanCache.clear;
477
+ if (typeof clearMethod !== 'function') {
478
+ throw new Error('ScanCache.clear() is not available');
479
+ }
480
+ await clearMethod.call(scanCache);
481
+ }
482
+ async function listGuardCacheEntries() {
483
+ const scanCache = await createGuardClassInstance('ScanCache', ['ScanCache', 'default']);
484
+ const listMethod = scanCache.list;
485
+ if (typeof listMethod !== 'function') {
486
+ throw new Error('ScanCache.list() is not available');
487
+ }
488
+ const listResult = await listMethod.call(scanCache);
489
+ return Array.isArray(listResult) ? listResult : [];
490
+ }
491
+ async function dismissGuardWarning(name) {
492
+ const warningStore = await createGuardClassInstance('WarningStore', ['WarningStore', 'default']);
493
+ const dismissMethod = warningStore.dismiss;
494
+ if (typeof dismissMethod !== 'function') {
495
+ throw new Error('WarningStore.dismiss() is not available');
496
+ }
497
+ return dismissMethod.call(warningStore, name);
498
+ }
499
+ function printGuardCacheList(entries) {
500
+ if (entries.length === 0) {
501
+ console.log('Guard cache is empty.');
502
+ return;
503
+ }
504
+ console.log('Guard cache entries:');
505
+ for (const entry of entries) {
506
+ if (!isObject(entry)) {
507
+ console.log(` - ${String(entry)}`);
508
+ continue;
509
+ }
510
+ const nestedEntry = isObject(entry.entry) ? entry.entry : undefined;
511
+ const pathValue = typeof entry.path === 'string'
512
+ ? entry.path
513
+ : nestedEntry && typeof nestedEntry.path === 'string'
514
+ ? nestedEntry.path
515
+ : undefined;
516
+ const skillName = typeof entry.skillName === 'string'
517
+ ? entry.skillName
518
+ : nestedEntry && typeof nestedEntry.skillName === 'string'
519
+ ? nestedEntry.skillName
520
+ : undefined;
521
+ const path = typeof pathValue === 'string' ? displayPath(pathValue) : undefined;
522
+ const hash = typeof entry.contentHash === 'string' ? entry.contentHash.slice(0, 12) : undefined;
523
+ const display = [skillName, path, hash].filter(Boolean).join(' | ');
524
+ if (display.length > 0) {
525
+ console.log(` - ${display}`);
526
+ continue;
527
+ }
528
+ console.log(` - ${JSON.stringify(entry)}`);
529
+ }
530
+ }
531
+ function toErrorMessage(error) {
532
+ return error instanceof Error ? error.message : String(error);
533
+ }
534
+ async function runGuardCommand(args) {
535
+ try {
536
+ if (args.command === 'guard_scan') {
537
+ return runGuardScanHook();
538
+ }
539
+ if (args.command === 'guard_check') {
540
+ return runGuardCheckHook();
541
+ }
542
+ if (args.command === 'guard_dismiss') {
543
+ await dismissGuardWarning(args.name);
544
+ console.log(`Dismissed warning for "${args.name}".`);
545
+ return 0;
546
+ }
547
+ if (args.command === 'guard_rescan') {
548
+ await clearGuardCache();
549
+ console.log('Guard cache cleared.');
550
+ return runGuardScanHook();
551
+ }
552
+ if (args.command === 'guard_cache_list') {
553
+ const entries = await listGuardCacheEntries();
554
+ printGuardCacheList(entries);
555
+ return 0;
556
+ }
557
+ await clearGuardCache();
558
+ console.log('Guard cache cleared.');
559
+ return 0;
560
+ }
561
+ catch (error) {
562
+ console.error(`Guard command failed: ${toErrorMessage(error)}`);
563
+ return 1;
564
+ }
565
+ }
284
566
  function parseMcpArgs(argv) {
285
567
  const subcommand = argv[0];
286
568
  const rest = argv.slice(1);
@@ -440,6 +722,140 @@ function displayPath(path) {
440
722
  return '~';
441
723
  return path.startsWith(home + '/') ? `~/${path.slice(home.length + 1)}` : path;
442
724
  }
725
+ function defaultOpenClawAction(dockerAvailable) {
726
+ return dockerAvailable ? 'scan_static_dynamic' : 'scan_static_only';
727
+ }
728
+ function isOpenClawPromptEnabled() {
729
+ return process.stdout.isTTY === true && process.stdin.isTTY === true && (0, env_1.isCiEnv)(process.env) !== true;
730
+ }
731
+ async function promptOpenClawAction(dockerAvailable) {
732
+ if (!isOpenClawPromptEnabled()) {
733
+ return defaultOpenClawAction(dockerAvailable);
734
+ }
735
+ const choices = [];
736
+ if (dockerAvailable) {
737
+ choices.push({
738
+ name: 'Scan all skills (static + dynamic analysis)',
739
+ value: 'scan_static_dynamic',
740
+ });
741
+ }
742
+ choices.push({
743
+ name: 'Scan all skills (static only)',
744
+ value: 'scan_static_only',
745
+ });
746
+ choices.push({
747
+ name: 'Harden configuration',
748
+ value: 'harden',
749
+ });
750
+ return (0, select_1.default)({
751
+ message: 'What would you like to do?',
752
+ choices,
753
+ default: defaultOpenClawAction(dockerAvailable),
754
+ });
755
+ }
756
+ function createOpenClawProgressHandler() {
757
+ if (process.stdout.isTTY !== true) {
758
+ return () => { };
759
+ }
760
+ let previousPhase = null;
761
+ return (event) => {
762
+ const label = event.phase === 'static' ? ' Phase 1 - Static analysis' : ' Phase 2 - Dynamic analysis';
763
+ if (previousPhase !== null && previousPhase !== event.phase) {
764
+ process.stdout.write('\n');
765
+ }
766
+ previousPhase = event.phase;
767
+ process.stdout.write(`\r${label}: ${event.completed}/${event.total}`);
768
+ if (event.completed >= event.total) {
769
+ process.stdout.write('\n');
770
+ }
771
+ };
772
+ }
773
+ async function runOpenClawWizard() {
774
+ console.log('\n Detecting your environment...\n');
775
+ const environment = await (0, detect_1.detectOpenClawEnvironment)({ cwd: process.cwd() });
776
+ console.log(' Found:');
777
+ if (environment.installed) {
778
+ const versionSuffix = environment.version ? ` (v${environment.version})` : '';
779
+ console.log(` OpenClaw Gateway${versionSuffix}`);
780
+ }
781
+ else {
782
+ console.log(' OpenClaw Gateway: not detected');
783
+ }
784
+ if (environment.skillsPaths.length === 0) {
785
+ console.log(' Skills directory: not detected');
786
+ }
787
+ else {
788
+ console.log(` Skills directories: ${environment.skillsPaths.length}`);
789
+ for (const skillPath of environment.skillsPaths) {
790
+ console.log(` - ${displayPath(skillPath)}`);
791
+ }
792
+ }
793
+ console.log(` Skills discovered: ${environment.skillCount}`);
794
+ if (environment.dockerAvailable) {
795
+ const composeStatus = environment.dockerComposeAvailable ? 'yes' : 'no';
796
+ console.log(` Docker: available (compose: ${composeStatus})`);
797
+ }
798
+ else {
799
+ console.log(' Docker: not available');
800
+ }
801
+ console.log();
802
+ if (!environment.installed && environment.skillsPaths.length === 0) {
803
+ console.log(' OpenClaw not detected. Add skills under ~/.openclaw/skills or ./skills and rerun.\n');
804
+ return 1;
805
+ }
806
+ const action = await promptOpenClawAction(environment.dockerAvailable);
807
+ if (action === 'harden') {
808
+ return (0, harden_1.runHarden)({
809
+ apply: true,
810
+ includeSystem: true,
811
+ });
812
+ }
813
+ if (environment.skillCount === 0) {
814
+ console.log(' No skill markdown files found in detected paths.\n');
815
+ return 0;
816
+ }
817
+ const dynamicRequested = action === 'scan_static_dynamic' && environment.dockerAvailable;
818
+ const policy = (0, scanner_1.resolveOpenClawPolicy)({ cwd: process.cwd() });
819
+ const scanResult = await (0, scanner_1.scanSkills)(environment.skillsPaths, policy, {
820
+ dynamicAnalysis: dynamicRequested,
821
+ quarantineOnRisk: true,
822
+ quarantineDir: process.env.SAPPERAI_QUARANTINE_DIR ?? (0, node_path_1.join)((0, node_os_1.homedir)(), '.openclaw', 'quarantine'),
823
+ onProgress: createOpenClawProgressHandler(),
824
+ });
825
+ const safeCount = scanResult.results.filter((entry) => entry.decision === 'safe').length;
826
+ const suspiciousCount = scanResult.results.filter((entry) => entry.decision === 'suspicious').length;
827
+ const quarantinedCount = scanResult.results.filter((entry) => entry.decision === 'quarantined').length;
828
+ console.log('\n Results:');
829
+ console.log(` ${safeCount} skills safe`);
830
+ console.log(` ${suspiciousCount} skills suspicious`);
831
+ console.log(` ${quarantinedCount} skills quarantined`);
832
+ if (dynamicRequested && scanResult.dynamicStatus === 'skipped_unconfigured') {
833
+ console.log('\n Dynamic analysis requested but no dynamic analyzer is configured yet.');
834
+ console.log(' Static analysis results are shown.\n');
835
+ }
836
+ if (dynamicRequested && scanResult.dynamicStatus === 'skipped_unavailable') {
837
+ console.log('\n Dynamic analysis requested but the dynamic analyzer is unavailable in this environment.');
838
+ console.log(' Static analysis results are shown.\n');
839
+ }
840
+ if (quarantinedCount > 0) {
841
+ console.log('\n Quarantined skills:');
842
+ const quarantined = scanResult.results.filter((entry) => entry.decision === 'quarantined');
843
+ for (const entry of quarantined) {
844
+ const reasons = entry.dynamicResult?.findings?.length
845
+ ? entry.dynamicResult.findings.map((finding) => `${finding.honeytoken.envVar} -> ${finding.destination}`)
846
+ : entry.staticResult?.reasons ?? [];
847
+ const reasonText = reasons.length > 0 ? reasons[0] : 'High-risk behavior detected';
848
+ console.log(` - ${entry.skillName} (${displayPath(entry.skillPath)}): ${reasonText}`);
849
+ }
850
+ }
851
+ if (suspiciousCount > 0) {
852
+ console.log('\n Suspicious skills require manual review.\n');
853
+ }
854
+ else {
855
+ console.log('\n Scan complete.\n');
856
+ }
857
+ return quarantinedCount > 0 || suspiciousCount > 0 ? 1 : 0;
858
+ }
443
859
  async function promptScanScope(cwd) {
444
860
  const answer = await (0, select_1.default)({
445
861
  message: 'Scan scope:',
@@ -0,0 +1,42 @@
1
+ import type { ScanCacheEntry, ScanCacheVerificationResult } from './types';
2
+ export interface ScanCacheOptions {
3
+ filePath?: string;
4
+ homeDir?: string;
5
+ keyPath?: string;
6
+ hostName?: string;
7
+ userId?: string;
8
+ readFileFn?: (filePath: string, encoding: BufferEncoding) => Promise<string>;
9
+ writeFileFn?: (filePath: string, content: string) => Promise<void>;
10
+ readBufferFileFn?: (filePath: string) => Promise<Buffer>;
11
+ writeBufferFileFn?: (filePath: string, content: Buffer) => Promise<void>;
12
+ randomBytesFn?: (size: number) => Buffer;
13
+ }
14
+ export declare class ScanCache {
15
+ private readonly cachePath;
16
+ private readonly hmacKeyPath;
17
+ private readonly readFileFn;
18
+ private readonly writeFileFn;
19
+ private readonly readBufferFileFn;
20
+ private readonly writeBufferFileFn;
21
+ private readonly randomBytesFn;
22
+ private state;
23
+ private hmacKey;
24
+ constructor(options?: ScanCacheOptions);
25
+ has(contentHash: string): Promise<boolean>;
26
+ get(contentHash: string): Promise<ScanCacheEntry | null>;
27
+ set(contentHash: string, entry: ScanCacheEntry): Promise<void>;
28
+ list(): Promise<Array<{
29
+ contentHash: string;
30
+ entry: ScanCacheEntry;
31
+ }>>;
32
+ clear(): Promise<void>;
33
+ verify(): Promise<ScanCacheVerificationResult>;
34
+ private loadState;
35
+ private normalizeState;
36
+ private createEmptyState;
37
+ private persist;
38
+ private computeHmac;
39
+ private getOrCreateHmacKey;
40
+ private readExistingHmacKey;
41
+ }
42
+ //# sourceMappingURL=ScanCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScanCache.d.ts","sourceRoot":"","sources":["../../src/guard/ScanCache.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAA;AAiB1E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC5E,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAClE,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACxD,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxE,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;CACzC;AA8GD,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiE;IAC5F,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsD;IAClF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAuC;IACxE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAsD;IACxF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IAExD,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,OAAO,CAAsB;gBAEzB,OAAO,GAAE,gBAAqB;IAoBpC,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK1C,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAaxD,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAa9D,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,cAAc,CAAA;KAAE,CAAC,CAAC;IAatE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,MAAM,IAAI,OAAO,CAAC,2BAA2B,CAAC;YActC,SAAS;IAkBvB,OAAO,CAAC,cAAc;YA0BR,gBAAgB;YAShB,OAAO;YAmBP,WAAW;YAMX,kBAAkB;YAiBlB,mBAAmB;CAWlC"}