getcrabb 0.1.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/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # CRABB - Security Scanner for OpenClaw AI Agents
2
+
3
+ CLI security scanner that produces a CRABB SCORE (0-100) with prioritized findings.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g getcrabb
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ # Scan default OpenClaw installation (~/.openclaw/)
15
+ crabb
16
+
17
+ # Scan custom directory
18
+ crabb --path ./my-openclaw
19
+
20
+ # Output as JSON
21
+ crabb --json
22
+
23
+ # Create shareable score card
24
+ crabb --share
25
+
26
+ # CI-friendly (no colors)
27
+ crabb --no-color
28
+ ```
29
+
30
+ ## Options
31
+
32
+ | Flag | Short | Description |
33
+ |------|-------|-------------|
34
+ | `--path <dir>` | `-p` | Path to OpenClaw directory |
35
+ | `--json` | `-j` | Output results as JSON |
36
+ | `--share` | `-s` | Share score card to crabb.ai |
37
+ | `--no-color` | | Disable colored output |
38
+ | `--help` | `-h` | Show help message |
39
+ | `--version` | `-v` | Show version number |
40
+
41
+ ## Exit Codes
42
+
43
+ | Code | Description |
44
+ |------|-------------|
45
+ | 0 | Score >= 75, no Critical/High findings |
46
+ | 1 | Score < 75 or Critical/High findings present |
47
+ | 2 | Scan failed (IO error, OpenClaw not found) |
48
+
49
+ ## Scanners
50
+
51
+ ### Credentials Scanner (max 40 points)
52
+ Detects API keys, tokens, and secrets in:
53
+ - `openclaw.json`
54
+ - `credentials/*`
55
+ - `agents/*/auth-profiles.json`
56
+ - `agents/*/sessions/*.jsonl`
57
+ - `.env` files
58
+
59
+ Supports: Anthropic, OpenAI, AWS, GitHub, Slack, Stripe, Discord, Telegram, and generic patterns.
60
+
61
+ ### Skills Scanner (max 30 points)
62
+ Static analysis for suspicious patterns in SKILL.md files:
63
+ - **Critical**: Remote code execution, curl piped to bash
64
+ - **High**: Data exfiltration, environment access
65
+ - **Medium**: Broad file access patterns
66
+ - **Low**: General network/file operations
67
+
68
+ ### Permissions Scanner (max 20 points)
69
+ Analyzes `openclaw.json` configuration:
70
+ - Sandbox mode (strict/permissive/disabled)
71
+ - DM policy settings
72
+ - Allowlist wildcards
73
+ - Gateway bind/auth/TLS settings
74
+ - File permissions (700/600)
75
+
76
+ ### Network Scanner (max 10 points)
77
+ Checks gateway configuration and local ports:
78
+ - Gateway bind mode analysis
79
+ - TLS and auth configuration
80
+ - Localhost port scan (18789, 8080, 3000)
81
+
82
+ ## Score Calculation
83
+
84
+ ```
85
+ score = 100 - sum(min(module_cap, module_penalty))
86
+ penalty = sum(severity_base × confidence)
87
+
88
+ Severity base:
89
+ - Critical: 27.5
90
+ - High: 17.5
91
+ - Medium: 7.5
92
+ - Low: 2.5
93
+ ```
94
+
95
+ ## Grades
96
+
97
+ | Grade | Score | Notes |
98
+ |-------|-------|-------|
99
+ | A | 90+ | Excellent security posture |
100
+ | B | 75+ | Good, minor improvements recommended |
101
+ | C | 60+ | Fair, review findings |
102
+ | D | 40+ | Poor, immediate action needed |
103
+ | F | <40 | Critical security issues |
104
+
105
+ **Note**: Critical findings cap the maximum grade at C.
106
+
107
+ ## Privacy
108
+
109
+ - **Offline by default**: No network calls without `--share`
110
+ - **No secrets in output**: All findings show type, file, and line only (redacted)
111
+ - **Share payload**: Contains only aggregates (score, grade, counts)
112
+
113
+ ## License
114
+
115
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,1055 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { parseArgs } from "util";
5
+ import { exit } from "process";
6
+
7
+ // src/config/paths.ts
8
+ import { homedir } from "os";
9
+ import { join } from "path";
10
+ function getDefaultOpenClawPath() {
11
+ return join(homedir(), ".openclaw");
12
+ }
13
+
14
+ // src/scanners/credentials.ts
15
+ import { join as join3 } from "path";
16
+
17
+ // src/utils/fs.ts
18
+ import { readFile, readdir, stat, access } from "fs/promises";
19
+ import { join as join2, relative } from "path";
20
+ import { constants } from "fs";
21
+ async function fileExists(path) {
22
+ try {
23
+ await access(path, constants.F_OK);
24
+ return true;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+ async function readTextFile(path) {
30
+ try {
31
+ return await readFile(path, "utf-8");
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ async function readJsonFile(path) {
37
+ const content = await readTextFile(path);
38
+ if (!content) return null;
39
+ try {
40
+ return JSON.parse(content);
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+ async function getFileStats(path) {
46
+ try {
47
+ return await stat(path);
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+ async function* walkDirectory(dir, basePath = dir) {
53
+ try {
54
+ const entries = await readdir(dir, { withFileTypes: true });
55
+ for (const entry of entries) {
56
+ const fullPath = join2(dir, entry.name);
57
+ const relativePath = relative(basePath, fullPath);
58
+ if (entry.isDirectory()) {
59
+ yield* walkDirectory(fullPath, basePath);
60
+ } else if (entry.isFile()) {
61
+ yield { path: fullPath, relativePath };
62
+ }
63
+ }
64
+ } catch {
65
+ }
66
+ }
67
+
68
+ // src/scanners/credentials.ts
69
+ var CAP = 40;
70
+ var CREDENTIAL_PATTERNS = [
71
+ // Anthropic
72
+ {
73
+ name: "Anthropic API Key",
74
+ pattern: /sk-ant-[a-zA-Z0-9-]{20,}/g,
75
+ severity: "critical",
76
+ confidence: 0.95
77
+ },
78
+ // OpenAI
79
+ {
80
+ name: "OpenAI API Key",
81
+ pattern: /sk-[a-zA-Z0-9]{20,}(?!-ant)/g,
82
+ severity: "critical",
83
+ confidence: 0.9
84
+ },
85
+ // Discord
86
+ {
87
+ name: "Discord Bot Token",
88
+ pattern: /[MN][A-Za-z\d]{23,}\.[\w-]{6}\.[\w-]{27,}/g,
89
+ severity: "critical",
90
+ confidence: 0.95
91
+ },
92
+ // Telegram
93
+ {
94
+ name: "Telegram Bot Token",
95
+ pattern: /\d{8,10}:[A-Za-z0-9_-]{35}/g,
96
+ severity: "critical",
97
+ confidence: 0.9
98
+ },
99
+ // AWS
100
+ {
101
+ name: "AWS Access Key ID",
102
+ pattern: /AKIA[0-9A-Z]{16}/g,
103
+ severity: "critical",
104
+ confidence: 0.95
105
+ },
106
+ {
107
+ name: "AWS Secret Access Key",
108
+ pattern: /(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])/g,
109
+ severity: "high",
110
+ confidence: 0.6
111
+ },
112
+ // GitHub
113
+ {
114
+ name: "GitHub Personal Access Token",
115
+ pattern: /ghp_[a-zA-Z0-9]{36}/g,
116
+ severity: "critical",
117
+ confidence: 0.95
118
+ },
119
+ {
120
+ name: "GitHub OAuth Token",
121
+ pattern: /gho_[a-zA-Z0-9]{36}/g,
122
+ severity: "critical",
123
+ confidence: 0.95
124
+ },
125
+ {
126
+ name: "GitHub Fine-grained PAT",
127
+ pattern: /github_pat_[a-zA-Z0-9_]{22,}/g,
128
+ severity: "critical",
129
+ confidence: 0.95
130
+ },
131
+ // Slack
132
+ {
133
+ name: "Slack Bot Token",
134
+ pattern: /xoxb-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}/g,
135
+ severity: "critical",
136
+ confidence: 0.95
137
+ },
138
+ {
139
+ name: "Slack User Token",
140
+ pattern: /xoxp-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}/g,
141
+ severity: "critical",
142
+ confidence: 0.95
143
+ },
144
+ // Stripe
145
+ {
146
+ name: "Stripe Secret Key",
147
+ pattern: /sk_live_[0-9a-zA-Z]{24,}/g,
148
+ severity: "critical",
149
+ confidence: 0.95
150
+ },
151
+ {
152
+ name: "Stripe Test Key",
153
+ pattern: /sk_test_[0-9a-zA-Z]{24,}/g,
154
+ severity: "medium",
155
+ confidence: 0.9
156
+ },
157
+ // Generic patterns
158
+ {
159
+ name: "Generic API Key",
160
+ pattern: /(?:api[_-]?key|apikey|api[_-]?token)\s*[=:]\s*["']?([a-zA-Z0-9_-]{20,})["']?/gi,
161
+ severity: "high",
162
+ confidence: 0.7
163
+ },
164
+ {
165
+ name: "Generic Secret",
166
+ pattern: /(?:secret|password|passwd|pwd)\s*[=:]\s*["']?([a-zA-Z0-9_!@#$%^&*-]{8,})["']?/gi,
167
+ severity: "high",
168
+ confidence: 0.6
169
+ },
170
+ // Private Keys
171
+ {
172
+ name: "Private Key",
173
+ pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
174
+ severity: "critical",
175
+ confidence: 0.99
176
+ }
177
+ ];
178
+ var PLACEHOLDER_PATTERNS = [
179
+ /^your[-_]?api[-_]?key[-_]?here$/i,
180
+ /^xxx+$/i,
181
+ /^placeholder$/i,
182
+ /^example$/i,
183
+ /^test[-_]?key$/i,
184
+ /^\$\{[^}]+\}$/,
185
+ /^<[^>]+>$/,
186
+ /^{{[^}]+}}$/,
187
+ /^%[^%]+%$/,
188
+ /^INSERT[-_]?YOUR[-_]?KEY$/i,
189
+ /^REPLACE[-_]?ME$/i,
190
+ /^TODO$/i,
191
+ /^changeme$/i
192
+ ];
193
+ function isPlaceholder(value) {
194
+ return PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(value.trim()));
195
+ }
196
+ function extractValue(match) {
197
+ return match[1] ?? match[0];
198
+ }
199
+ async function scanFile(filePath, relativePath) {
200
+ const content = await readTextFile(filePath);
201
+ if (!content) return [];
202
+ const findings = [];
203
+ const lines = content.split("\n");
204
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
205
+ const line = lines[lineNum];
206
+ for (const credPattern of CREDENTIAL_PATTERNS) {
207
+ credPattern.pattern.lastIndex = 0;
208
+ let match;
209
+ while ((match = credPattern.pattern.exec(line)) !== null) {
210
+ const value = extractValue(match);
211
+ if (isPlaceholder(value)) {
212
+ continue;
213
+ }
214
+ if (value.length < 8) {
215
+ continue;
216
+ }
217
+ findings.push({
218
+ scanner: "credentials",
219
+ severity: credPattern.severity,
220
+ title: credPattern.name,
221
+ description: `Detected ${credPattern.name} in configuration file`,
222
+ file: relativePath,
223
+ line: lineNum + 1,
224
+ confidence: credPattern.confidence
225
+ });
226
+ }
227
+ }
228
+ }
229
+ return findings;
230
+ }
231
+ async function scanCredentials(openclawPath) {
232
+ const findings = [];
233
+ const filesToScan = [
234
+ { path: join3(openclawPath, "openclaw.json"), relative: "openclaw.json" },
235
+ { path: join3(openclawPath, ".env"), relative: ".env" }
236
+ ];
237
+ const credentialsDir = join3(openclawPath, "credentials");
238
+ if (await fileExists(credentialsDir)) {
239
+ for await (const file of walkDirectory(credentialsDir, openclawPath)) {
240
+ filesToScan.push({ path: file.path, relative: file.relativePath });
241
+ }
242
+ }
243
+ const agentsDir = join3(openclawPath, "agents");
244
+ if (await fileExists(agentsDir)) {
245
+ for await (const file of walkDirectory(agentsDir, openclawPath)) {
246
+ if (file.relativePath.includes("auth-profiles.json") || file.relativePath.endsWith(".jsonl") || file.relativePath.endsWith(".json")) {
247
+ filesToScan.push({ path: file.path, relative: file.relativePath });
248
+ }
249
+ }
250
+ }
251
+ for (const file of filesToScan) {
252
+ if (await fileExists(file.path)) {
253
+ const fileFindings = await scanFile(file.path, file.relative);
254
+ findings.push(...fileFindings);
255
+ }
256
+ }
257
+ const severityScores = {
258
+ critical: 27.5,
259
+ high: 17.5,
260
+ medium: 7.5,
261
+ low: 2.5
262
+ };
263
+ let penalty = 0;
264
+ for (const finding of findings) {
265
+ penalty += severityScores[finding.severity] * finding.confidence;
266
+ }
267
+ return {
268
+ scanner: "credentials",
269
+ findings,
270
+ penalty: Math.min(penalty, CAP),
271
+ cap: CAP
272
+ };
273
+ }
274
+
275
+ // src/scanners/skills.ts
276
+ import { join as join4 } from "path";
277
+ var CAP2 = 30;
278
+ var SKILL_PATTERNS = [
279
+ // Critical: Remote code execution
280
+ {
281
+ name: "Curl piped to shell",
282
+ pattern: /curl\s+[^|]*\|\s*(?:bash|sh|zsh)/gi,
283
+ severity: "critical",
284
+ confidence: 0.95,
285
+ description: "Downloading and executing remote scripts is extremely dangerous"
286
+ },
287
+ {
288
+ name: "Wget piped to shell",
289
+ pattern: /wget\s+[^|]*\|\s*(?:bash|sh|zsh)/gi,
290
+ severity: "critical",
291
+ confidence: 0.95,
292
+ description: "Downloading and executing remote scripts is extremely dangerous"
293
+ },
294
+ {
295
+ name: "Code execution function",
296
+ pattern: /\b(?:eval|Function)\s*\(/gi,
297
+ severity: "critical",
298
+ confidence: 0.8,
299
+ description: "Dynamic code execution can lead to arbitrary code execution"
300
+ },
301
+ {
302
+ name: "System command execution",
303
+ pattern: /\b(?:os\.system|subprocess\.call|subprocess\.run|child_process)\s*\(/gi,
304
+ severity: "critical",
305
+ confidence: 0.85,
306
+ description: "System command execution may allow arbitrary command injection"
307
+ },
308
+ // Critical: Sensitive file access
309
+ {
310
+ name: "SSH key access",
311
+ pattern: /\.ssh\/(?:id_rsa|id_ed25519|id_dsa|authorized_keys)/gi,
312
+ severity: "critical",
313
+ confidence: 0.9,
314
+ description: "Accessing SSH keys can compromise authentication"
315
+ },
316
+ {
317
+ name: "Password file access",
318
+ pattern: /\/etc\/(?:passwd|shadow)/gi,
319
+ severity: "critical",
320
+ confidence: 0.95,
321
+ description: "Accessing system password files is a security risk"
322
+ },
323
+ // High: Data exfiltration patterns
324
+ {
325
+ name: "POST request with data",
326
+ pattern: /(?:curl|wget|fetch|axios|request)\s+.*(?:-d|--data|POST)/gi,
327
+ severity: "high",
328
+ confidence: 0.7,
329
+ description: "Outbound data transmission may indicate exfiltration"
330
+ },
331
+ {
332
+ name: "Base64 encode and send",
333
+ pattern: /base64.*(?:curl|wget|fetch|send|post)/gi,
334
+ severity: "high",
335
+ confidence: 0.75,
336
+ description: "Encoding and transmitting data may indicate exfiltration"
337
+ },
338
+ {
339
+ name: "Environment variable dump",
340
+ pattern: /\benv\b|\$ENV|\bprocess\.env\b|\bos\.environ\b/gi,
341
+ severity: "high",
342
+ confidence: 0.6,
343
+ description: "Environment access may expose sensitive configuration"
344
+ },
345
+ // Medium: File system access
346
+ {
347
+ name: "Unrestricted file read",
348
+ pattern: /(?:fs\.readFile|open\s*\(|read\s*\().*(?:\*|\.\.)/gi,
349
+ severity: "medium",
350
+ confidence: 0.6,
351
+ description: "Broad file read patterns may access unintended files"
352
+ },
353
+ {
354
+ name: "Home directory access",
355
+ pattern: /~\/|\/home\/|\$HOME/gi,
356
+ severity: "medium",
357
+ confidence: 0.5,
358
+ description: "Accessing user home directory may expose sensitive data"
359
+ },
360
+ {
361
+ name: "Recursive directory operations",
362
+ pattern: /(?:-r|--recursive|walk|glob\*\*)/gi,
363
+ severity: "medium",
364
+ confidence: 0.4,
365
+ description: "Recursive operations may access more files than intended"
366
+ },
367
+ // Low: Suspicious but not necessarily harmful
368
+ {
369
+ name: "Network connection",
370
+ pattern: /(?:socket|connect|http|https):\/\//gi,
371
+ severity: "low",
372
+ confidence: 0.3,
373
+ description: "Network connections should be reviewed for necessity"
374
+ },
375
+ {
376
+ name: "File write operation",
377
+ pattern: /(?:fs\.writeFile|open\s*\([^)]*['"]\s*w|>+\s*[a-zA-Z])/gi,
378
+ severity: "low",
379
+ confidence: 0.4,
380
+ description: "File write operations should be reviewed"
381
+ }
382
+ ];
383
+ async function scanSkillFile(filePath, relativePath) {
384
+ const content = await readTextFile(filePath);
385
+ if (!content) return [];
386
+ const findings = [];
387
+ const lines = content.split("\n");
388
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
389
+ const line = lines[lineNum];
390
+ for (const skillPattern of SKILL_PATTERNS) {
391
+ skillPattern.pattern.lastIndex = 0;
392
+ if (skillPattern.pattern.test(line)) {
393
+ findings.push({
394
+ scanner: "skills",
395
+ severity: skillPattern.severity,
396
+ title: skillPattern.name,
397
+ description: skillPattern.description,
398
+ file: relativePath,
399
+ line: lineNum + 1,
400
+ confidence: skillPattern.confidence
401
+ });
402
+ }
403
+ }
404
+ }
405
+ return findings;
406
+ }
407
+ async function scanSkills(openclawPath) {
408
+ const findings = [];
409
+ const skillsDirs = [
410
+ join4(openclawPath, "skills"),
411
+ join4(openclawPath, "workspace", "skills")
412
+ ];
413
+ for (const skillsDir of skillsDirs) {
414
+ if (await fileExists(skillsDir)) {
415
+ for await (const file of walkDirectory(skillsDir, openclawPath)) {
416
+ if (file.relativePath.endsWith(".md")) {
417
+ const fileFindings = await scanSkillFile(file.path, file.relativePath);
418
+ findings.push(...fileFindings);
419
+ }
420
+ }
421
+ }
422
+ }
423
+ const severityScores = {
424
+ critical: 27.5,
425
+ high: 17.5,
426
+ medium: 7.5,
427
+ low: 2.5
428
+ };
429
+ let penalty = 0;
430
+ for (const finding of findings) {
431
+ penalty += severityScores[finding.severity] * finding.confidence;
432
+ }
433
+ return {
434
+ scanner: "skills",
435
+ findings,
436
+ penalty: Math.min(penalty, CAP2),
437
+ cap: CAP2
438
+ };
439
+ }
440
+
441
+ // src/scanners/permissions.ts
442
+ import { join as join5 } from "path";
443
+ var CAP3 = 20;
444
+ function checkSandboxMode(config) {
445
+ const mode = config.sandbox?.mode;
446
+ if (mode === "disabled") {
447
+ return {
448
+ scanner: "permissions",
449
+ severity: "critical",
450
+ title: "Sandbox disabled",
451
+ description: "Agent sandbox is completely disabled, allowing unrestricted system access",
452
+ file: "openclaw.json",
453
+ confidence: 0.95
454
+ };
455
+ }
456
+ if (mode === "permissive") {
457
+ return {
458
+ scanner: "permissions",
459
+ severity: "high",
460
+ title: "Sandbox in permissive mode",
461
+ description: "Agent sandbox is in permissive mode, may allow unintended access",
462
+ file: "openclaw.json",
463
+ confidence: 0.85
464
+ };
465
+ }
466
+ return null;
467
+ }
468
+ function checkDmPolicy(config) {
469
+ const policy = config.dmPolicy;
470
+ if (policy === "allow") {
471
+ return {
472
+ scanner: "permissions",
473
+ severity: "medium",
474
+ title: "DM policy allows all",
475
+ description: "Direct message policy allows all messages without filtering",
476
+ file: "openclaw.json",
477
+ confidence: 0.7
478
+ };
479
+ }
480
+ return null;
481
+ }
482
+ function checkAllowlist(config) {
483
+ const findings = [];
484
+ const allowlist = config.allowlist ?? [];
485
+ for (const entry of allowlist) {
486
+ if (entry === "*" || entry === "**") {
487
+ findings.push({
488
+ scanner: "permissions",
489
+ severity: "critical",
490
+ title: "Wildcard allowlist",
491
+ description: "Allowlist contains unrestricted wildcard, allowing all access",
492
+ file: "openclaw.json",
493
+ confidence: 0.95
494
+ });
495
+ } else if (entry.includes("*")) {
496
+ findings.push({
497
+ scanner: "permissions",
498
+ severity: "medium",
499
+ title: "Broad allowlist pattern",
500
+ description: `Allowlist pattern "${entry}" may be too permissive`,
501
+ file: "openclaw.json",
502
+ confidence: 0.6
503
+ });
504
+ }
505
+ }
506
+ return findings;
507
+ }
508
+ function checkGateway(config) {
509
+ const findings = [];
510
+ const gateway = config.gateway;
511
+ if (!gateway) return findings;
512
+ if (gateway.bind === "0.0.0.0" || gateway.bind === "::") {
513
+ findings.push({
514
+ scanner: "permissions",
515
+ severity: "high",
516
+ title: "Gateway binds to all interfaces",
517
+ description: "Gateway is exposed on all network interfaces, should bind to localhost",
518
+ file: "openclaw.json",
519
+ confidence: 0.9
520
+ });
521
+ }
522
+ if (gateway.auth === false) {
523
+ findings.push({
524
+ scanner: "permissions",
525
+ severity: "high",
526
+ title: "Gateway authentication disabled",
527
+ description: "Gateway has authentication disabled, allowing unauthenticated access",
528
+ file: "openclaw.json",
529
+ confidence: 0.9
530
+ });
531
+ }
532
+ if (gateway.tls === false && gateway.bind !== "localhost" && gateway.bind !== "127.0.0.1") {
533
+ findings.push({
534
+ scanner: "permissions",
535
+ severity: "medium",
536
+ title: "Gateway TLS disabled",
537
+ description: "Gateway TLS is disabled for non-localhost connections",
538
+ file: "openclaw.json",
539
+ confidence: 0.75
540
+ });
541
+ }
542
+ return findings;
543
+ }
544
+ async function checkFilePermissions(openclawPath) {
545
+ const findings = [];
546
+ const rootStats = await getFileStats(openclawPath);
547
+ if (rootStats) {
548
+ const mode = rootStats.mode & 511;
549
+ if ((mode & 63) !== 0) {
550
+ findings.push({
551
+ scanner: "permissions",
552
+ severity: "medium",
553
+ title: "OpenClaw directory too permissive",
554
+ description: `Directory permissions ${mode.toString(8)} allow group/other access, recommend 700`,
555
+ file: openclawPath,
556
+ confidence: 0.8
557
+ });
558
+ }
559
+ }
560
+ const credentialsDir = join5(openclawPath, "credentials");
561
+ if (await fileExists(credentialsDir)) {
562
+ const credStats = await getFileStats(credentialsDir);
563
+ if (credStats) {
564
+ const mode = credStats.mode & 511;
565
+ if ((mode & 63) !== 0) {
566
+ findings.push({
567
+ scanner: "permissions",
568
+ severity: "high",
569
+ title: "Credentials directory too permissive",
570
+ description: `Credentials permissions ${mode.toString(8)} allow group/other access, recommend 700`,
571
+ file: "credentials/",
572
+ confidence: 0.9
573
+ });
574
+ }
575
+ }
576
+ }
577
+ return findings;
578
+ }
579
+ async function scanPermissions(openclawPath) {
580
+ const findings = [];
581
+ const configPath = join5(openclawPath, "openclaw.json");
582
+ const config = await readJsonFile(configPath);
583
+ if (config) {
584
+ const sandboxFinding = checkSandboxMode(config);
585
+ if (sandboxFinding) findings.push(sandboxFinding);
586
+ const dmFinding = checkDmPolicy(config);
587
+ if (dmFinding) findings.push(dmFinding);
588
+ findings.push(...checkAllowlist(config));
589
+ findings.push(...checkGateway(config));
590
+ }
591
+ findings.push(...await checkFilePermissions(openclawPath));
592
+ const severityScores = {
593
+ critical: 27.5,
594
+ high: 17.5,
595
+ medium: 7.5,
596
+ low: 2.5
597
+ };
598
+ let penalty = 0;
599
+ for (const finding of findings) {
600
+ penalty += severityScores[finding.severity] * finding.confidence;
601
+ }
602
+ return {
603
+ scanner: "permissions",
604
+ findings,
605
+ penalty: Math.min(penalty, CAP3),
606
+ cap: CAP3
607
+ };
608
+ }
609
+
610
+ // src/scanners/network.ts
611
+ import { join as join6 } from "path";
612
+ import { createConnection } from "net";
613
+ var CAP4 = 10;
614
+ var DEFAULT_PORTS = [18789, 8080, 3e3];
615
+ var PORT_SCAN_TIMEOUT = 1e3;
616
+ async function checkPortOpen(port, host = "localhost") {
617
+ return new Promise((resolve) => {
618
+ const socket = createConnection({ port, host, timeout: PORT_SCAN_TIMEOUT });
619
+ socket.on("connect", () => {
620
+ socket.destroy();
621
+ resolve(true);
622
+ });
623
+ socket.on("timeout", () => {
624
+ socket.destroy();
625
+ resolve(false);
626
+ });
627
+ socket.on("error", () => {
628
+ socket.destroy();
629
+ resolve(false);
630
+ });
631
+ });
632
+ }
633
+ async function scanOpenPorts() {
634
+ const findings = [];
635
+ for (const port of DEFAULT_PORTS) {
636
+ const isOpen = await checkPortOpen(port);
637
+ if (isOpen) {
638
+ findings.push({
639
+ scanner: "network",
640
+ severity: "low",
641
+ title: `Port ${port} is open`,
642
+ description: `Localhost port ${port} is listening, verify if this is expected`,
643
+ confidence: 0.5
644
+ });
645
+ }
646
+ }
647
+ return findings;
648
+ }
649
+ function analyzeGatewayConfig(config) {
650
+ const findings = [];
651
+ const gateway = config.gateway;
652
+ if (!gateway) return findings;
653
+ const bind = gateway.bind ?? "localhost";
654
+ const isExposed = bind === "0.0.0.0" || bind === "::";
655
+ if (isExposed) {
656
+ if (!gateway.tls) {
657
+ findings.push({
658
+ scanner: "network",
659
+ severity: "high",
660
+ title: "Exposed gateway without TLS",
661
+ description: "Gateway is exposed to network without TLS encryption",
662
+ file: "openclaw.json",
663
+ confidence: 0.9
664
+ });
665
+ }
666
+ if (!gateway.auth) {
667
+ findings.push({
668
+ scanner: "network",
669
+ severity: "critical",
670
+ title: "Exposed gateway without authentication",
671
+ description: "Gateway is exposed to network without authentication",
672
+ file: "openclaw.json",
673
+ confidence: 0.95
674
+ });
675
+ }
676
+ }
677
+ const port = gateway.port ?? 18789;
678
+ if (port < 1024 && port !== 443 && port !== 80) {
679
+ findings.push({
680
+ scanner: "network",
681
+ severity: "medium",
682
+ title: "Non-standard privileged port",
683
+ description: `Gateway uses privileged port ${port}, requires elevated permissions`,
684
+ file: "openclaw.json",
685
+ confidence: 0.7
686
+ });
687
+ }
688
+ return findings;
689
+ }
690
+ async function scanNetwork(openclawPath) {
691
+ const findings = [];
692
+ const configPath = join6(openclawPath, "openclaw.json");
693
+ const config = await readJsonFile(configPath);
694
+ if (config) {
695
+ findings.push(...analyzeGatewayConfig(config));
696
+ }
697
+ findings.push(...await scanOpenPorts());
698
+ const severityScores = {
699
+ critical: 27.5,
700
+ high: 17.5,
701
+ medium: 7.5,
702
+ low: 2.5
703
+ };
704
+ let penalty = 0;
705
+ for (const finding of findings) {
706
+ penalty += severityScores[finding.severity] * finding.confidence;
707
+ }
708
+ return {
709
+ scanner: "network",
710
+ findings,
711
+ penalty: Math.min(penalty, CAP4),
712
+ cap: CAP4
713
+ };
714
+ }
715
+
716
+ // src/scanners/index.ts
717
+ async function runAllScanners(options) {
718
+ const { openclawPath } = options;
719
+ const results = await Promise.all([
720
+ scanCredentials(openclawPath),
721
+ scanSkills(openclawPath),
722
+ scanPermissions(openclawPath),
723
+ scanNetwork(openclawPath)
724
+ ]);
725
+ return results;
726
+ }
727
+
728
+ // src/scoring/index.ts
729
+ function calculateScore(results) {
730
+ const totalPenalty = results.reduce((sum, r) => sum + r.penalty, 0);
731
+ return Math.max(0, Math.round(100 - totalPenalty));
732
+ }
733
+ function determineGrade(score, findings) {
734
+ const hasCritical = findings.some((f) => f.severity === "critical");
735
+ if (hasCritical) {
736
+ if (score >= 75) return "C";
737
+ if (score >= 60) return "C";
738
+ if (score >= 40) return "D";
739
+ return "F";
740
+ }
741
+ if (score >= 90) return "A";
742
+ if (score >= 75) return "B";
743
+ if (score >= 60) return "C";
744
+ if (score >= 40) return "D";
745
+ return "F";
746
+ }
747
+ function buildScanResult(results, openclawPath) {
748
+ const findings = results.flatMap((r) => r.findings);
749
+ const score = calculateScore(results);
750
+ const grade = determineGrade(score, findings);
751
+ return {
752
+ score,
753
+ grade,
754
+ scanners: results,
755
+ findings,
756
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
757
+ openclawPath
758
+ };
759
+ }
760
+ function getExitCode(result) {
761
+ const hasCriticalOrHigh = result.findings.some(
762
+ (f) => f.severity === "critical" || f.severity === "high"
763
+ );
764
+ if (result.score < 75 || hasCriticalOrHigh) {
765
+ return 1;
766
+ }
767
+ return 0;
768
+ }
769
+ function countBySeverity(findings) {
770
+ return {
771
+ critical: findings.filter((f) => f.severity === "critical").length,
772
+ high: findings.filter((f) => f.severity === "high").length,
773
+ medium: findings.filter((f) => f.severity === "medium").length,
774
+ low: findings.filter((f) => f.severity === "low").length
775
+ };
776
+ }
777
+
778
+ // src/output/terminal.ts
779
+ import chalk from "chalk";
780
+ import ora from "ora";
781
+ import boxen from "boxen";
782
+
783
+ // src/utils/redact.ts
784
+ function formatFindingLocation(file, line) {
785
+ if (line !== void 0) {
786
+ return `${file}:${line}`;
787
+ }
788
+ return file;
789
+ }
790
+
791
+ // src/output/terminal.ts
792
+ var CRAB = "\u{1F980}";
793
+ var GRADE_COLORS = {
794
+ A: chalk.green,
795
+ B: chalk.greenBright,
796
+ C: chalk.yellow,
797
+ D: chalk.hex("#FFA500"),
798
+ F: chalk.red
799
+ };
800
+ var SEVERITY_COLORS = {
801
+ critical: chalk.bgRed.white,
802
+ high: chalk.red,
803
+ medium: chalk.yellow,
804
+ low: chalk.gray
805
+ };
806
+ var SEVERITY_ICONS = {
807
+ critical: "\u{1F6A8}",
808
+ high: "\u26A0\uFE0F",
809
+ medium: "\u{1F7E1}",
810
+ low: "\u2139\uFE0F"
811
+ };
812
+ function createSpinner(text) {
813
+ return ora({
814
+ text,
815
+ spinner: "dots"
816
+ });
817
+ }
818
+ function printHeader() {
819
+ console.log(
820
+ boxen(
821
+ `${CRAB} ${chalk.bold("CRABB")} ${chalk.dim("Security Scanner for OpenClaw")}`,
822
+ {
823
+ padding: { top: 0, bottom: 0, left: 1, right: 1 },
824
+ borderColor: "cyan",
825
+ borderStyle: "round"
826
+ }
827
+ )
828
+ );
829
+ console.log();
830
+ }
831
+ function printScore(result) {
832
+ const gradeColor = GRADE_COLORS[result.grade];
833
+ const counts = countBySeverity(result.findings);
834
+ const scoreDisplay = boxen(
835
+ [
836
+ `${CRAB} ${chalk.bold("CRABB SCORE")}`,
837
+ "",
838
+ ` ${gradeColor(chalk.bold(`${result.score}`))} ${chalk.dim("/ 100")}`,
839
+ ` Grade: ${gradeColor(chalk.bold(result.grade))}`,
840
+ "",
841
+ chalk.dim("\u2500".repeat(24)),
842
+ "",
843
+ `${SEVERITY_ICONS.critical} Critical: ${counts.critical > 0 ? chalk.red(counts.critical) : chalk.dim("0")}`,
844
+ `${SEVERITY_ICONS.high} High: ${counts.high > 0 ? chalk.red(counts.high) : chalk.dim("0")}`,
845
+ `${SEVERITY_ICONS.medium} Medium: ${counts.medium > 0 ? chalk.yellow(counts.medium) : chalk.dim("0")}`,
846
+ `${SEVERITY_ICONS.low} Low: ${counts.low > 0 ? chalk.gray(counts.low) : chalk.dim("0")}`
847
+ ].join("\n"),
848
+ {
849
+ padding: 1,
850
+ borderColor: result.grade === "A" || result.grade === "B" ? "green" : result.grade === "C" ? "yellow" : "red",
851
+ borderStyle: "round"
852
+ }
853
+ );
854
+ console.log(scoreDisplay);
855
+ console.log();
856
+ }
857
+ function printFindings(findings) {
858
+ if (findings.length === 0) {
859
+ console.log(chalk.green(`${CRAB} No security issues found!`));
860
+ return;
861
+ }
862
+ const sortedFindings = [...findings].sort((a, b) => {
863
+ const order = { critical: 0, high: 1, medium: 2, low: 3 };
864
+ return order[a.severity] - order[b.severity];
865
+ });
866
+ console.log(chalk.bold("Findings:\n"));
867
+ for (const finding of sortedFindings) {
868
+ const severityColor = SEVERITY_COLORS[finding.severity];
869
+ const icon = SEVERITY_ICONS[finding.severity];
870
+ console.log(`${icon} ${severityColor(finding.severity.toUpperCase())} ${chalk.bold(finding.title)}`);
871
+ console.log(` ${chalk.dim(finding.description)}`);
872
+ if (finding.file) {
873
+ console.log(` ${chalk.cyan(formatFindingLocation(finding.file, finding.line))}`);
874
+ }
875
+ console.log();
876
+ }
877
+ }
878
+ function printScannerSummary(result) {
879
+ console.log(chalk.bold("Scanner Summary:\n"));
880
+ for (const scanner of result.scanners) {
881
+ const penaltyColor = scanner.penalty > 0 ? chalk.red : chalk.green;
882
+ const bar = createProgressBar(scanner.cap - scanner.penalty, scanner.cap);
883
+ console.log(
884
+ ` ${scanner.scanner.padEnd(12)} ${bar} ${penaltyColor(`-${scanner.penalty.toFixed(1)}`)} ${chalk.dim(`/ ${scanner.cap}`)}`
885
+ );
886
+ }
887
+ console.log();
888
+ }
889
+ function createProgressBar(value, max, width = 20) {
890
+ const percentage = Math.max(0, Math.min(1, value / max));
891
+ const filled = Math.round(percentage * width);
892
+ const empty = width - filled;
893
+ const color = percentage >= 0.75 ? chalk.green : percentage >= 0.5 ? chalk.yellow : chalk.red;
894
+ return color("\u2588".repeat(filled)) + chalk.dim("\u2591".repeat(empty));
895
+ }
896
+ function printError(message) {
897
+ console.error(chalk.red(`\u2717 Error: ${message}`));
898
+ }
899
+ function printSuccess(message) {
900
+ console.log(chalk.green(`\u2713 ${message}`));
901
+ }
902
+
903
+ // src/output/json.ts
904
+ function formatJsonOutput(result) {
905
+ return JSON.stringify(result, null, 2);
906
+ }
907
+ function buildSharePayload(result) {
908
+ const counts = countBySeverity(result.findings);
909
+ return {
910
+ score: result.score,
911
+ grade: result.grade,
912
+ scannerSummary: result.scanners.map((s) => ({
913
+ scanner: s.scanner,
914
+ findingsCount: s.findings.length,
915
+ penalty: s.penalty
916
+ })),
917
+ criticalCount: counts.critical,
918
+ highCount: counts.high,
919
+ mediumCount: counts.medium,
920
+ lowCount: counts.low,
921
+ timestamp: result.timestamp
922
+ };
923
+ }
924
+
925
+ // src/share/index.ts
926
+ var SHARE_API_URL = "https://crabb.ai/api/share";
927
+ async function shareResult(result) {
928
+ const payload = buildSharePayload(result);
929
+ const response = await fetch(SHARE_API_URL, {
930
+ method: "POST",
931
+ headers: {
932
+ "Content-Type": "application/json"
933
+ },
934
+ body: JSON.stringify(payload)
935
+ });
936
+ if (!response.ok) {
937
+ throw new Error(`Failed to share result: ${response.status} ${response.statusText}`);
938
+ }
939
+ return response.json();
940
+ }
941
+
942
+ // src/cli.ts
943
+ function printHelp() {
944
+ console.log(`
945
+ \u{1F980} CRABB - Security Scanner for OpenClaw AI Agents
946
+
947
+ Usage: crabb [options]
948
+
949
+ Options:
950
+ -p, --path <dir> Path to OpenClaw directory (default: ~/.openclaw/)
951
+ -j, --json Output results as JSON
952
+ -s, --share Share score card to crabb.ai
953
+ --no-color Disable colored output
954
+ -h, --help Show this help message
955
+ -v, --version Show version number
956
+
957
+ Examples:
958
+ crabb # Scan default OpenClaw installation
959
+ crabb --path ./my-openclaw # Scan custom directory
960
+ crabb --json # Output as JSON
961
+ crabb --share # Create shareable score card
962
+
963
+ Exit codes:
964
+ 0 - Score >= 75, no Critical/High findings
965
+ 1 - Score < 75 or Critical/High findings present
966
+ 2 - Scan failed (IO error, OpenClaw not found)
967
+ `);
968
+ }
969
+ function printVersion() {
970
+ console.log("crabb v0.1.0");
971
+ }
972
+ async function main() {
973
+ let options;
974
+ try {
975
+ const { values } = parseArgs({
976
+ options: {
977
+ path: { type: "string", short: "p" },
978
+ json: { type: "boolean", short: "j", default: false },
979
+ share: { type: "boolean", short: "s", default: false },
980
+ "no-color": { type: "boolean", default: false },
981
+ help: { type: "boolean", short: "h", default: false },
982
+ version: { type: "boolean", short: "v", default: false }
983
+ },
984
+ strict: true,
985
+ allowPositionals: false
986
+ });
987
+ if (values.help) {
988
+ printHelp();
989
+ exit(0);
990
+ }
991
+ if (values.version) {
992
+ printVersion();
993
+ exit(0);
994
+ }
995
+ options = {
996
+ path: values.path ?? getDefaultOpenClawPath(),
997
+ json: values.json ?? false,
998
+ share: values.share ?? false,
999
+ noColor: values["no-color"] ?? false
1000
+ };
1001
+ } catch (err) {
1002
+ printError(`Invalid arguments: ${err instanceof Error ? err.message : String(err)}`);
1003
+ printHelp();
1004
+ exit(2);
1005
+ }
1006
+ if (options.noColor) {
1007
+ process.env["FORCE_COLOR"] = "0";
1008
+ }
1009
+ if (!options.json) {
1010
+ printHeader();
1011
+ }
1012
+ if (!await fileExists(options.path)) {
1013
+ printError(`OpenClaw directory not found: ${options.path}`);
1014
+ exit(2);
1015
+ }
1016
+ const spinner = options.json ? null : createSpinner("Scanning OpenClaw configuration...");
1017
+ spinner?.start();
1018
+ try {
1019
+ const scannerResults = await runAllScanners({ openclawPath: options.path });
1020
+ const result = buildScanResult(scannerResults, options.path);
1021
+ spinner?.stop();
1022
+ if (options.json) {
1023
+ console.log(formatJsonOutput(result));
1024
+ } else {
1025
+ printScore(result);
1026
+ printScannerSummary(result);
1027
+ printFindings(result.findings);
1028
+ }
1029
+ if (options.share) {
1030
+ const shareSpinner = options.json ? null : createSpinner("Sharing score card...");
1031
+ shareSpinner?.start();
1032
+ try {
1033
+ const shareResponse = await shareResult(result);
1034
+ shareSpinner?.stop();
1035
+ if (options.json) {
1036
+ console.log(JSON.stringify({ shared: shareResponse }, null, 2));
1037
+ } else {
1038
+ printSuccess(`Score card shared: ${shareResponse.url}`);
1039
+ console.log(` Delete token: ${shareResponse.deleteToken}`);
1040
+ }
1041
+ } catch (err) {
1042
+ shareSpinner?.stop();
1043
+ printError(`Failed to share: ${err instanceof Error ? err.message : String(err)}`);
1044
+ }
1045
+ }
1046
+ const exitCode = getExitCode(result);
1047
+ exit(exitCode);
1048
+ } catch (err) {
1049
+ spinner?.stop();
1050
+ printError(`Scan failed: ${err instanceof Error ? err.message : String(err)}`);
1051
+ exit(2);
1052
+ }
1053
+ }
1054
+ main();
1055
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/config/paths.ts","../src/scanners/credentials.ts","../src/utils/fs.ts","../src/scanners/skills.ts","../src/scanners/permissions.ts","../src/scanners/network.ts","../src/scanners/index.ts","../src/scoring/index.ts","../src/output/terminal.ts","../src/utils/redact.ts","../src/output/json.ts","../src/share/index.ts"],"sourcesContent":["import { parseArgs } from 'node:util';\nimport { exit } from 'node:process';\nimport type { CliOptions } from './types/index.js';\nimport { getDefaultOpenClawPath } from './config/paths.js';\nimport { runAllScanners } from './scanners/index.js';\nimport { buildScanResult, getExitCode } from './scoring/index.js';\nimport {\n printHeader,\n printScore,\n printFindings,\n printScannerSummary,\n printError,\n printSuccess,\n createSpinner,\n} from './output/terminal.js';\nimport { formatJsonOutput } from './output/json.js';\nimport { shareResult } from './share/index.js';\nimport { fileExists } from './utils/fs.js';\n\nfunction parseCliArgs(): CliOptions {\n const { values } = parseArgs({\n options: {\n path: {\n type: 'string',\n short: 'p',\n },\n json: {\n type: 'boolean',\n short: 'j',\n default: false,\n },\n share: {\n type: 'boolean',\n short: 's',\n default: false,\n },\n 'no-color': {\n type: 'boolean',\n default: false,\n },\n help: {\n type: 'boolean',\n short: 'h',\n default: false,\n },\n version: {\n type: 'boolean',\n short: 'v',\n default: false,\n },\n },\n strict: true,\n allowPositionals: false,\n });\n\n return {\n path: values.path ?? getDefaultOpenClawPath(),\n json: values.json ?? false,\n share: values.share ?? false,\n noColor: values['no-color'] ?? false,\n };\n}\n\nfunction printHelp() {\n console.log(`\n\\u{1F980} CRABB - Security Scanner for OpenClaw AI Agents\n\nUsage: crabb [options]\n\nOptions:\n -p, --path <dir> Path to OpenClaw directory (default: ~/.openclaw/)\n -j, --json Output results as JSON\n -s, --share Share score card to crabb.ai\n --no-color Disable colored output\n -h, --help Show this help message\n -v, --version Show version number\n\nExamples:\n crabb # Scan default OpenClaw installation\n crabb --path ./my-openclaw # Scan custom directory\n crabb --json # Output as JSON\n crabb --share # Create shareable score card\n\nExit codes:\n 0 - Score >= 75, no Critical/High findings\n 1 - Score < 75 or Critical/High findings present\n 2 - Scan failed (IO error, OpenClaw not found)\n`);\n}\n\nfunction printVersion() {\n console.log('crabb v0.1.0');\n}\n\nasync function main() {\n let options: CliOptions;\n\n try {\n const { values } = parseArgs({\n options: {\n path: { type: 'string', short: 'p' },\n json: { type: 'boolean', short: 'j', default: false },\n share: { type: 'boolean', short: 's', default: false },\n 'no-color': { type: 'boolean', default: false },\n help: { type: 'boolean', short: 'h', default: false },\n version: { type: 'boolean', short: 'v', default: false },\n },\n strict: true,\n allowPositionals: false,\n });\n\n if (values.help) {\n printHelp();\n exit(0);\n }\n\n if (values.version) {\n printVersion();\n exit(0);\n }\n\n options = {\n path: values.path ?? getDefaultOpenClawPath(),\n json: values.json ?? false,\n share: values.share ?? false,\n noColor: values['no-color'] ?? false,\n };\n } catch (err) {\n printError(`Invalid arguments: ${err instanceof Error ? err.message : String(err)}`);\n printHelp();\n exit(2);\n }\n\n if (options.noColor) {\n process.env['FORCE_COLOR'] = '0';\n }\n\n if (!options.json) {\n printHeader();\n }\n\n if (!await fileExists(options.path)) {\n printError(`OpenClaw directory not found: ${options.path}`);\n exit(2);\n }\n\n const spinner = options.json ? null : createSpinner('Scanning OpenClaw configuration...');\n spinner?.start();\n\n try {\n const scannerResults = await runAllScanners({ openclawPath: options.path });\n const result = buildScanResult(scannerResults, options.path);\n\n spinner?.stop();\n\n if (options.json) {\n console.log(formatJsonOutput(result));\n } else {\n printScore(result);\n printScannerSummary(result);\n printFindings(result.findings);\n }\n\n if (options.share) {\n const shareSpinner = options.json ? null : createSpinner('Sharing score card...');\n shareSpinner?.start();\n\n try {\n const shareResponse = await shareResult(result);\n shareSpinner?.stop();\n\n if (options.json) {\n console.log(JSON.stringify({ shared: shareResponse }, null, 2));\n } else {\n printSuccess(`Score card shared: ${shareResponse.url}`);\n console.log(` Delete token: ${shareResponse.deleteToken}`);\n }\n } catch (err) {\n shareSpinner?.stop();\n printError(`Failed to share: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n const exitCode = getExitCode(result);\n exit(exitCode);\n } catch (err) {\n spinner?.stop();\n printError(`Scan failed: ${err instanceof Error ? err.message : String(err)}`);\n exit(2);\n }\n}\n\nmain();\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nexport function getDefaultOpenClawPath(): string {\n return join(homedir(), '.openclaw');\n}\n\nexport function getOpenClawPaths(basePath: string) {\n return {\n root: basePath,\n config: join(basePath, 'openclaw.json'),\n credentials: join(basePath, 'credentials'),\n agents: join(basePath, 'agents'),\n skills: join(basePath, 'skills'),\n workspaceSkills: join(basePath, 'workspace', 'skills'),\n };\n}\n\nexport const SCAN_FILE_PATTERNS = {\n credentials: [\n 'openclaw.json',\n 'credentials/**/*',\n 'agents/*/agent/auth-profiles.json',\n 'agents/*/sessions/*.jsonl',\n '.env',\n '.env.*',\n ],\n skills: [\n 'skills/**/*.md',\n 'skills/**/SKILL.md',\n 'workspace/skills/**/*.md',\n ],\n} as const;\n","import { join } from 'node:path';\nimport type { Finding, ScannerResult } from '../types/index.js';\nimport { readTextFile, walkDirectory, fileExists } from '../utils/fs.js';\n\nconst CAP = 40;\n\ninterface CredentialPattern {\n name: string;\n pattern: RegExp;\n severity: 'critical' | 'high' | 'medium';\n confidence: number;\n}\n\nconst CREDENTIAL_PATTERNS: CredentialPattern[] = [\n // Anthropic\n {\n name: 'Anthropic API Key',\n pattern: /sk-ant-[a-zA-Z0-9-]{20,}/g,\n severity: 'critical',\n confidence: 0.95,\n },\n // OpenAI\n {\n name: 'OpenAI API Key',\n pattern: /sk-[a-zA-Z0-9]{20,}(?!-ant)/g,\n severity: 'critical',\n confidence: 0.9,\n },\n // Discord\n {\n name: 'Discord Bot Token',\n pattern: /[MN][A-Za-z\\d]{23,}\\.[\\w-]{6}\\.[\\w-]{27,}/g,\n severity: 'critical',\n confidence: 0.95,\n },\n // Telegram\n {\n name: 'Telegram Bot Token',\n pattern: /\\d{8,10}:[A-Za-z0-9_-]{35}/g,\n severity: 'critical',\n confidence: 0.9,\n },\n // AWS\n {\n name: 'AWS Access Key ID',\n pattern: /AKIA[0-9A-Z]{16}/g,\n severity: 'critical',\n confidence: 0.95,\n },\n {\n name: 'AWS Secret Access Key',\n pattern: /(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])/g,\n severity: 'high',\n confidence: 0.6,\n },\n // GitHub\n {\n name: 'GitHub Personal Access Token',\n pattern: /ghp_[a-zA-Z0-9]{36}/g,\n severity: 'critical',\n confidence: 0.95,\n },\n {\n name: 'GitHub OAuth Token',\n pattern: /gho_[a-zA-Z0-9]{36}/g,\n severity: 'critical',\n confidence: 0.95,\n },\n {\n name: 'GitHub Fine-grained PAT',\n pattern: /github_pat_[a-zA-Z0-9_]{22,}/g,\n severity: 'critical',\n confidence: 0.95,\n },\n // Slack\n {\n name: 'Slack Bot Token',\n pattern: /xoxb-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}/g,\n severity: 'critical',\n confidence: 0.95,\n },\n {\n name: 'Slack User Token',\n pattern: /xoxp-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}/g,\n severity: 'critical',\n confidence: 0.95,\n },\n // Stripe\n {\n name: 'Stripe Secret Key',\n pattern: /sk_live_[0-9a-zA-Z]{24,}/g,\n severity: 'critical',\n confidence: 0.95,\n },\n {\n name: 'Stripe Test Key',\n pattern: /sk_test_[0-9a-zA-Z]{24,}/g,\n severity: 'medium',\n confidence: 0.9,\n },\n // Generic patterns\n {\n name: 'Generic API Key',\n pattern: /(?:api[_-]?key|apikey|api[_-]?token)\\s*[=:]\\s*[\"']?([a-zA-Z0-9_-]{20,})[\"']?/gi,\n severity: 'high',\n confidence: 0.7,\n },\n {\n name: 'Generic Secret',\n pattern: /(?:secret|password|passwd|pwd)\\s*[=:]\\s*[\"']?([a-zA-Z0-9_!@#$%^&*-]{8,})[\"']?/gi,\n severity: 'high',\n confidence: 0.6,\n },\n // Private Keys\n {\n name: 'Private Key',\n pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,\n severity: 'critical',\n confidence: 0.99,\n },\n];\n\nconst PLACEHOLDER_PATTERNS = [\n /^your[-_]?api[-_]?key[-_]?here$/i,\n /^xxx+$/i,\n /^placeholder$/i,\n /^example$/i,\n /^test[-_]?key$/i,\n /^\\$\\{[^}]+\\}$/,\n /^<[^>]+>$/,\n /^{{[^}]+}}$/,\n /^%[^%]+%$/,\n /^INSERT[-_]?YOUR[-_]?KEY$/i,\n /^REPLACE[-_]?ME$/i,\n /^TODO$/i,\n /^changeme$/i,\n];\n\nfunction isPlaceholder(value: string): boolean {\n return PLACEHOLDER_PATTERNS.some(pattern => pattern.test(value.trim()));\n}\n\nfunction extractValue(match: RegExpMatchArray): string {\n return match[1] ?? match[0];\n}\n\nasync function scanFile(\n filePath: string,\n relativePath: string\n): Promise<Finding[]> {\n const content = await readTextFile(filePath);\n if (!content) return [];\n\n const findings: Finding[] = [];\n const lines = content.split('\\n');\n\n for (let lineNum = 0; lineNum < lines.length; lineNum++) {\n const line = lines[lineNum]!;\n\n for (const credPattern of CREDENTIAL_PATTERNS) {\n credPattern.pattern.lastIndex = 0;\n\n let match: RegExpExecArray | null;\n while ((match = credPattern.pattern.exec(line)) !== null) {\n const value = extractValue(match);\n\n if (isPlaceholder(value)) {\n continue;\n }\n\n if (value.length < 8) {\n continue;\n }\n\n findings.push({\n scanner: 'credentials',\n severity: credPattern.severity,\n title: credPattern.name,\n description: `Detected ${credPattern.name} in configuration file`,\n file: relativePath,\n line: lineNum + 1,\n confidence: credPattern.confidence,\n });\n }\n }\n }\n\n return findings;\n}\n\nexport async function scanCredentials(openclawPath: string): Promise<ScannerResult> {\n const findings: Finding[] = [];\n\n const filesToScan = [\n { path: join(openclawPath, 'openclaw.json'), relative: 'openclaw.json' },\n { path: join(openclawPath, '.env'), relative: '.env' },\n ];\n\n const credentialsDir = join(openclawPath, 'credentials');\n if (await fileExists(credentialsDir)) {\n for await (const file of walkDirectory(credentialsDir, openclawPath)) {\n filesToScan.push({ path: file.path, relative: file.relativePath });\n }\n }\n\n const agentsDir = join(openclawPath, 'agents');\n if (await fileExists(agentsDir)) {\n for await (const file of walkDirectory(agentsDir, openclawPath)) {\n if (\n file.relativePath.includes('auth-profiles.json') ||\n file.relativePath.endsWith('.jsonl') ||\n file.relativePath.endsWith('.json')\n ) {\n filesToScan.push({ path: file.path, relative: file.relativePath });\n }\n }\n }\n\n for (const file of filesToScan) {\n if (await fileExists(file.path)) {\n const fileFindings = await scanFile(file.path, file.relative);\n findings.push(...fileFindings);\n }\n }\n\n const severityScores = {\n critical: 27.5,\n high: 17.5,\n medium: 7.5,\n low: 2.5,\n };\n\n let penalty = 0;\n for (const finding of findings) {\n penalty += severityScores[finding.severity] * finding.confidence;\n }\n\n return {\n scanner: 'credentials',\n findings,\n penalty: Math.min(penalty, CAP),\n cap: CAP,\n };\n}\n","import { readFile, readdir, stat, access } from 'node:fs/promises';\nimport { join, relative } from 'node:path';\nimport { constants } from 'node:fs';\n\nexport async function fileExists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function readTextFile(path: string): Promise<string | null> {\n try {\n return await readFile(path, 'utf-8');\n } catch {\n return null;\n }\n}\n\nexport async function readJsonFile<T>(path: string): Promise<T | null> {\n const content = await readTextFile(path);\n if (!content) return null;\n try {\n return JSON.parse(content) as T;\n } catch {\n return null;\n }\n}\n\nexport async function getFileStats(path: string) {\n try {\n return await stat(path);\n } catch {\n return null;\n }\n}\n\nexport async function* walkDirectory(\n dir: string,\n basePath: string = dir\n): AsyncGenerator<{ path: string; relativePath: string }> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n const relativePath = relative(basePath, fullPath);\n\n if (entry.isDirectory()) {\n yield* walkDirectory(fullPath, basePath);\n } else if (entry.isFile()) {\n yield { path: fullPath, relativePath };\n }\n }\n } catch {\n // Directory doesn't exist or not accessible\n }\n}\n\nexport function matchesPattern(filePath: string, pattern: string): boolean {\n // Simple glob matching\n const regexPattern = pattern\n .replace(/\\./g, '\\\\.')\n .replace(/\\*\\*/g, '{{GLOBSTAR}}')\n .replace(/\\*/g, '[^/]*')\n .replace(/\\{\\{GLOBSTAR\\}\\}/g, '.*');\n\n return new RegExp(`^${regexPattern}$`).test(filePath);\n}\n\nexport function matchesAnyPattern(filePath: string, patterns: readonly string[]): boolean {\n return patterns.some(pattern => matchesPattern(filePath, pattern));\n}\n","import { join } from 'node:path';\nimport type { Finding, ScannerResult } from '../types/index.js';\nimport { readTextFile, walkDirectory, fileExists } from '../utils/fs.js';\n\nconst CAP = 30;\n\ninterface SkillPattern {\n name: string;\n pattern: RegExp;\n severity: 'critical' | 'high' | 'medium' | 'low';\n confidence: number;\n description: string;\n}\n\nconst SKILL_PATTERNS: SkillPattern[] = [\n // Critical: Remote code execution\n {\n name: 'Curl piped to shell',\n pattern: /curl\\s+[^|]*\\|\\s*(?:bash|sh|zsh)/gi,\n severity: 'critical',\n confidence: 0.95,\n description: 'Downloading and executing remote scripts is extremely dangerous',\n },\n {\n name: 'Wget piped to shell',\n pattern: /wget\\s+[^|]*\\|\\s*(?:bash|sh|zsh)/gi,\n severity: 'critical',\n confidence: 0.95,\n description: 'Downloading and executing remote scripts is extremely dangerous',\n },\n {\n name: 'Code execution function',\n pattern: /\\b(?:eval|Function)\\s*\\(/gi,\n severity: 'critical',\n confidence: 0.8,\n description: 'Dynamic code execution can lead to arbitrary code execution',\n },\n {\n name: 'System command execution',\n pattern: /\\b(?:os\\.system|subprocess\\.call|subprocess\\.run|child_process)\\s*\\(/gi,\n severity: 'critical',\n confidence: 0.85,\n description: 'System command execution may allow arbitrary command injection',\n },\n // Critical: Sensitive file access\n {\n name: 'SSH key access',\n pattern: /\\.ssh\\/(?:id_rsa|id_ed25519|id_dsa|authorized_keys)/gi,\n severity: 'critical',\n confidence: 0.9,\n description: 'Accessing SSH keys can compromise authentication',\n },\n {\n name: 'Password file access',\n pattern: /\\/etc\\/(?:passwd|shadow)/gi,\n severity: 'critical',\n confidence: 0.95,\n description: 'Accessing system password files is a security risk',\n },\n // High: Data exfiltration patterns\n {\n name: 'POST request with data',\n pattern: /(?:curl|wget|fetch|axios|request)\\s+.*(?:-d|--data|POST)/gi,\n severity: 'high',\n confidence: 0.7,\n description: 'Outbound data transmission may indicate exfiltration',\n },\n {\n name: 'Base64 encode and send',\n pattern: /base64.*(?:curl|wget|fetch|send|post)/gi,\n severity: 'high',\n confidence: 0.75,\n description: 'Encoding and transmitting data may indicate exfiltration',\n },\n {\n name: 'Environment variable dump',\n pattern: /\\benv\\b|\\$ENV|\\bprocess\\.env\\b|\\bos\\.environ\\b/gi,\n severity: 'high',\n confidence: 0.6,\n description: 'Environment access may expose sensitive configuration',\n },\n // Medium: File system access\n {\n name: 'Unrestricted file read',\n pattern: /(?:fs\\.readFile|open\\s*\\(|read\\s*\\().*(?:\\*|\\.\\.)/gi,\n severity: 'medium',\n confidence: 0.6,\n description: 'Broad file read patterns may access unintended files',\n },\n {\n name: 'Home directory access',\n pattern: /~\\/|\\/home\\/|\\$HOME/gi,\n severity: 'medium',\n confidence: 0.5,\n description: 'Accessing user home directory may expose sensitive data',\n },\n {\n name: 'Recursive directory operations',\n pattern: /(?:-r|--recursive|walk|glob\\*\\*)/gi,\n severity: 'medium',\n confidence: 0.4,\n description: 'Recursive operations may access more files than intended',\n },\n // Low: Suspicious but not necessarily harmful\n {\n name: 'Network connection',\n pattern: /(?:socket|connect|http|https):\\/\\//gi,\n severity: 'low',\n confidence: 0.3,\n description: 'Network connections should be reviewed for necessity',\n },\n {\n name: 'File write operation',\n pattern: /(?:fs\\.writeFile|open\\s*\\([^)]*['\"]\\s*w|>+\\s*[a-zA-Z])/gi,\n severity: 'low',\n confidence: 0.4,\n description: 'File write operations should be reviewed',\n },\n];\n\nasync function scanSkillFile(\n filePath: string,\n relativePath: string\n): Promise<Finding[]> {\n const content = await readTextFile(filePath);\n if (!content) return [];\n\n const findings: Finding[] = [];\n const lines = content.split('\\n');\n\n for (let lineNum = 0; lineNum < lines.length; lineNum++) {\n const line = lines[lineNum]!;\n\n for (const skillPattern of SKILL_PATTERNS) {\n skillPattern.pattern.lastIndex = 0;\n\n if (skillPattern.pattern.test(line)) {\n findings.push({\n scanner: 'skills',\n severity: skillPattern.severity,\n title: skillPattern.name,\n description: skillPattern.description,\n file: relativePath,\n line: lineNum + 1,\n confidence: skillPattern.confidence,\n });\n }\n }\n }\n\n return findings;\n}\n\nexport async function scanSkills(openclawPath: string): Promise<ScannerResult> {\n const findings: Finding[] = [];\n\n const skillsDirs = [\n join(openclawPath, 'skills'),\n join(openclawPath, 'workspace', 'skills'),\n ];\n\n for (const skillsDir of skillsDirs) {\n if (await fileExists(skillsDir)) {\n for await (const file of walkDirectory(skillsDir, openclawPath)) {\n if (file.relativePath.endsWith('.md')) {\n const fileFindings = await scanSkillFile(file.path, file.relativePath);\n findings.push(...fileFindings);\n }\n }\n }\n }\n\n const severityScores = {\n critical: 27.5,\n high: 17.5,\n medium: 7.5,\n low: 2.5,\n };\n\n let penalty = 0;\n for (const finding of findings) {\n penalty += severityScores[finding.severity] * finding.confidence;\n }\n\n return {\n scanner: 'skills',\n findings,\n penalty: Math.min(penalty, CAP),\n cap: CAP,\n };\n}\n","import { join } from 'node:path';\nimport type { Finding, ScannerResult } from '../types/index.js';\nimport { readJsonFile, getFileStats, fileExists } from '../utils/fs.js';\n\nconst CAP = 20;\n\ninterface OpenClawConfig {\n sandbox?: {\n mode?: 'strict' | 'permissive' | 'disabled';\n };\n dmPolicy?: 'allow' | 'deny' | 'ask';\n allowlist?: string[];\n gateway?: {\n bind?: string;\n auth?: boolean;\n tls?: boolean;\n };\n [key: string]: unknown;\n}\n\nfunction checkSandboxMode(config: OpenClawConfig): Finding | null {\n const mode = config.sandbox?.mode;\n\n if (mode === 'disabled') {\n return {\n scanner: 'permissions',\n severity: 'critical',\n title: 'Sandbox disabled',\n description: 'Agent sandbox is completely disabled, allowing unrestricted system access',\n file: 'openclaw.json',\n confidence: 0.95,\n };\n }\n\n if (mode === 'permissive') {\n return {\n scanner: 'permissions',\n severity: 'high',\n title: 'Sandbox in permissive mode',\n description: 'Agent sandbox is in permissive mode, may allow unintended access',\n file: 'openclaw.json',\n confidence: 0.85,\n };\n }\n\n return null;\n}\n\nfunction checkDmPolicy(config: OpenClawConfig): Finding | null {\n const policy = config.dmPolicy;\n\n if (policy === 'allow') {\n return {\n scanner: 'permissions',\n severity: 'medium',\n title: 'DM policy allows all',\n description: 'Direct message policy allows all messages without filtering',\n file: 'openclaw.json',\n confidence: 0.7,\n };\n }\n\n return null;\n}\n\nfunction checkAllowlist(config: OpenClawConfig): Finding[] {\n const findings: Finding[] = [];\n const allowlist = config.allowlist ?? [];\n\n for (const entry of allowlist) {\n if (entry === '*' || entry === '**') {\n findings.push({\n scanner: 'permissions',\n severity: 'critical',\n title: 'Wildcard allowlist',\n description: 'Allowlist contains unrestricted wildcard, allowing all access',\n file: 'openclaw.json',\n confidence: 0.95,\n });\n } else if (entry.includes('*')) {\n findings.push({\n scanner: 'permissions',\n severity: 'medium',\n title: 'Broad allowlist pattern',\n description: `Allowlist pattern \"${entry}\" may be too permissive`,\n file: 'openclaw.json',\n confidence: 0.6,\n });\n }\n }\n\n return findings;\n}\n\nfunction checkGateway(config: OpenClawConfig): Finding[] {\n const findings: Finding[] = [];\n const gateway = config.gateway;\n\n if (!gateway) return findings;\n\n if (gateway.bind === '0.0.0.0' || gateway.bind === '::') {\n findings.push({\n scanner: 'permissions',\n severity: 'high',\n title: 'Gateway binds to all interfaces',\n description: 'Gateway is exposed on all network interfaces, should bind to localhost',\n file: 'openclaw.json',\n confidence: 0.9,\n });\n }\n\n if (gateway.auth === false) {\n findings.push({\n scanner: 'permissions',\n severity: 'high',\n title: 'Gateway authentication disabled',\n description: 'Gateway has authentication disabled, allowing unauthenticated access',\n file: 'openclaw.json',\n confidence: 0.9,\n });\n }\n\n if (gateway.tls === false && gateway.bind !== 'localhost' && gateway.bind !== '127.0.0.1') {\n findings.push({\n scanner: 'permissions',\n severity: 'medium',\n title: 'Gateway TLS disabled',\n description: 'Gateway TLS is disabled for non-localhost connections',\n file: 'openclaw.json',\n confidence: 0.75,\n });\n }\n\n return findings;\n}\n\nasync function checkFilePermissions(openclawPath: string): Promise<Finding[]> {\n const findings: Finding[] = [];\n\n const rootStats = await getFileStats(openclawPath);\n if (rootStats) {\n const mode = rootStats.mode & 0o777;\n if ((mode & 0o077) !== 0) {\n findings.push({\n scanner: 'permissions',\n severity: 'medium',\n title: 'OpenClaw directory too permissive',\n description: `Directory permissions ${mode.toString(8)} allow group/other access, recommend 700`,\n file: openclawPath,\n confidence: 0.8,\n });\n }\n }\n\n const credentialsDir = join(openclawPath, 'credentials');\n if (await fileExists(credentialsDir)) {\n const credStats = await getFileStats(credentialsDir);\n if (credStats) {\n const mode = credStats.mode & 0o777;\n if ((mode & 0o077) !== 0) {\n findings.push({\n scanner: 'permissions',\n severity: 'high',\n title: 'Credentials directory too permissive',\n description: `Credentials permissions ${mode.toString(8)} allow group/other access, recommend 700`,\n file: 'credentials/',\n confidence: 0.9,\n });\n }\n }\n }\n\n return findings;\n}\n\nexport async function scanPermissions(openclawPath: string): Promise<ScannerResult> {\n const findings: Finding[] = [];\n\n const configPath = join(openclawPath, 'openclaw.json');\n const config = await readJsonFile<OpenClawConfig>(configPath);\n\n if (config) {\n const sandboxFinding = checkSandboxMode(config);\n if (sandboxFinding) findings.push(sandboxFinding);\n\n const dmFinding = checkDmPolicy(config);\n if (dmFinding) findings.push(dmFinding);\n\n findings.push(...checkAllowlist(config));\n findings.push(...checkGateway(config));\n }\n\n findings.push(...await checkFilePermissions(openclawPath));\n\n const severityScores = {\n critical: 27.5,\n high: 17.5,\n medium: 7.5,\n low: 2.5,\n };\n\n let penalty = 0;\n for (const finding of findings) {\n penalty += severityScores[finding.severity] * finding.confidence;\n }\n\n return {\n scanner: 'permissions',\n findings,\n penalty: Math.min(penalty, CAP),\n cap: CAP,\n };\n}\n","import { join } from 'node:path';\nimport { createConnection } from 'node:net';\nimport type { Finding, ScannerResult } from '../types/index.js';\nimport { readJsonFile } from '../utils/fs.js';\n\nconst CAP = 10;\n\ninterface GatewayConfig {\n bind?: string;\n port?: number;\n auth?: boolean;\n tls?: boolean;\n}\n\ninterface OpenClawConfig {\n gateway?: GatewayConfig;\n [key: string]: unknown;\n}\n\nconst DEFAULT_PORTS = [18789, 8080, 3000];\nconst PORT_SCAN_TIMEOUT = 1000;\n\nasync function checkPortOpen(port: number, host: string = 'localhost'): Promise<boolean> {\n return new Promise((resolve) => {\n const socket = createConnection({ port, host, timeout: PORT_SCAN_TIMEOUT });\n\n socket.on('connect', () => {\n socket.destroy();\n resolve(true);\n });\n\n socket.on('timeout', () => {\n socket.destroy();\n resolve(false);\n });\n\n socket.on('error', () => {\n socket.destroy();\n resolve(false);\n });\n });\n}\n\nasync function scanOpenPorts(): Promise<Finding[]> {\n const findings: Finding[] = [];\n\n for (const port of DEFAULT_PORTS) {\n const isOpen = await checkPortOpen(port);\n if (isOpen) {\n findings.push({\n scanner: 'network',\n severity: 'low',\n title: `Port ${port} is open`,\n description: `Localhost port ${port} is listening, verify if this is expected`,\n confidence: 0.5,\n });\n }\n }\n\n return findings;\n}\n\nfunction analyzeGatewayConfig(config: OpenClawConfig): Finding[] {\n const findings: Finding[] = [];\n const gateway = config.gateway;\n\n if (!gateway) return findings;\n\n const bind = gateway.bind ?? 'localhost';\n const isExposed = bind === '0.0.0.0' || bind === '::';\n\n if (isExposed) {\n if (!gateway.tls) {\n findings.push({\n scanner: 'network',\n severity: 'high',\n title: 'Exposed gateway without TLS',\n description: 'Gateway is exposed to network without TLS encryption',\n file: 'openclaw.json',\n confidence: 0.9,\n });\n }\n\n if (!gateway.auth) {\n findings.push({\n scanner: 'network',\n severity: 'critical',\n title: 'Exposed gateway without authentication',\n description: 'Gateway is exposed to network without authentication',\n file: 'openclaw.json',\n confidence: 0.95,\n });\n }\n }\n\n const port = gateway.port ?? 18789;\n if (port < 1024 && port !== 443 && port !== 80) {\n findings.push({\n scanner: 'network',\n severity: 'medium',\n title: 'Non-standard privileged port',\n description: `Gateway uses privileged port ${port}, requires elevated permissions`,\n file: 'openclaw.json',\n confidence: 0.7,\n });\n }\n\n return findings;\n}\n\nexport async function scanNetwork(openclawPath: string): Promise<ScannerResult> {\n const findings: Finding[] = [];\n\n const configPath = join(openclawPath, 'openclaw.json');\n const config = await readJsonFile<OpenClawConfig>(configPath);\n\n if (config) {\n findings.push(...analyzeGatewayConfig(config));\n }\n\n findings.push(...await scanOpenPorts());\n\n const severityScores = {\n critical: 27.5,\n high: 17.5,\n medium: 7.5,\n low: 2.5,\n };\n\n let penalty = 0;\n for (const finding of findings) {\n penalty += severityScores[finding.severity] * finding.confidence;\n }\n\n return {\n scanner: 'network',\n findings,\n penalty: Math.min(penalty, CAP),\n cap: CAP,\n };\n}\n","import type { ScannerResult, Finding } from '../types/index.js';\nimport { scanCredentials } from './credentials.js';\nimport { scanSkills } from './skills.js';\nimport { scanPermissions } from './permissions.js';\nimport { scanNetwork } from './network.js';\n\nexport interface ScanOptions {\n openclawPath: string;\n}\n\nexport async function runAllScanners(options: ScanOptions): Promise<ScannerResult[]> {\n const { openclawPath } = options;\n\n const results = await Promise.all([\n scanCredentials(openclawPath),\n scanSkills(openclawPath),\n scanPermissions(openclawPath),\n scanNetwork(openclawPath),\n ]);\n\n return results;\n}\n\nexport function getAllFindings(results: ScannerResult[]): Finding[] {\n return results.flatMap(r => r.findings);\n}\n\nexport function getTotalPenalty(results: ScannerResult[]): number {\n return results.reduce((sum, r) => sum + r.penalty, 0);\n}\n\nexport { scanCredentials, scanSkills, scanPermissions, scanNetwork };\n","import type { ScannerResult, Finding, Grade, ScanResult } from '../types/index.js';\n\nexport function calculateScore(results: ScannerResult[]): number {\n const totalPenalty = results.reduce((sum, r) => sum + r.penalty, 0);\n return Math.max(0, Math.round(100 - totalPenalty));\n}\n\nexport function determineGrade(score: number, findings: Finding[]): Grade {\n const hasCritical = findings.some(f => f.severity === 'critical');\n\n if (hasCritical) {\n if (score >= 75) return 'C';\n if (score >= 60) return 'C';\n if (score >= 40) return 'D';\n return 'F';\n }\n\n if (score >= 90) return 'A';\n if (score >= 75) return 'B';\n if (score >= 60) return 'C';\n if (score >= 40) return 'D';\n return 'F';\n}\n\nexport function buildScanResult(\n results: ScannerResult[],\n openclawPath: string\n): ScanResult {\n const findings = results.flatMap(r => r.findings);\n const score = calculateScore(results);\n const grade = determineGrade(score, findings);\n\n return {\n score,\n grade,\n scanners: results,\n findings,\n timestamp: new Date().toISOString(),\n openclawPath,\n };\n}\n\nexport function getExitCode(result: ScanResult): number {\n const hasCriticalOrHigh = result.findings.some(\n f => f.severity === 'critical' || f.severity === 'high'\n );\n\n if (result.score < 75 || hasCriticalOrHigh) {\n return 1;\n }\n\n return 0;\n}\n\nexport function countBySeverity(findings: Finding[]) {\n return {\n critical: findings.filter(f => f.severity === 'critical').length,\n high: findings.filter(f => f.severity === 'high').length,\n medium: findings.filter(f => f.severity === 'medium').length,\n low: findings.filter(f => f.severity === 'low').length,\n };\n}\n","import chalk from 'chalk';\nimport ora from 'ora';\nimport boxen from 'boxen';\nimport type { ScanResult, Finding, Grade } from '../types/index.js';\nimport { countBySeverity } from '../scoring/index.js';\nimport { formatFindingLocation } from '../utils/redact.js';\n\nconst CRAB = '\\u{1F980}';\n\nconst GRADE_COLORS: Record<Grade, (text: string) => string> = {\n A: chalk.green,\n B: chalk.greenBright,\n C: chalk.yellow,\n D: chalk.hex('#FFA500'),\n F: chalk.red,\n};\n\nconst SEVERITY_COLORS = {\n critical: chalk.bgRed.white,\n high: chalk.red,\n medium: chalk.yellow,\n low: chalk.gray,\n};\n\nconst SEVERITY_ICONS = {\n critical: '\\u{1F6A8}',\n high: '\\u26A0\\uFE0F',\n medium: '\\u{1F7E1}',\n low: '\\u2139\\uFE0F',\n};\n\nexport function createSpinner(text: string) {\n return ora({\n text,\n spinner: 'dots',\n });\n}\n\nexport function printHeader() {\n console.log(\n boxen(\n `${CRAB} ${chalk.bold('CRABB')} ${chalk.dim('Security Scanner for OpenClaw')}`,\n {\n padding: { top: 0, bottom: 0, left: 1, right: 1 },\n borderColor: 'cyan',\n borderStyle: 'round',\n }\n )\n );\n console.log();\n}\n\nexport function printScore(result: ScanResult) {\n const gradeColor = GRADE_COLORS[result.grade];\n const counts = countBySeverity(result.findings);\n\n const scoreDisplay = boxen(\n [\n `${CRAB} ${chalk.bold('CRABB SCORE')}`,\n '',\n ` ${gradeColor(chalk.bold(`${result.score}`))} ${chalk.dim('/ 100')}`,\n ` Grade: ${gradeColor(chalk.bold(result.grade))}`,\n '',\n chalk.dim('─'.repeat(24)),\n '',\n `${SEVERITY_ICONS.critical} Critical: ${counts.critical > 0 ? chalk.red(counts.critical) : chalk.dim('0')}`,\n `${SEVERITY_ICONS.high} High: ${counts.high > 0 ? chalk.red(counts.high) : chalk.dim('0')}`,\n `${SEVERITY_ICONS.medium} Medium: ${counts.medium > 0 ? chalk.yellow(counts.medium) : chalk.dim('0')}`,\n `${SEVERITY_ICONS.low} Low: ${counts.low > 0 ? chalk.gray(counts.low) : chalk.dim('0')}`,\n ].join('\\n'),\n {\n padding: 1,\n borderColor: result.grade === 'A' || result.grade === 'B' ? 'green' : result.grade === 'C' ? 'yellow' : 'red',\n borderStyle: 'round',\n }\n );\n\n console.log(scoreDisplay);\n console.log();\n}\n\nexport function printFindings(findings: Finding[]) {\n if (findings.length === 0) {\n console.log(chalk.green(`${CRAB} No security issues found!`));\n return;\n }\n\n const sortedFindings = [...findings].sort((a, b) => {\n const order = { critical: 0, high: 1, medium: 2, low: 3 };\n return order[a.severity] - order[b.severity];\n });\n\n console.log(chalk.bold('Findings:\\n'));\n\n for (const finding of sortedFindings) {\n const severityColor = SEVERITY_COLORS[finding.severity];\n const icon = SEVERITY_ICONS[finding.severity];\n\n console.log(`${icon} ${severityColor(finding.severity.toUpperCase())} ${chalk.bold(finding.title)}`);\n console.log(` ${chalk.dim(finding.description)}`);\n if (finding.file) {\n console.log(` ${chalk.cyan(formatFindingLocation(finding.file, finding.line))}`);\n }\n console.log();\n }\n}\n\nexport function printScannerSummary(result: ScanResult) {\n console.log(chalk.bold('Scanner Summary:\\n'));\n\n for (const scanner of result.scanners) {\n const penaltyColor = scanner.penalty > 0 ? chalk.red : chalk.green;\n const bar = createProgressBar(scanner.cap - scanner.penalty, scanner.cap);\n\n console.log(\n ` ${scanner.scanner.padEnd(12)} ${bar} ${penaltyColor(`-${scanner.penalty.toFixed(1)}`)} ${chalk.dim(`/ ${scanner.cap}`)}`\n );\n }\n\n console.log();\n}\n\nfunction createProgressBar(value: number, max: number, width: number = 20): string {\n const percentage = Math.max(0, Math.min(1, value / max));\n const filled = Math.round(percentage * width);\n const empty = width - filled;\n\n const color = percentage >= 0.75 ? chalk.green : percentage >= 0.5 ? chalk.yellow : chalk.red;\n\n return color('\\u2588'.repeat(filled)) + chalk.dim('\\u2591'.repeat(empty));\n}\n\nexport function printError(message: string) {\n console.error(chalk.red(`\\u2717 Error: ${message}`));\n}\n\nexport function printSuccess(message: string) {\n console.log(chalk.green(`\\u2713 ${message}`));\n}\n","/**\n * Redaction utilities - NEVER output real secrets\n */\n\nconst REDACT_PATTERNS = [\n // API Keys with known prefixes\n /sk-[a-zA-Z0-9]{20,}/g,\n /sk-ant-[a-zA-Z0-9-]{20,}/g,\n /xoxb-[a-zA-Z0-9-]+/g,\n /xoxp-[a-zA-Z0-9-]+/g,\n /ghp_[a-zA-Z0-9]{36}/g,\n /gho_[a-zA-Z0-9]{36}/g,\n /github_pat_[a-zA-Z0-9_]{22,}/g,\n\n // Generic secrets\n /[a-zA-Z0-9_-]{32,}/g,\n];\n\nexport function redactValue(value: string): string {\n if (value.length <= 8) {\n return '*'.repeat(value.length);\n }\n\n const visibleStart = 4;\n const visibleEnd = 4;\n const redactedLength = value.length - visibleStart - visibleEnd;\n\n return `${value.slice(0, visibleStart)}${'*'.repeat(Math.max(4, redactedLength))}${value.slice(-visibleEnd)}`;\n}\n\nexport function redactLine(line: string): string {\n let result = line;\n\n for (const pattern of REDACT_PATTERNS) {\n result = result.replace(pattern, (match) => redactValue(match));\n }\n\n return result;\n}\n\nexport function formatFindingLocation(file: string, line?: number): string {\n if (line !== undefined) {\n return `${file}:${line}`;\n }\n return file;\n}\n","import type { ScanResult, SharePayload } from '../types/index.js';\nimport { countBySeverity } from '../scoring/index.js';\n\nexport function formatJsonOutput(result: ScanResult): string {\n return JSON.stringify(result, null, 2);\n}\n\nexport function buildSharePayload(result: ScanResult): SharePayload {\n const counts = countBySeverity(result.findings);\n\n return {\n score: result.score,\n grade: result.grade,\n scannerSummary: result.scanners.map(s => ({\n scanner: s.scanner,\n findingsCount: s.findings.length,\n penalty: s.penalty,\n })),\n criticalCount: counts.critical,\n highCount: counts.high,\n mediumCount: counts.medium,\n lowCount: counts.low,\n timestamp: result.timestamp,\n };\n}\n","import type { ScanResult, ShareResponse } from '../types/index.js';\nimport { buildSharePayload } from '../output/json.js';\n\nconst SHARE_API_URL = 'https://crabb.ai/api/share';\n\nexport async function shareResult(result: ScanResult): Promise<ShareResponse> {\n const payload = buildSharePayload(result);\n\n const response = await fetch(SHARE_API_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to share result: ${response.status} ${response.statusText}`);\n }\n\n return response.json() as Promise<ShareResponse>;\n}\n\nexport async function shareResultMock(result: ScanResult): Promise<ShareResponse> {\n const payload = buildSharePayload(result);\n\n const mockId = `mock-${Date.now().toString(36)}`;\n\n return {\n id: mockId,\n url: `https://crabb.ai/score/${mockId}`,\n deleteToken: `delete-${mockId}`,\n };\n}\n"],"mappings":"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","names":["join","join","join","join","CAP","join","join","CAP","join","join","CAP","join"]}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "getcrabb",
3
+ "version": "0.1.0",
4
+ "description": "CLI security scanner for OpenClaw AI agents",
5
+ "type": "module",
6
+ "main": "./dist/cli.js",
7
+ "bin": {
8
+ "crabb": "./dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsup",
15
+ "dev": "tsx src/cli.ts",
16
+ "lint": "tsc --noEmit",
17
+ "test": "vitest run",
18
+ "test:watch": "vitest",
19
+ "clean": "rm -rf dist"
20
+ },
21
+ "keywords": [
22
+ "security",
23
+ "scanner",
24
+ "openclaw",
25
+ "ai",
26
+ "agents",
27
+ "cli"
28
+ ],
29
+ "author": "",
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "boxen": "^8.0.1",
33
+ "chalk": "^5.3.0",
34
+ "ora": "^8.1.1"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.10.0",
38
+ "tsup": "^8.3.5",
39
+ "tsx": "^4.19.2",
40
+ "typescript": "^5.3.0",
41
+ "vitest": "^2.1.0"
42
+ },
43
+ "engines": {
44
+ "node": ">=18"
45
+ }
46
+ }