@cencori/scan 0.1.1 → 0.3.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.js +512 -37
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +512 -37
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +228 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +228 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -4
package/dist/index.js
CHANGED
|
@@ -97,6 +97,12 @@ var SECRET_PATTERNS = [
|
|
|
97
97
|
pattern: /sk_test_[0-9a-zA-Z]{24,}/g,
|
|
98
98
|
severity: "medium"
|
|
99
99
|
},
|
|
100
|
+
{
|
|
101
|
+
name: "Stripe Webhook Secret",
|
|
102
|
+
provider: "Stripe",
|
|
103
|
+
pattern: /whsec_[a-zA-Z0-9]{24,}/g,
|
|
104
|
+
severity: "critical"
|
|
105
|
+
},
|
|
100
106
|
// AWS
|
|
101
107
|
{
|
|
102
108
|
name: "AWS Access Key ID",
|
|
@@ -123,6 +129,12 @@ var SECRET_PATTERNS = [
|
|
|
123
129
|
pattern: /gho_[a-zA-Z0-9]{36}/g,
|
|
124
130
|
severity: "critical"
|
|
125
131
|
},
|
|
132
|
+
{
|
|
133
|
+
name: "GitHub Webhook Secret",
|
|
134
|
+
provider: "GitHub",
|
|
135
|
+
pattern: /sha256=[a-fA-F0-9]{64}/g,
|
|
136
|
+
severity: "high"
|
|
137
|
+
},
|
|
126
138
|
// Telegram
|
|
127
139
|
{
|
|
128
140
|
name: "Telegram Bot Token",
|
|
@@ -205,13 +217,56 @@ var SECRET_PATTERNS = [
|
|
|
205
217
|
pattern: /hf_[a-zA-Z0-9]{34}/g,
|
|
206
218
|
severity: "critical"
|
|
207
219
|
},
|
|
208
|
-
//
|
|
220
|
+
// JWT Secrets
|
|
209
221
|
{
|
|
210
|
-
name: "
|
|
211
|
-
provider: "
|
|
212
|
-
pattern: /[
|
|
213
|
-
|
|
214
|
-
|
|
222
|
+
name: "JWT Secret Assignment",
|
|
223
|
+
provider: "Generic",
|
|
224
|
+
pattern: /JWT_SECRET\s*[:=]\s*["'][^"']{16,}["']/gi,
|
|
225
|
+
severity: "critical"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
name: "Hardcoded JWT Sign",
|
|
229
|
+
provider: "Generic",
|
|
230
|
+
pattern: /jwt\.(sign|verify)\s*\([^,]+,\s*["'][^"']{10,}["']/gi,
|
|
231
|
+
severity: "critical"
|
|
232
|
+
},
|
|
233
|
+
// OAuth Secrets
|
|
234
|
+
{
|
|
235
|
+
name: "OAuth Client Secret",
|
|
236
|
+
provider: "Generic",
|
|
237
|
+
pattern: /client_secret\s*[:=]\s*["'][a-zA-Z0-9_-]{20,}["']/gi,
|
|
238
|
+
severity: "critical"
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
name: "Google Client Secret",
|
|
242
|
+
provider: "Google",
|
|
243
|
+
pattern: /GOOGLE_CLIENT_SECRET\s*[:=]\s*["'][^"']+["']/gi,
|
|
244
|
+
severity: "critical"
|
|
245
|
+
},
|
|
246
|
+
// Database Connection Strings
|
|
247
|
+
{
|
|
248
|
+
name: "MongoDB Connection String",
|
|
249
|
+
provider: "MongoDB",
|
|
250
|
+
pattern: /mongodb(\+srv)?:\/\/[^@\s]+@[^\s"']+/g,
|
|
251
|
+
severity: "critical"
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: "PostgreSQL Connection String",
|
|
255
|
+
provider: "PostgreSQL",
|
|
256
|
+
pattern: /postgres(ql)?:\/\/[^\s"']+/g,
|
|
257
|
+
severity: "critical"
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "MySQL Connection String",
|
|
261
|
+
provider: "MySQL",
|
|
262
|
+
pattern: /mysql:\/\/[^\s"']+/g,
|
|
263
|
+
severity: "critical"
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "Redis Connection String",
|
|
267
|
+
provider: "Redis",
|
|
268
|
+
pattern: /redis:\/\/[^\s"']+/g,
|
|
269
|
+
severity: "high"
|
|
215
270
|
}
|
|
216
271
|
];
|
|
217
272
|
var PII_PATTERNS = [
|
|
@@ -247,7 +302,7 @@ var PII_PATTERNS = [
|
|
|
247
302
|
}
|
|
248
303
|
];
|
|
249
304
|
var ROUTE_PATTERNS = [
|
|
250
|
-
// Next.js API routes
|
|
305
|
+
// Next.js API routes
|
|
251
306
|
{
|
|
252
307
|
name: "Next.js API Route (check for auth)",
|
|
253
308
|
framework: "Next.js",
|
|
@@ -262,6 +317,120 @@ var ROUTE_PATTERNS = [
|
|
|
262
317
|
pattern: /app\.(get|post|put|delete|patch)\s*\(\s*["'`][^"'`]+["'`]\s*,\s*(?!.*auth)/gi,
|
|
263
318
|
severity: "medium",
|
|
264
319
|
description: "Express route - check if auth middleware is applied"
|
|
320
|
+
},
|
|
321
|
+
// Admin routes
|
|
322
|
+
{
|
|
323
|
+
name: "Admin Route Exposed",
|
|
324
|
+
framework: "Generic",
|
|
325
|
+
pattern: /["'`](\/admin|\/dashboard|\/internal|\/private)[^"'`]*["'`]/gi,
|
|
326
|
+
severity: "high",
|
|
327
|
+
description: "Sensitive route - ensure proper authentication"
|
|
328
|
+
}
|
|
329
|
+
];
|
|
330
|
+
var VULNERABILITY_PATTERNS = [
|
|
331
|
+
// Hardcoded URLs
|
|
332
|
+
{
|
|
333
|
+
name: "Localhost URL in Code",
|
|
334
|
+
category: "hardcoded-url",
|
|
335
|
+
pattern: /https?:\/\/localhost[:\d]*/gi,
|
|
336
|
+
severity: "medium",
|
|
337
|
+
description: "Development URL - should use environment variables"
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: "Staging/Dev URL in Code",
|
|
341
|
+
category: "hardcoded-url",
|
|
342
|
+
pattern: /https?:\/\/(staging\.|dev\.|test\.)[^\s"']+/gi,
|
|
343
|
+
severity: "medium",
|
|
344
|
+
description: "Non-production URL in code"
|
|
345
|
+
},
|
|
346
|
+
// Debug artifacts (skip console.log - too many false positives for CLI tools)
|
|
347
|
+
{
|
|
348
|
+
name: "Debug Flag Enabled",
|
|
349
|
+
category: "debug",
|
|
350
|
+
pattern: /DEBUG\s*[:=]\s*(true|1|["']true["'])/gi,
|
|
351
|
+
severity: "medium",
|
|
352
|
+
description: "Debug mode enabled - disable in production"
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
name: "Hardcoded Development Mode",
|
|
356
|
+
category: "debug",
|
|
357
|
+
pattern: /NODE_ENV\s*[:=]\s*["']development["']/gi,
|
|
358
|
+
severity: "medium",
|
|
359
|
+
description: "Hardcoded development mode"
|
|
360
|
+
},
|
|
361
|
+
// CORS issues
|
|
362
|
+
{
|
|
363
|
+
name: "CORS Wildcard Origin",
|
|
364
|
+
category: "cors",
|
|
365
|
+
pattern: /Access-Control-Allow-Origin['":\s]+\*/g,
|
|
366
|
+
severity: "high",
|
|
367
|
+
description: "Allows requests from any origin - security risk"
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
name: "Permissive CORS Config",
|
|
371
|
+
category: "cors",
|
|
372
|
+
pattern: /cors\s*\(\s*\)/g,
|
|
373
|
+
severity: "medium",
|
|
374
|
+
description: "CORS with default (permissive) settings"
|
|
375
|
+
},
|
|
376
|
+
// SQL Injection
|
|
377
|
+
{
|
|
378
|
+
name: "SQL String Concatenation",
|
|
379
|
+
category: "injection",
|
|
380
|
+
pattern: /query\s*\(\s*[`'"].*\$\{.*\}/g,
|
|
381
|
+
severity: "critical",
|
|
382
|
+
description: "Potential SQL injection - use parameterized queries"
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
name: "SQL String Addition",
|
|
386
|
+
category: "injection",
|
|
387
|
+
pattern: /(SELECT|INSERT|UPDATE|DELETE).*["']\s*\+\s*\w+/gi,
|
|
388
|
+
severity: "critical",
|
|
389
|
+
description: "SQL built with string concatenation"
|
|
390
|
+
},
|
|
391
|
+
// XSS Vulnerabilities
|
|
392
|
+
{
|
|
393
|
+
name: "React dangerouslySetInnerHTML",
|
|
394
|
+
category: "xss",
|
|
395
|
+
pattern: /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html/g,
|
|
396
|
+
severity: "high",
|
|
397
|
+
description: "Renders raw HTML - ensure input is sanitized"
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
name: "Direct innerHTML Assignment",
|
|
401
|
+
category: "xss",
|
|
402
|
+
pattern: /\.innerHTML\s*=/g,
|
|
403
|
+
severity: "high",
|
|
404
|
+
description: "Direct HTML injection - use textContent instead"
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
name: "Vue v-html Directive",
|
|
408
|
+
category: "xss",
|
|
409
|
+
pattern: /v-html\s*=\s*["'][^"']+["']/g,
|
|
410
|
+
severity: "high",
|
|
411
|
+
description: "Vue raw HTML binding - ensure input is sanitized"
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
name: "Document Write",
|
|
415
|
+
category: "xss",
|
|
416
|
+
pattern: /document\.write\s*\(/g,
|
|
417
|
+
severity: "high",
|
|
418
|
+
description: "Deprecated and potentially dangerous"
|
|
419
|
+
},
|
|
420
|
+
// Eval and code execution
|
|
421
|
+
{
|
|
422
|
+
name: "Eval Usage",
|
|
423
|
+
category: "injection",
|
|
424
|
+
pattern: /\beval\s*\(/g,
|
|
425
|
+
severity: "critical",
|
|
426
|
+
description: "Code execution - major security risk"
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: "Function Constructor",
|
|
430
|
+
category: "injection",
|
|
431
|
+
pattern: /new\s+Function\s*\(/g,
|
|
432
|
+
severity: "high",
|
|
433
|
+
description: "Dynamic code execution risk"
|
|
265
434
|
}
|
|
266
435
|
];
|
|
267
436
|
var IGNORE_PATTERNS = [
|
|
@@ -302,7 +471,9 @@ var SCANNABLE_EXTENSIONS = [
|
|
|
302
471
|
".sql",
|
|
303
472
|
".sh",
|
|
304
473
|
".bash",
|
|
305
|
-
".zsh"
|
|
474
|
+
".zsh",
|
|
475
|
+
".vue",
|
|
476
|
+
".svelte"
|
|
306
477
|
];
|
|
307
478
|
|
|
308
479
|
// src/scanner/index.ts
|
|
@@ -332,9 +503,14 @@ function isScannable(filePath) {
|
|
|
332
503
|
const ext = path.extname(filePath).toLowerCase();
|
|
333
504
|
return SCANNABLE_EXTENSIONS.includes(ext);
|
|
334
505
|
}
|
|
506
|
+
function isDocOrTestFile(filePath) {
|
|
507
|
+
const lower = filePath.toLowerCase();
|
|
508
|
+
return lower.includes(".test.") || lower.includes(".spec.") || lower.includes("__tests__") || lower.includes("/test/") || lower.includes("/tests/") || lower.endsWith(".md") || lower.includes("/docs/");
|
|
509
|
+
}
|
|
335
510
|
function scanFile(filePath, content) {
|
|
336
511
|
const issues = [];
|
|
337
512
|
const relativePath = filePath;
|
|
513
|
+
const isDocFile = isDocOrTestFile(filePath);
|
|
338
514
|
for (const pattern of SECRET_PATTERNS) {
|
|
339
515
|
pattern.pattern.lastIndex = 0;
|
|
340
516
|
let match;
|
|
@@ -352,47 +528,73 @@ function scanFile(filePath, content) {
|
|
|
352
528
|
});
|
|
353
529
|
}
|
|
354
530
|
}
|
|
355
|
-
|
|
531
|
+
if (!isDocFile) {
|
|
532
|
+
for (const pattern of PII_PATTERNS) {
|
|
533
|
+
pattern.pattern.lastIndex = 0;
|
|
534
|
+
let match;
|
|
535
|
+
while ((match = pattern.pattern.exec(content)) !== null) {
|
|
536
|
+
const matchStr = match[0];
|
|
537
|
+
if (isLikelyFalsePositive(matchStr, pattern.name, filePath)) {
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
const pos = getPosition(content, match.index);
|
|
541
|
+
issues.push({
|
|
542
|
+
type: "pii",
|
|
543
|
+
severity: pattern.severity,
|
|
544
|
+
name: pattern.name,
|
|
545
|
+
file: relativePath,
|
|
546
|
+
line: pos.line,
|
|
547
|
+
column: pos.column,
|
|
548
|
+
match: redact(matchStr, 3)
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
for (const pattern of ROUTE_PATTERNS) {
|
|
356
554
|
pattern.pattern.lastIndex = 0;
|
|
357
555
|
let match;
|
|
358
556
|
while ((match = pattern.pattern.exec(content)) !== null) {
|
|
359
|
-
const matchStr = match[0];
|
|
360
|
-
if (isLikelyFalsePositive(matchStr, pattern.name)) {
|
|
361
|
-
continue;
|
|
362
|
-
}
|
|
363
557
|
const pos = getPosition(content, match.index);
|
|
364
558
|
issues.push({
|
|
365
|
-
type: "
|
|
559
|
+
type: "route",
|
|
366
560
|
severity: pattern.severity,
|
|
367
561
|
name: pattern.name,
|
|
368
562
|
file: relativePath,
|
|
369
563
|
line: pos.line,
|
|
370
564
|
column: pos.column,
|
|
371
|
-
match:
|
|
565
|
+
match: match[0],
|
|
566
|
+
description: pattern.description
|
|
372
567
|
});
|
|
373
568
|
}
|
|
374
569
|
}
|
|
375
|
-
for (const pattern of
|
|
570
|
+
for (const pattern of VULNERABILITY_PATTERNS) {
|
|
571
|
+
if (pattern.category === "debug" && isDocFile) {
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
376
574
|
pattern.pattern.lastIndex = 0;
|
|
377
575
|
let match;
|
|
378
576
|
while ((match = pattern.pattern.exec(content)) !== null) {
|
|
577
|
+
if (pattern.category === "debug" && pattern.name === "Console Log Statement") {
|
|
578
|
+
if (match[0].includes("error") || match[0].includes("warn")) {
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
379
582
|
const pos = getPosition(content, match.index);
|
|
380
583
|
issues.push({
|
|
381
|
-
type: "
|
|
584
|
+
type: "vulnerability",
|
|
585
|
+
category: pattern.category,
|
|
382
586
|
severity: pattern.severity,
|
|
383
587
|
name: pattern.name,
|
|
384
588
|
file: relativePath,
|
|
385
589
|
line: pos.line,
|
|
386
590
|
column: pos.column,
|
|
387
|
-
match: match[0],
|
|
591
|
+
match: match[0].length > 50 ? match[0].slice(0, 50) + "..." : match[0],
|
|
388
592
|
description: pattern.description
|
|
389
593
|
});
|
|
390
594
|
}
|
|
391
595
|
}
|
|
392
596
|
const fileName = path.basename(filePath);
|
|
393
597
|
if (fileName.startsWith(".env") && !fileName.includes(".example")) {
|
|
394
|
-
const gitignorePath = path.join(path.dirname(filePath), ".gitignore");
|
|
395
|
-
const gitignoreExists = fs.existsSync(gitignorePath);
|
|
396
598
|
issues.push({
|
|
397
599
|
type: "config",
|
|
398
600
|
severity: "high",
|
|
@@ -401,20 +603,14 @@ function scanFile(filePath, content) {
|
|
|
401
603
|
line: 1,
|
|
402
604
|
column: 1,
|
|
403
605
|
match: fileName,
|
|
404
|
-
description:
|
|
606
|
+
description: "Add .env* to .gitignore"
|
|
405
607
|
});
|
|
406
608
|
}
|
|
407
609
|
return issues;
|
|
408
610
|
}
|
|
409
|
-
function isLikelyFalsePositive(match, patternName) {
|
|
611
|
+
function isLikelyFalsePositive(match, patternName, filePath) {
|
|
410
612
|
if (patternName === "Email Address") {
|
|
411
|
-
const falseDomains = [
|
|
412
|
-
"example.com",
|
|
413
|
-
"example.org",
|
|
414
|
-
"test.com",
|
|
415
|
-
"localhost",
|
|
416
|
-
"placeholder.com"
|
|
417
|
-
];
|
|
613
|
+
const falseDomains = ["example.com", "example.org", "test.com", "localhost", "placeholder.com"];
|
|
418
614
|
if (falseDomains.some((d) => match.includes(d))) {
|
|
419
615
|
return true;
|
|
420
616
|
}
|
|
@@ -456,13 +652,12 @@ function calculateScore(issues) {
|
|
|
456
652
|
const critical = issues.filter((i) => i.severity === "critical").length;
|
|
457
653
|
const high = issues.filter((i) => i.severity === "high").length;
|
|
458
654
|
const medium = issues.filter((i) => i.severity === "medium").length;
|
|
459
|
-
const total = issues.length;
|
|
460
655
|
if (critical > 0) return "F";
|
|
461
656
|
if (high >= 3) return "F";
|
|
462
657
|
if (high >= 2) return "D";
|
|
463
658
|
if (high >= 1 || medium >= 5) return "C";
|
|
464
659
|
if (medium >= 2) return "B";
|
|
465
|
-
if (
|
|
660
|
+
if (issues.length === 0) return "A";
|
|
466
661
|
return "B";
|
|
467
662
|
}
|
|
468
663
|
function getTierDescription(score) {
|
|
@@ -476,7 +671,7 @@ function getTierDescription(score) {
|
|
|
476
671
|
case "D":
|
|
477
672
|
return "Poor. Significant security issues detected.";
|
|
478
673
|
case "F":
|
|
479
|
-
return "Critical!
|
|
674
|
+
return "Critical! Major security vulnerabilities found.";
|
|
480
675
|
default:
|
|
481
676
|
return "";
|
|
482
677
|
}
|
|
@@ -519,6 +714,7 @@ async function scan(targetPath) {
|
|
|
519
714
|
pii: issues.filter((i) => i.type === "pii").length,
|
|
520
715
|
routes: issues.filter((i) => i.type === "route").length,
|
|
521
716
|
config: issues.filter((i) => i.type === "config").length,
|
|
717
|
+
vulnerabilities: issues.filter((i) => i.type === "vulnerability").length,
|
|
522
718
|
critical: issues.filter((i) => i.severity === "critical").length,
|
|
523
719
|
high: issues.filter((i) => i.severity === "high").length,
|
|
524
720
|
medium: issues.filter((i) => i.severity === "medium").length,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/scanner/index.ts","../src/scanner/patterns.ts"],"sourcesContent":["/**\n * Cencori Vibe Check\n * Security scanner for AI apps\n */\n\nexport { scan, type ScanResult, type ScanIssue } from './scanner/index.js';\nexport {\n SECRET_PATTERNS,\n PII_PATTERNS,\n ROUTE_PATTERNS,\n type SecretPattern,\n type PIIPattern,\n type RoutePattern,\n} from './scanner/patterns.js';\n","import * as fs from 'fs';\nimport * as path from 'path';\nimport { glob } from 'glob';\nimport {\n SECRET_PATTERNS,\n PII_PATTERNS,\n ROUTE_PATTERNS,\n IGNORE_PATTERNS,\n SCANNABLE_EXTENSIONS,\n type SecretPattern,\n type PIIPattern,\n type RoutePattern,\n} from './patterns';\n\nexport interface ScanIssue {\n type: 'secret' | 'pii' | 'route' | 'config';\n severity: 'critical' | 'high' | 'medium' | 'low';\n name: string;\n provider?: string;\n file: string;\n line: number;\n column: number;\n match: string; // Redacted version\n description?: string;\n}\n\nexport interface ScanResult {\n score: 'A' | 'B' | 'C' | 'D' | 'F';\n tierDescription: string;\n issues: ScanIssue[];\n filesScanned: number;\n scanDuration: number;\n summary: {\n secrets: number;\n pii: number;\n routes: number;\n config: number;\n critical: number;\n high: number;\n medium: number;\n low: number;\n };\n}\n\n/**\n * Redact sensitive content for display\n */\nfunction redact(match: string, showChars: number = 4): string {\n if (match.length <= showChars * 2) {\n return '*'.repeat(match.length);\n }\n return match.slice(0, showChars) + '****' + match.slice(-showChars);\n}\n\n/**\n * Get line and column number for a match index\n */\nfunction getPosition(content: string, index: number): { line: number; column: number } {\n const lines = content.slice(0, index).split('\\n');\n return {\n line: lines.length,\n column: lines[lines.length - 1].length + 1,\n };\n}\n\n/**\n * Check if a file should be ignored\n */\nfunction shouldIgnore(filePath: string): boolean {\n const normalized = filePath.replace(/\\\\/g, '/');\n return IGNORE_PATTERNS.some(pattern => {\n if (pattern.startsWith('*')) {\n return normalized.endsWith(pattern.slice(1));\n }\n return normalized.includes(pattern);\n });\n}\n\n/**\n * Check if file has scannable extension\n */\nfunction isScannable(filePath: string): boolean {\n const ext = path.extname(filePath).toLowerCase();\n return SCANNABLE_EXTENSIONS.includes(ext);\n}\n\n/**\n * Scan a single file for issues\n */\nfunction scanFile(filePath: string, content: string): ScanIssue[] {\n const issues: ScanIssue[] = [];\n const relativePath = filePath;\n\n // Scan for secrets\n for (const pattern of SECRET_PATTERNS) {\n // Reset regex lastIndex\n pattern.pattern.lastIndex = 0;\n let match;\n while ((match = pattern.pattern.exec(content)) !== null) {\n const pos = getPosition(content, match.index);\n issues.push({\n type: 'secret',\n severity: pattern.severity,\n name: pattern.name,\n provider: pattern.provider,\n file: relativePath,\n line: pos.line,\n column: pos.column,\n match: redact(match[0]),\n });\n }\n }\n\n // Scan for PII\n for (const pattern of PII_PATTERNS) {\n pattern.pattern.lastIndex = 0;\n let match;\n while ((match = pattern.pattern.exec(content)) !== null) {\n // Skip common false positives\n const matchStr = match[0];\n if (isLikelyFalsePositive(matchStr, pattern.name)) {\n continue;\n }\n\n const pos = getPosition(content, match.index);\n issues.push({\n type: 'pii',\n severity: pattern.severity,\n name: pattern.name,\n file: relativePath,\n line: pos.line,\n column: pos.column,\n match: redact(matchStr, 3),\n });\n }\n }\n\n // Scan for exposed routes\n for (const pattern of ROUTE_PATTERNS) {\n pattern.pattern.lastIndex = 0;\n let match;\n while ((match = pattern.pattern.exec(content)) !== null) {\n const pos = getPosition(content, match.index);\n issues.push({\n type: 'route',\n severity: pattern.severity,\n name: pattern.name,\n file: relativePath,\n line: pos.line,\n column: pos.column,\n match: match[0],\n description: pattern.description,\n });\n }\n }\n\n // Check for .env files in wrong places\n const fileName = path.basename(filePath);\n if (fileName.startsWith('.env') && !fileName.includes('.example')) {\n // If we're scanning a .env file, it might be committed\n const gitignorePath = path.join(path.dirname(filePath), '.gitignore');\n const gitignoreExists = fs.existsSync(gitignorePath);\n\n issues.push({\n type: 'config',\n severity: 'high',\n name: 'Environment file in repository',\n file: relativePath,\n line: 1,\n column: 1,\n match: fileName,\n description: gitignoreExists\n ? 'Verify this file is in .gitignore'\n : 'Add .env* to .gitignore',\n });\n }\n\n return issues;\n}\n\n/**\n * Filter out likely false positives\n */\nfunction isLikelyFalsePositive(match: string, patternName: string): boolean {\n // Common email false positives\n if (patternName === 'Email Address') {\n const falseDomains = [\n 'example.com',\n 'example.org',\n 'test.com',\n 'localhost',\n 'placeholder.com',\n ];\n if (falseDomains.some(d => match.includes(d))) {\n return true;\n }\n\n // Common public email prefixes (not personal PII)\n const publicPrefixes = [\n 'support@',\n 'help@',\n 'info@',\n 'contact@',\n 'sales@',\n 'admin@',\n 'noreply@',\n 'no-reply@',\n 'hello@',\n 'team@',\n 'partners@',\n 'enterprise@',\n 'security@',\n 'privacy@',\n 'legal@',\n ];\n if (publicPrefixes.some(p => match.toLowerCase().startsWith(p))) {\n return true;\n }\n }\n\n // IP address false positives (version numbers, etc)\n if (patternName === 'IP Address') {\n const falseIPs = ['0.0.0.0', '127.0.0.1', '192.168.', '10.0.', '172.16.'];\n if (falseIPs.some(ip => match.startsWith(ip))) {\n return true;\n }\n }\n\n // Phone number false positives (example numbers in docs)\n if (patternName.includes('Phone Number')) {\n // Common example phone numbers\n if (match.includes('555') || match.includes('123-456') || match.includes('000-000')) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Calculate the fragility score based on issues\n */\nfunction calculateScore(issues: ScanIssue[]): 'A' | 'B' | 'C' | 'D' | 'F' {\n const critical = issues.filter(i => i.severity === 'critical').length;\n const high = issues.filter(i => i.severity === 'high').length;\n const medium = issues.filter(i => i.severity === 'medium').length;\n const total = issues.length;\n\n // Scoring algorithm\n if (critical > 0) return 'F';\n if (high >= 3) return 'F';\n if (high >= 2) return 'D';\n if (high >= 1 || medium >= 5) return 'C';\n if (medium >= 2) return 'B';\n if (total === 0) return 'A';\n return 'B';\n}\n\n/**\n * Get tier description\n */\nfunction getTierDescription(score: string): string {\n switch (score) {\n case 'A':\n return 'Excellent! No security issues detected.';\n case 'B':\n return 'Good, but minor improvements recommended.';\n case 'C':\n return 'Fair. Some security concerns need attention.';\n case 'D':\n return 'Poor. Significant security issues detected.';\n case 'F':\n return 'Critical! Your app is leaking secrets.';\n default:\n return '';\n }\n}\n\n/**\n * Main scan function\n */\nexport async function scan(targetPath: string): Promise<ScanResult> {\n const startTime = Date.now();\n const absolutePath = path.resolve(targetPath);\n\n // Get all files\n const files = await glob('**/*', {\n cwd: absolutePath,\n nodir: true,\n ignore: IGNORE_PATTERNS,\n absolute: true,\n });\n\n const issues: ScanIssue[] = [];\n let filesScanned = 0;\n\n for (const file of files) {\n if (!isScannable(file) || shouldIgnore(file)) {\n continue;\n }\n\n try {\n const content = fs.readFileSync(file, 'utf-8');\n const relativePath = path.relative(absolutePath, file);\n const fileIssues = scanFile(relativePath, content);\n issues.push(...fileIssues);\n filesScanned++;\n } catch {\n // Skip files that can't be read\n continue;\n }\n }\n\n const score = calculateScore(issues);\n const scanDuration = Date.now() - startTime;\n\n return {\n score,\n tierDescription: getTierDescription(score),\n issues,\n filesScanned,\n scanDuration,\n summary: {\n secrets: issues.filter(i => i.type === 'secret').length,\n pii: issues.filter(i => i.type === 'pii').length,\n routes: issues.filter(i => i.type === 'route').length,\n config: issues.filter(i => i.type === 'config').length,\n critical: issues.filter(i => i.severity === 'critical').length,\n high: issues.filter(i => i.severity === 'high').length,\n medium: issues.filter(i => i.severity === 'medium').length,\n low: issues.filter(i => i.severity === 'low').length,\n },\n };\n}\n","/**\n * Secret detection patterns for common API keys and tokens\n */\nexport interface SecretPattern {\n name: string;\n provider: string;\n pattern: RegExp;\n severity: 'critical' | 'high' | 'medium' | 'low';\n}\n\nexport const SECRET_PATTERNS: SecretPattern[] = [\n // OpenAI\n {\n name: 'OpenAI API Key',\n provider: 'OpenAI',\n pattern: /sk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20}/g,\n severity: 'critical',\n },\n {\n name: 'OpenAI Project Key',\n provider: 'OpenAI',\n pattern: /sk-proj-[a-zA-Z0-9_-]{80,}/g,\n severity: 'critical',\n },\n // Anthropic\n {\n name: 'Anthropic API Key',\n provider: 'Anthropic',\n pattern: /sk-ant-[a-zA-Z0-9-]{90,}/g,\n severity: 'critical',\n },\n // Google\n {\n name: 'Google API Key',\n provider: 'Google',\n pattern: /AIza[0-9A-Za-z_-]{35}/g,\n severity: 'critical',\n },\n // Supabase\n {\n name: 'Supabase Service Role Key',\n provider: 'Supabase',\n pattern: /eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.[a-zA-Z0-9_-]+\\.[a-zA-Z0-9_-]+/g,\n severity: 'critical',\n },\n {\n name: 'Supabase Anon Key (if hardcoded)',\n provider: 'Supabase',\n pattern: /SUPABASE_ANON_KEY\\s*[:=]\\s*[\"']eyJ[^\"']+[\"']/g,\n severity: 'medium',\n },\n // Stripe\n {\n name: 'Stripe Secret Key',\n provider: 'Stripe',\n pattern: /sk_live_[0-9a-zA-Z]{24,}/g,\n severity: 'critical',\n },\n {\n name: 'Stripe Test Key',\n provider: 'Stripe',\n pattern: /sk_test_[0-9a-zA-Z]{24,}/g,\n severity: 'medium',\n },\n // AWS\n {\n name: 'AWS Access Key ID',\n provider: 'AWS',\n pattern: /AKIA[0-9A-Z]{16}/g,\n severity: 'critical',\n },\n {\n name: 'AWS Secret Access Key',\n provider: 'AWS',\n pattern: /aws_secret_access_key\\s*[:=]\\s*[\"'][A-Za-z0-9/+=]{40}[\"']/gi,\n severity: 'critical',\n },\n // GitHub\n {\n name: 'GitHub Personal Access Token',\n provider: 'GitHub',\n pattern: /ghp_[a-zA-Z0-9]{36}/g,\n severity: 'critical',\n },\n {\n name: 'GitHub OAuth Token',\n provider: 'GitHub',\n pattern: /gho_[a-zA-Z0-9]{36}/g,\n severity: 'critical',\n },\n // Telegram\n {\n name: 'Telegram Bot Token',\n provider: 'Telegram',\n pattern: /[0-9]{9,10}:[a-zA-Z0-9_-]{35}/g,\n severity: 'high',\n },\n // Discord\n {\n name: 'Discord Bot Token',\n provider: 'Discord',\n pattern: /[MN][A-Za-z\\d]{23,}\\.[\\w-]{6}\\.[\\w-]{27}/g,\n severity: 'high',\n },\n // Slack\n {\n name: 'Slack Bot Token',\n provider: 'Slack',\n pattern: /xoxb-[0-9]{11}-[0-9]{11}-[a-zA-Z0-9]{24}/g,\n severity: 'high',\n },\n // SendGrid\n {\n name: 'SendGrid API Key',\n provider: 'SendGrid',\n pattern: /SG\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9_-]{43}/g,\n severity: 'high',\n },\n // Twilio\n {\n name: 'Twilio API Key',\n provider: 'Twilio',\n pattern: /SK[a-fA-F0-9]{32}/g,\n severity: 'high',\n },\n // Mailgun\n {\n name: 'Mailgun API Key',\n provider: 'Mailgun',\n pattern: /key-[a-zA-Z0-9]{32}/g,\n severity: 'high',\n },\n // Firebase\n {\n name: 'Firebase Database URL',\n provider: 'Firebase',\n pattern: /https:\\/\\/[a-z0-9-]+\\.firebaseio\\.com/g,\n severity: 'medium',\n },\n // Generic patterns\n {\n name: 'Private Key',\n provider: 'Generic',\n pattern: /-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,\n severity: 'critical',\n },\n {\n name: 'Generic API Key Assignment',\n provider: 'Generic',\n pattern: /(api_key|apikey|api_secret|secret_key)\\s*[:=]\\s*[\"'][a-zA-Z0-9_-]{20,}[\"']/gi,\n severity: 'high',\n },\n {\n name: 'Password Assignment',\n provider: 'Generic',\n pattern: /(password|passwd|pwd)\\s*[:=]\\s*[\"'][^\"']{8,}[\"']/gi,\n severity: 'high',\n },\n // Replicate\n {\n name: 'Replicate API Token',\n provider: 'Replicate',\n pattern: /r8_[a-zA-Z0-9]{38}/g,\n severity: 'critical',\n },\n // Hugging Face\n {\n name: 'Hugging Face Token',\n provider: 'Hugging Face',\n pattern: /hf_[a-zA-Z0-9]{34}/g,\n severity: 'critical',\n },\n // Cohere\n {\n name: 'Cohere API Key',\n provider: 'Cohere',\n pattern: /[a-zA-Z0-9]{40}/g, // Less specific, check context\n severity: 'medium',\n },\n];\n\n/**\n * PII detection patterns\n */\nexport interface PIIPattern {\n name: string;\n pattern: RegExp;\n severity: 'high' | 'medium' | 'low';\n}\n\nexport const PII_PATTERNS: PIIPattern[] = [\n {\n name: 'Email Address',\n pattern: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g,\n severity: 'medium',\n },\n {\n name: 'Phone Number (US)',\n pattern: /(\\+1[-.\\s]?)?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}/g,\n severity: 'medium',\n },\n {\n name: 'Phone Number (International)',\n pattern: /\\+[1-9]\\d{1,14}/g,\n severity: 'medium',\n },\n {\n name: 'Social Security Number',\n pattern: /\\b\\d{3}-\\d{2}-\\d{4}\\b/g,\n severity: 'high',\n },\n {\n name: 'Credit Card Number',\n pattern: /\\b(?:\\d{4}[-\\s]?){3}\\d{4}\\b/g,\n severity: 'high',\n },\n {\n name: 'IP Address',\n pattern: /\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g,\n severity: 'low',\n },\n];\n\n/**\n * Exposed route patterns for common frameworks\n */\nexport interface RoutePattern {\n name: string;\n framework: string;\n pattern: RegExp;\n severity: 'high' | 'medium' | 'low';\n description: string;\n}\n\nexport const ROUTE_PATTERNS: RoutePattern[] = [\n // Next.js API routes without auth\n {\n name: 'Next.js API Route (check for auth)',\n framework: 'Next.js',\n pattern: /export\\s+(async\\s+)?function\\s+(GET|POST|PUT|DELETE|PATCH)\\s*\\(/g,\n severity: 'medium',\n description: 'API route handler - verify authentication is implemented',\n },\n // Express routes\n {\n name: 'Express Route without Auth Middleware',\n framework: 'Express',\n pattern: /app\\.(get|post|put|delete|patch)\\s*\\(\\s*[\"'`][^\"'`]+[\"'`]\\s*,\\s*(?!.*auth)/gi,\n severity: 'medium',\n description: 'Express route - check if auth middleware is applied',\n },\n];\n\n/**\n * Files/patterns to ignore\n */\nexport const IGNORE_PATTERNS = [\n 'node_modules',\n '.git',\n 'dist',\n 'build',\n '.next',\n '.venv',\n '__pycache__',\n '*.min.js',\n '*.min.css',\n '*.map',\n 'package-lock.json',\n 'yarn.lock',\n 'pnpm-lock.yaml',\n];\n\n/**\n * File extensions to scan\n */\nexport const SCANNABLE_EXTENSIONS = [\n '.js',\n '.jsx',\n '.ts',\n '.tsx',\n '.mjs',\n '.cjs',\n '.py',\n '.rb',\n '.go',\n '.java',\n '.php',\n '.env',\n '.json',\n '.yaml',\n '.yml',\n '.toml',\n '.xml',\n '.md',\n '.txt',\n '.sql',\n '.sh',\n '.bash',\n '.zsh',\n];\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/scanner/index.ts","../src/scanner/patterns.ts"],"sourcesContent":["/**\n * Cencori Vibe Check\n * Security scanner for AI apps\n */\n\nexport { scan, type ScanResult, type ScanIssue } from './scanner/index.js';\nexport {\n SECRET_PATTERNS,\n PII_PATTERNS,\n ROUTE_PATTERNS,\n type SecretPattern,\n type PIIPattern,\n type RoutePattern,\n} from './scanner/patterns.js';\n","import * as fs from 'fs';\nimport * as path from 'path';\nimport { glob } from 'glob';\nimport {\n SECRET_PATTERNS,\n PII_PATTERNS,\n ROUTE_PATTERNS,\n VULNERABILITY_PATTERNS,\n IGNORE_PATTERNS,\n SCANNABLE_EXTENSIONS,\n} from './patterns';\n\nexport type IssueType = 'secret' | 'pii' | 'route' | 'config' | 'vulnerability';\nexport type IssueSeverity = 'critical' | 'high' | 'medium' | 'low';\n\nexport interface ScanIssue {\n type: IssueType;\n category?: string;\n severity: IssueSeverity;\n name: string;\n provider?: string;\n file: string;\n line: number;\n column: number;\n match: string;\n description?: string;\n}\n\nexport interface ScanResult {\n score: 'A' | 'B' | 'C' | 'D' | 'F';\n tierDescription: string;\n issues: ScanIssue[];\n filesScanned: number;\n scanDuration: number;\n summary: {\n secrets: number;\n pii: number;\n routes: number;\n config: number;\n vulnerabilities: number;\n critical: number;\n high: number;\n medium: number;\n low: number;\n };\n}\n\n/**\n * Redact sensitive content for display\n */\nfunction redact(match: string, showChars: number = 4): string {\n if (match.length <= showChars * 2) {\n return '*'.repeat(match.length);\n }\n return match.slice(0, showChars) + '****' + match.slice(-showChars);\n}\n\n/**\n * Get line and column number for a match index\n */\nfunction getPosition(content: string, index: number): { line: number; column: number } {\n const lines = content.slice(0, index).split('\\n');\n return {\n line: lines.length,\n column: lines[lines.length - 1].length + 1,\n };\n}\n\n/**\n * Check if a file should be ignored\n */\nfunction shouldIgnore(filePath: string): boolean {\n const normalized = filePath.replace(/\\\\/g, '/');\n return IGNORE_PATTERNS.some(pattern => {\n if (pattern.startsWith('*')) {\n return normalized.endsWith(pattern.slice(1));\n }\n return normalized.includes(pattern);\n });\n}\n\n/**\n * Check if file has scannable extension\n */\nfunction isScannable(filePath: string): boolean {\n const ext = path.extname(filePath).toLowerCase();\n return SCANNABLE_EXTENSIONS.includes(ext);\n}\n\n/**\n * Check if file is a documentation or test file\n */\nfunction isDocOrTestFile(filePath: string): boolean {\n const lower = filePath.toLowerCase();\n return (\n lower.includes('.test.') ||\n lower.includes('.spec.') ||\n lower.includes('__tests__') ||\n lower.includes('/test/') ||\n lower.includes('/tests/') ||\n lower.endsWith('.md') ||\n lower.includes('/docs/')\n );\n}\n\n/**\n * Scan a single file for issues\n */\nfunction scanFile(filePath: string, content: string): ScanIssue[] {\n const issues: ScanIssue[] = [];\n const relativePath = filePath;\n const isDocFile = isDocOrTestFile(filePath);\n\n // Scan for secrets\n for (const pattern of SECRET_PATTERNS) {\n pattern.pattern.lastIndex = 0;\n let match;\n while ((match = pattern.pattern.exec(content)) !== null) {\n const pos = getPosition(content, match.index);\n issues.push({\n type: 'secret',\n severity: pattern.severity,\n name: pattern.name,\n provider: pattern.provider,\n file: relativePath,\n line: pos.line,\n column: pos.column,\n match: redact(match[0]),\n });\n }\n }\n\n // Scan for PII (skip in doc files)\n if (!isDocFile) {\n for (const pattern of PII_PATTERNS) {\n pattern.pattern.lastIndex = 0;\n let match;\n while ((match = pattern.pattern.exec(content)) !== null) {\n const matchStr = match[0];\n if (isLikelyFalsePositive(matchStr, pattern.name, filePath)) {\n continue;\n }\n\n const pos = getPosition(content, match.index);\n issues.push({\n type: 'pii',\n severity: pattern.severity,\n name: pattern.name,\n file: relativePath,\n line: pos.line,\n column: pos.column,\n match: redact(matchStr, 3),\n });\n }\n }\n }\n\n // Scan for exposed routes\n for (const pattern of ROUTE_PATTERNS) {\n pattern.pattern.lastIndex = 0;\n let match;\n while ((match = pattern.pattern.exec(content)) !== null) {\n const pos = getPosition(content, match.index);\n issues.push({\n type: 'route',\n severity: pattern.severity,\n name: pattern.name,\n file: relativePath,\n line: pos.line,\n column: pos.column,\n match: match[0],\n description: pattern.description,\n });\n }\n }\n\n // Scan for vulnerabilities (skip debug checks in test files)\n for (const pattern of VULNERABILITY_PATTERNS) {\n // Skip debug pattern checks in test/doc files\n if (pattern.category === 'debug' && isDocFile) {\n continue;\n }\n\n pattern.pattern.lastIndex = 0;\n let match;\n while ((match = pattern.pattern.exec(content)) !== null) {\n // Skip console.log false positives\n if (pattern.category === 'debug' && pattern.name === 'Console Log Statement') {\n // Allow console.error and console.warn\n if (match[0].includes('error') || match[0].includes('warn')) {\n continue;\n }\n }\n\n const pos = getPosition(content, match.index);\n issues.push({\n type: 'vulnerability',\n category: pattern.category,\n severity: pattern.severity,\n name: pattern.name,\n file: relativePath,\n line: pos.line,\n column: pos.column,\n match: match[0].length > 50 ? match[0].slice(0, 50) + '...' : match[0],\n description: pattern.description,\n });\n }\n }\n\n // Check for .env files\n const fileName = path.basename(filePath);\n if (fileName.startsWith('.env') && !fileName.includes('.example')) {\n issues.push({\n type: 'config',\n severity: 'high',\n name: 'Environment file in repository',\n file: relativePath,\n line: 1,\n column: 1,\n match: fileName,\n description: 'Add .env* to .gitignore',\n });\n }\n\n return issues;\n}\n\n/**\n * Filter out likely false positives\n */\nfunction isLikelyFalsePositive(match: string, patternName: string, filePath: string): boolean {\n // Email false positives\n if (patternName === 'Email Address') {\n const falseDomains = ['example.com', 'example.org', 'test.com', 'localhost', 'placeholder.com'];\n if (falseDomains.some(d => match.includes(d))) {\n return true;\n }\n\n const publicPrefixes = [\n 'support@', 'help@', 'info@', 'contact@', 'sales@', 'admin@',\n 'noreply@', 'no-reply@', 'hello@', 'team@', 'partners@',\n 'enterprise@', 'security@', 'privacy@', 'legal@',\n ];\n if (publicPrefixes.some(p => match.toLowerCase().startsWith(p))) {\n return true;\n }\n }\n\n // IP address false positives\n if (patternName === 'IP Address') {\n const falseIPs = ['0.0.0.0', '127.0.0.1', '192.168.', '10.0.', '172.16.'];\n if (falseIPs.some(ip => match.startsWith(ip))) {\n return true;\n }\n }\n\n // Phone number false positives\n if (patternName.includes('Phone Number')) {\n if (match.includes('555') || match.includes('123-456') || match.includes('000-000')) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Calculate the security score\n */\nfunction calculateScore(issues: ScanIssue[]): 'A' | 'B' | 'C' | 'D' | 'F' {\n const critical = issues.filter(i => i.severity === 'critical').length;\n const high = issues.filter(i => i.severity === 'high').length;\n const medium = issues.filter(i => i.severity === 'medium').length;\n\n if (critical > 0) return 'F';\n if (high >= 3) return 'F';\n if (high >= 2) return 'D';\n if (high >= 1 || medium >= 5) return 'C';\n if (medium >= 2) return 'B';\n if (issues.length === 0) return 'A';\n return 'B';\n}\n\n/**\n * Get tier description\n */\nfunction getTierDescription(score: string): string {\n switch (score) {\n case 'A': return 'Excellent! No security issues detected.';\n case 'B': return 'Good, but minor improvements recommended.';\n case 'C': return 'Fair. Some security concerns need attention.';\n case 'D': return 'Poor. Significant security issues detected.';\n case 'F': return 'Critical! Major security vulnerabilities found.';\n default: return '';\n }\n}\n\n/**\n * Main scan function\n */\nexport async function scan(targetPath: string): Promise<ScanResult> {\n const startTime = Date.now();\n const absolutePath = path.resolve(targetPath);\n\n const files = await glob('**/*', {\n cwd: absolutePath,\n nodir: true,\n ignore: IGNORE_PATTERNS,\n absolute: true,\n });\n\n const issues: ScanIssue[] = [];\n let filesScanned = 0;\n\n for (const file of files) {\n if (!isScannable(file) || shouldIgnore(file)) {\n continue;\n }\n\n try {\n const content = fs.readFileSync(file, 'utf-8');\n const relativePath = path.relative(absolutePath, file);\n const fileIssues = scanFile(relativePath, content);\n issues.push(...fileIssues);\n filesScanned++;\n } catch {\n continue;\n }\n }\n\n const score = calculateScore(issues);\n const scanDuration = Date.now() - startTime;\n\n return {\n score,\n tierDescription: getTierDescription(score),\n issues,\n filesScanned,\n scanDuration,\n summary: {\n secrets: issues.filter(i => i.type === 'secret').length,\n pii: issues.filter(i => i.type === 'pii').length,\n routes: issues.filter(i => i.type === 'route').length,\n config: issues.filter(i => i.type === 'config').length,\n vulnerabilities: issues.filter(i => i.type === 'vulnerability').length,\n critical: issues.filter(i => i.severity === 'critical').length,\n high: issues.filter(i => i.severity === 'high').length,\n medium: issues.filter(i => i.severity === 'medium').length,\n low: issues.filter(i => i.severity === 'low').length,\n },\n };\n}\n","/**\n * Secret detection patterns for common API keys and tokens\n */\nexport interface SecretPattern {\n name: string;\n provider: string;\n pattern: RegExp;\n severity: 'critical' | 'high' | 'medium' | 'low';\n}\n\nexport const SECRET_PATTERNS: SecretPattern[] = [\n // OpenAI\n {\n name: 'OpenAI API Key',\n provider: 'OpenAI',\n pattern: /sk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20}/g,\n severity: 'critical',\n },\n {\n name: 'OpenAI Project Key',\n provider: 'OpenAI',\n pattern: /sk-proj-[a-zA-Z0-9_-]{80,}/g,\n severity: 'critical',\n },\n // Anthropic\n {\n name: 'Anthropic API Key',\n provider: 'Anthropic',\n pattern: /sk-ant-[a-zA-Z0-9-]{90,}/g,\n severity: 'critical',\n },\n // Google\n {\n name: 'Google API Key',\n provider: 'Google',\n pattern: /AIza[0-9A-Za-z_-]{35}/g,\n severity: 'critical',\n },\n // Supabase\n {\n name: 'Supabase Service Role Key',\n provider: 'Supabase',\n pattern: /eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.[a-zA-Z0-9_-]+\\.[a-zA-Z0-9_-]+/g,\n severity: 'critical',\n },\n {\n name: 'Supabase Anon Key (if hardcoded)',\n provider: 'Supabase',\n pattern: /SUPABASE_ANON_KEY\\s*[:=]\\s*[\"']eyJ[^\"']+[\"']/g,\n severity: 'medium',\n },\n // Stripe\n {\n name: 'Stripe Secret Key',\n provider: 'Stripe',\n pattern: /sk_live_[0-9a-zA-Z]{24,}/g,\n severity: 'critical',\n },\n {\n name: 'Stripe Test Key',\n provider: 'Stripe',\n pattern: /sk_test_[0-9a-zA-Z]{24,}/g,\n severity: 'medium',\n },\n {\n name: 'Stripe Webhook Secret',\n provider: 'Stripe',\n pattern: /whsec_[a-zA-Z0-9]{24,}/g,\n severity: 'critical',\n },\n // AWS\n {\n name: 'AWS Access Key ID',\n provider: 'AWS',\n pattern: /AKIA[0-9A-Z]{16}/g,\n severity: 'critical',\n },\n {\n name: 'AWS Secret Access Key',\n provider: 'AWS',\n pattern: /aws_secret_access_key\\s*[:=]\\s*[\"'][A-Za-z0-9/+=]{40}[\"']/gi,\n severity: 'critical',\n },\n // GitHub\n {\n name: 'GitHub Personal Access Token',\n provider: 'GitHub',\n pattern: /ghp_[a-zA-Z0-9]{36}/g,\n severity: 'critical',\n },\n {\n name: 'GitHub OAuth Token',\n provider: 'GitHub',\n pattern: /gho_[a-zA-Z0-9]{36}/g,\n severity: 'critical',\n },\n {\n name: 'GitHub Webhook Secret',\n provider: 'GitHub',\n pattern: /sha256=[a-fA-F0-9]{64}/g,\n severity: 'high',\n },\n // Telegram\n {\n name: 'Telegram Bot Token',\n provider: 'Telegram',\n pattern: /[0-9]{9,10}:[a-zA-Z0-9_-]{35}/g,\n severity: 'high',\n },\n // Discord\n {\n name: 'Discord Bot Token',\n provider: 'Discord',\n pattern: /[MN][A-Za-z\\d]{23,}\\.[\\w-]{6}\\.[\\w-]{27}/g,\n severity: 'high',\n },\n // Slack\n {\n name: 'Slack Bot Token',\n provider: 'Slack',\n pattern: /xoxb-[0-9]{11}-[0-9]{11}-[a-zA-Z0-9]{24}/g,\n severity: 'high',\n },\n // SendGrid\n {\n name: 'SendGrid API Key',\n provider: 'SendGrid',\n pattern: /SG\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9_-]{43}/g,\n severity: 'high',\n },\n // Twilio\n {\n name: 'Twilio API Key',\n provider: 'Twilio',\n pattern: /SK[a-fA-F0-9]{32}/g,\n severity: 'high',\n },\n // Mailgun\n {\n name: 'Mailgun API Key',\n provider: 'Mailgun',\n pattern: /key-[a-zA-Z0-9]{32}/g,\n severity: 'high',\n },\n // Firebase\n {\n name: 'Firebase Database URL',\n provider: 'Firebase',\n pattern: /https:\\/\\/[a-z0-9-]+\\.firebaseio\\.com/g,\n severity: 'medium',\n },\n // Generic patterns\n {\n name: 'Private Key',\n provider: 'Generic',\n pattern: /-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,\n severity: 'critical',\n },\n {\n name: 'Generic API Key Assignment',\n provider: 'Generic',\n pattern: /(api_key|apikey|api_secret|secret_key)\\s*[:=]\\s*[\"'][a-zA-Z0-9_-]{20,}[\"']/gi,\n severity: 'high',\n },\n {\n name: 'Password Assignment',\n provider: 'Generic',\n pattern: /(password|passwd|pwd)\\s*[:=]\\s*[\"'][^\"']{8,}[\"']/gi,\n severity: 'high',\n },\n // Replicate\n {\n name: 'Replicate API Token',\n provider: 'Replicate',\n pattern: /r8_[a-zA-Z0-9]{38}/g,\n severity: 'critical',\n },\n // Hugging Face\n {\n name: 'Hugging Face Token',\n provider: 'Hugging Face',\n pattern: /hf_[a-zA-Z0-9]{34}/g,\n severity: 'critical',\n },\n // JWT Secrets\n {\n name: 'JWT Secret Assignment',\n provider: 'Generic',\n pattern: /JWT_SECRET\\s*[:=]\\s*[\"'][^\"']{16,}[\"']/gi,\n severity: 'critical',\n },\n {\n name: 'Hardcoded JWT Sign',\n provider: 'Generic',\n pattern: /jwt\\.(sign|verify)\\s*\\([^,]+,\\s*[\"'][^\"']{10,}[\"']/gi,\n severity: 'critical',\n },\n // OAuth Secrets\n {\n name: 'OAuth Client Secret',\n provider: 'Generic',\n pattern: /client_secret\\s*[:=]\\s*[\"'][a-zA-Z0-9_-]{20,}[\"']/gi,\n severity: 'critical',\n },\n {\n name: 'Google Client Secret',\n provider: 'Google',\n pattern: /GOOGLE_CLIENT_SECRET\\s*[:=]\\s*[\"'][^\"']+[\"']/gi,\n severity: 'critical',\n },\n // Database Connection Strings\n {\n name: 'MongoDB Connection String',\n provider: 'MongoDB',\n pattern: /mongodb(\\+srv)?:\\/\\/[^@\\s]+@[^\\s\"']+/g,\n severity: 'critical',\n },\n {\n name: 'PostgreSQL Connection String',\n provider: 'PostgreSQL',\n pattern: /postgres(ql)?:\\/\\/[^\\s\"']+/g,\n severity: 'critical',\n },\n {\n name: 'MySQL Connection String',\n provider: 'MySQL',\n pattern: /mysql:\\/\\/[^\\s\"']+/g,\n severity: 'critical',\n },\n {\n name: 'Redis Connection String',\n provider: 'Redis',\n pattern: /redis:\\/\\/[^\\s\"']+/g,\n severity: 'high',\n },\n];\n\n/**\n * PII detection patterns\n */\nexport interface PIIPattern {\n name: string;\n pattern: RegExp;\n severity: 'high' | 'medium' | 'low';\n}\n\nexport const PII_PATTERNS: PIIPattern[] = [\n {\n name: 'Email Address',\n pattern: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g,\n severity: 'medium',\n },\n {\n name: 'Phone Number (US)',\n pattern: /(\\+1[-.\\s]?)?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}/g,\n severity: 'medium',\n },\n {\n name: 'Phone Number (International)',\n pattern: /\\+[1-9]\\d{1,14}/g,\n severity: 'medium',\n },\n {\n name: 'Social Security Number',\n pattern: /\\b\\d{3}-\\d{2}-\\d{4}\\b/g,\n severity: 'high',\n },\n {\n name: 'Credit Card Number',\n pattern: /\\b(?:\\d{4}[-\\s]?){3}\\d{4}\\b/g,\n severity: 'high',\n },\n {\n name: 'IP Address',\n pattern: /\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g,\n severity: 'low',\n },\n];\n\n/**\n * Exposed route patterns for common frameworks\n */\nexport interface RoutePattern {\n name: string;\n framework: string;\n pattern: RegExp;\n severity: 'high' | 'medium' | 'low';\n description: string;\n}\n\nexport const ROUTE_PATTERNS: RoutePattern[] = [\n // Next.js API routes\n {\n name: 'Next.js API Route (check for auth)',\n framework: 'Next.js',\n pattern: /export\\s+(async\\s+)?function\\s+(GET|POST|PUT|DELETE|PATCH)\\s*\\(/g,\n severity: 'medium',\n description: 'API route handler - verify authentication is implemented',\n },\n // Express routes\n {\n name: 'Express Route without Auth Middleware',\n framework: 'Express',\n pattern: /app\\.(get|post|put|delete|patch)\\s*\\(\\s*[\"'`][^\"'`]+[\"'`]\\s*,\\s*(?!.*auth)/gi,\n severity: 'medium',\n description: 'Express route - check if auth middleware is applied',\n },\n // Admin routes\n {\n name: 'Admin Route Exposed',\n framework: 'Generic',\n pattern: /[\"'`](\\/admin|\\/dashboard|\\/internal|\\/private)[^\"'`]*[\"'`]/gi,\n severity: 'high',\n description: 'Sensitive route - ensure proper authentication',\n },\n];\n\n/**\n * Security vulnerability patterns\n */\nexport interface VulnerabilityPattern {\n name: string;\n category: string;\n pattern: RegExp;\n severity: 'critical' | 'high' | 'medium' | 'low';\n description: string;\n}\n\nexport const VULNERABILITY_PATTERNS: VulnerabilityPattern[] = [\n // Hardcoded URLs\n {\n name: 'Localhost URL in Code',\n category: 'hardcoded-url',\n pattern: /https?:\\/\\/localhost[:\\d]*/gi,\n severity: 'medium',\n description: 'Development URL - should use environment variables',\n },\n {\n name: 'Staging/Dev URL in Code',\n category: 'hardcoded-url',\n pattern: /https?:\\/\\/(staging\\.|dev\\.|test\\.)[^\\s\"']+/gi,\n severity: 'medium',\n description: 'Non-production URL in code',\n },\n // Debug artifacts (skip console.log - too many false positives for CLI tools)\n {\n name: 'Debug Flag Enabled',\n category: 'debug',\n pattern: /DEBUG\\s*[:=]\\s*(true|1|[\"']true[\"'])/gi,\n severity: 'medium',\n description: 'Debug mode enabled - disable in production',\n },\n {\n name: 'Hardcoded Development Mode',\n category: 'debug',\n pattern: /NODE_ENV\\s*[:=]\\s*[\"']development[\"']/gi,\n severity: 'medium',\n description: 'Hardcoded development mode',\n },\n // CORS issues\n {\n name: 'CORS Wildcard Origin',\n category: 'cors',\n pattern: /Access-Control-Allow-Origin['\":\\s]+\\*/g,\n severity: 'high',\n description: 'Allows requests from any origin - security risk',\n },\n {\n name: 'Permissive CORS Config',\n category: 'cors',\n pattern: /cors\\s*\\(\\s*\\)/g,\n severity: 'medium',\n description: 'CORS with default (permissive) settings',\n },\n // SQL Injection\n {\n name: 'SQL String Concatenation',\n category: 'injection',\n pattern: /query\\s*\\(\\s*[`'\"].*\\$\\{.*\\}/g,\n severity: 'critical',\n description: 'Potential SQL injection - use parameterized queries',\n },\n {\n name: 'SQL String Addition',\n category: 'injection',\n pattern: /(SELECT|INSERT|UPDATE|DELETE).*[\"']\\s*\\+\\s*\\w+/gi,\n severity: 'critical',\n description: 'SQL built with string concatenation',\n },\n // XSS Vulnerabilities\n {\n name: 'React dangerouslySetInnerHTML',\n category: 'xss',\n pattern: /dangerouslySetInnerHTML\\s*=\\s*\\{\\s*\\{\\s*__html/g,\n severity: 'high',\n description: 'Renders raw HTML - ensure input is sanitized',\n },\n {\n name: 'Direct innerHTML Assignment',\n category: 'xss',\n pattern: /\\.innerHTML\\s*=/g,\n severity: 'high',\n description: 'Direct HTML injection - use textContent instead',\n },\n {\n name: 'Vue v-html Directive',\n category: 'xss',\n pattern: /v-html\\s*=\\s*[\"'][^\"']+[\"']/g,\n severity: 'high',\n description: 'Vue raw HTML binding - ensure input is sanitized',\n },\n {\n name: 'Document Write',\n category: 'xss',\n pattern: /document\\.write\\s*\\(/g,\n severity: 'high',\n description: 'Deprecated and potentially dangerous',\n },\n // Eval and code execution\n {\n name: 'Eval Usage',\n category: 'injection',\n pattern: /\\beval\\s*\\(/g,\n severity: 'critical',\n description: 'Code execution - major security risk',\n },\n {\n name: 'Function Constructor',\n category: 'injection',\n pattern: /new\\s+Function\\s*\\(/g,\n severity: 'high',\n description: 'Dynamic code execution risk',\n },\n];\n\n/**\n * Files/patterns to ignore\n */\nexport const IGNORE_PATTERNS = [\n 'node_modules',\n '.git',\n 'dist',\n 'build',\n '.next',\n '.venv',\n '__pycache__',\n '*.min.js',\n '*.min.css',\n '*.map',\n 'package-lock.json',\n 'yarn.lock',\n 'pnpm-lock.yaml',\n];\n\n/**\n * File extensions to scan\n */\nexport const SCANNABLE_EXTENSIONS = [\n '.js',\n '.jsx',\n '.ts',\n '.tsx',\n '.mjs',\n '.cjs',\n '.py',\n '.rb',\n '.go',\n '.java',\n '.php',\n '.env',\n '.json',\n '.yaml',\n '.yml',\n '.toml',\n '.xml',\n '.md',\n '.txt',\n '.sql',\n '.sh',\n '.bash',\n '.zsh',\n '.vue',\n '.svelte',\n];\n"],"mappings":"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","names":[]}
|