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.
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +416 -0
- package/dist/guard/ScanCache.d.ts +42 -0
- package/dist/guard/ScanCache.d.ts.map +1 -0
- package/dist/guard/ScanCache.js +261 -0
- package/dist/guard/WarningStore.d.ts +33 -0
- package/dist/guard/WarningStore.d.ts.map +1 -0
- package/dist/guard/WarningStore.js +305 -0
- package/dist/guard/getDefaultPolicy.d.ts +3 -0
- package/dist/guard/getDefaultPolicy.d.ts.map +1 -0
- package/dist/guard/getDefaultPolicy.js +23 -0
- package/dist/guard/hooks/guardCheck.d.ts +12 -0
- package/dist/guard/hooks/guardCheck.d.ts.map +1 -0
- package/dist/guard/hooks/guardCheck.js +137 -0
- package/dist/guard/hooks/guardScan.d.ts +27 -0
- package/dist/guard/hooks/guardScan.d.ts.map +1 -0
- package/dist/guard/hooks/guardScan.js +242 -0
- package/dist/guard/scanSingleSkill.d.ts +11 -0
- package/dist/guard/scanSingleSkill.d.ts.map +1 -0
- package/dist/guard/scanSingleSkill.js +82 -0
- package/dist/guard/setup.d.ts +44 -0
- package/dist/guard/setup.d.ts.map +1 -0
- package/dist/guard/setup.js +296 -0
- package/dist/guard/types.d.ts +43 -0
- package/dist/guard/types.d.ts.map +1 -0
- package/dist/guard/types.js +2 -0
- package/dist/openclaw/contextAdapter.d.ts +8 -0
- package/dist/openclaw/contextAdapter.d.ts.map +1 -0
- package/dist/openclaw/contextAdapter.js +30 -0
- package/dist/openclaw/detect.d.ts +22 -0
- package/dist/openclaw/detect.d.ts.map +1 -0
- package/dist/openclaw/detect.js +217 -0
- package/dist/openclaw/docker/DockerSandbox.d.ts +59 -0
- package/dist/openclaw/docker/DockerSandbox.d.ts.map +1 -0
- package/dist/openclaw/docker/DockerSandbox.js +372 -0
- package/dist/openclaw/docker/HoneytokenGenerator.d.ts +20 -0
- package/dist/openclaw/docker/HoneytokenGenerator.d.ts.map +1 -0
- package/dist/openclaw/docker/HoneytokenGenerator.js +224 -0
- package/dist/openclaw/docker/OpenClawTestRunner.d.ts +26 -0
- package/dist/openclaw/docker/OpenClawTestRunner.d.ts.map +1 -0
- package/dist/openclaw/docker/OpenClawTestRunner.js +93 -0
- package/dist/openclaw/docker/TrafficAnalyzer.d.ts +16 -0
- package/dist/openclaw/docker/TrafficAnalyzer.d.ts.map +1 -0
- package/dist/openclaw/docker/TrafficAnalyzer.js +260 -0
- package/dist/openclaw/scanner.d.ts +74 -0
- package/dist/openclaw/scanner.d.ts.map +1 -0
- package/dist/openclaw/scanner.js +452 -0
- package/dist/postinstall.d.ts.map +1 -1
- package/dist/postinstall.js +40 -1
- package/dist/utils/fs.d.ts.map +1 -1
- package/dist/utils/fs.js +7 -1
- package/package.json +9 -5
- package/src/openclaw/docker/Dockerfile +23 -0
- package/src/openclaw/docker/docker-compose.yml +65 -0
- package/src/openclaw/docker/install-ca.sh +30 -0
- 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":";
|
|
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"}
|