@cencori/scan 0.1.0 → 0.2.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.mjs CHANGED
@@ -65,6 +65,12 @@ var SECRET_PATTERNS = [
65
65
  pattern: /sk_test_[0-9a-zA-Z]{24,}/g,
66
66
  severity: "medium"
67
67
  },
68
+ {
69
+ name: "Stripe Webhook Secret",
70
+ provider: "Stripe",
71
+ pattern: /whsec_[a-zA-Z0-9]{24,}/g,
72
+ severity: "critical"
73
+ },
68
74
  // AWS
69
75
  {
70
76
  name: "AWS Access Key ID",
@@ -91,6 +97,12 @@ var SECRET_PATTERNS = [
91
97
  pattern: /gho_[a-zA-Z0-9]{36}/g,
92
98
  severity: "critical"
93
99
  },
100
+ {
101
+ name: "GitHub Webhook Secret",
102
+ provider: "GitHub",
103
+ pattern: /sha256=[a-fA-F0-9]{64}/g,
104
+ severity: "high"
105
+ },
94
106
  // Telegram
95
107
  {
96
108
  name: "Telegram Bot Token",
@@ -173,13 +185,56 @@ var SECRET_PATTERNS = [
173
185
  pattern: /hf_[a-zA-Z0-9]{34}/g,
174
186
  severity: "critical"
175
187
  },
176
- // Cohere
188
+ // JWT Secrets
177
189
  {
178
- name: "Cohere API Key",
179
- provider: "Cohere",
180
- pattern: /[a-zA-Z0-9]{40}/g,
181
- // Less specific, check context
182
- severity: "medium"
190
+ name: "JWT Secret Assignment",
191
+ provider: "Generic",
192
+ pattern: /JWT_SECRET\s*[:=]\s*["'][^"']{16,}["']/gi,
193
+ severity: "critical"
194
+ },
195
+ {
196
+ name: "Hardcoded JWT Sign",
197
+ provider: "Generic",
198
+ pattern: /jwt\.(sign|verify)\s*\([^,]+,\s*["'][^"']{10,}["']/gi,
199
+ severity: "critical"
200
+ },
201
+ // OAuth Secrets
202
+ {
203
+ name: "OAuth Client Secret",
204
+ provider: "Generic",
205
+ pattern: /client_secret\s*[:=]\s*["'][a-zA-Z0-9_-]{20,}["']/gi,
206
+ severity: "critical"
207
+ },
208
+ {
209
+ name: "Google Client Secret",
210
+ provider: "Google",
211
+ pattern: /GOOGLE_CLIENT_SECRET\s*[:=]\s*["'][^"']+["']/gi,
212
+ severity: "critical"
213
+ },
214
+ // Database Connection Strings
215
+ {
216
+ name: "MongoDB Connection String",
217
+ provider: "MongoDB",
218
+ pattern: /mongodb(\+srv)?:\/\/[^@\s]+@[^\s"']+/g,
219
+ severity: "critical"
220
+ },
221
+ {
222
+ name: "PostgreSQL Connection String",
223
+ provider: "PostgreSQL",
224
+ pattern: /postgres(ql)?:\/\/[^\s"']+/g,
225
+ severity: "critical"
226
+ },
227
+ {
228
+ name: "MySQL Connection String",
229
+ provider: "MySQL",
230
+ pattern: /mysql:\/\/[^\s"']+/g,
231
+ severity: "critical"
232
+ },
233
+ {
234
+ name: "Redis Connection String",
235
+ provider: "Redis",
236
+ pattern: /redis:\/\/[^\s"']+/g,
237
+ severity: "high"
183
238
  }
184
239
  ];
185
240
  var PII_PATTERNS = [
@@ -215,7 +270,7 @@ var PII_PATTERNS = [
215
270
  }
216
271
  ];
217
272
  var ROUTE_PATTERNS = [
218
- // Next.js API routes without auth
273
+ // Next.js API routes
219
274
  {
220
275
  name: "Next.js API Route (check for auth)",
221
276
  framework: "Next.js",
@@ -230,6 +285,120 @@ var ROUTE_PATTERNS = [
230
285
  pattern: /app\.(get|post|put|delete|patch)\s*\(\s*["'`][^"'`]+["'`]\s*,\s*(?!.*auth)/gi,
231
286
  severity: "medium",
232
287
  description: "Express route - check if auth middleware is applied"
288
+ },
289
+ // Admin routes
290
+ {
291
+ name: "Admin Route Exposed",
292
+ framework: "Generic",
293
+ pattern: /["'`](\/admin|\/dashboard|\/internal|\/private)[^"'`]*["'`]/gi,
294
+ severity: "high",
295
+ description: "Sensitive route - ensure proper authentication"
296
+ }
297
+ ];
298
+ var VULNERABILITY_PATTERNS = [
299
+ // Hardcoded URLs
300
+ {
301
+ name: "Localhost URL in Code",
302
+ category: "hardcoded-url",
303
+ pattern: /https?:\/\/localhost[:\d]*/gi,
304
+ severity: "medium",
305
+ description: "Development URL - should use environment variables"
306
+ },
307
+ {
308
+ name: "Staging/Dev URL in Code",
309
+ category: "hardcoded-url",
310
+ pattern: /https?:\/\/(staging\.|dev\.|test\.)[^\s"']+/gi,
311
+ severity: "medium",
312
+ description: "Non-production URL in code"
313
+ },
314
+ // Debug artifacts (skip console.log - too many false positives for CLI tools)
315
+ {
316
+ name: "Debug Flag Enabled",
317
+ category: "debug",
318
+ pattern: /DEBUG\s*[:=]\s*(true|1|["']true["'])/gi,
319
+ severity: "medium",
320
+ description: "Debug mode enabled - disable in production"
321
+ },
322
+ {
323
+ name: "Hardcoded Development Mode",
324
+ category: "debug",
325
+ pattern: /NODE_ENV\s*[:=]\s*["']development["']/gi,
326
+ severity: "medium",
327
+ description: "Hardcoded development mode"
328
+ },
329
+ // CORS issues
330
+ {
331
+ name: "CORS Wildcard Origin",
332
+ category: "cors",
333
+ pattern: /Access-Control-Allow-Origin['":\s]+\*/g,
334
+ severity: "high",
335
+ description: "Allows requests from any origin - security risk"
336
+ },
337
+ {
338
+ name: "Permissive CORS Config",
339
+ category: "cors",
340
+ pattern: /cors\s*\(\s*\)/g,
341
+ severity: "medium",
342
+ description: "CORS with default (permissive) settings"
343
+ },
344
+ // SQL Injection
345
+ {
346
+ name: "SQL String Concatenation",
347
+ category: "injection",
348
+ pattern: /query\s*\(\s*[`'"].*\$\{.*\}/g,
349
+ severity: "critical",
350
+ description: "Potential SQL injection - use parameterized queries"
351
+ },
352
+ {
353
+ name: "SQL String Addition",
354
+ category: "injection",
355
+ pattern: /(SELECT|INSERT|UPDATE|DELETE).*["']\s*\+\s*\w+/gi,
356
+ severity: "critical",
357
+ description: "SQL built with string concatenation"
358
+ },
359
+ // XSS Vulnerabilities
360
+ {
361
+ name: "React dangerouslySetInnerHTML",
362
+ category: "xss",
363
+ pattern: /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html/g,
364
+ severity: "high",
365
+ description: "Renders raw HTML - ensure input is sanitized"
366
+ },
367
+ {
368
+ name: "Direct innerHTML Assignment",
369
+ category: "xss",
370
+ pattern: /\.innerHTML\s*=/g,
371
+ severity: "high",
372
+ description: "Direct HTML injection - use textContent instead"
373
+ },
374
+ {
375
+ name: "Vue v-html Directive",
376
+ category: "xss",
377
+ pattern: /v-html\s*=\s*["'][^"']+["']/g,
378
+ severity: "high",
379
+ description: "Vue raw HTML binding - ensure input is sanitized"
380
+ },
381
+ {
382
+ name: "Document Write",
383
+ category: "xss",
384
+ pattern: /document\.write\s*\(/g,
385
+ severity: "high",
386
+ description: "Deprecated and potentially dangerous"
387
+ },
388
+ // Eval and code execution
389
+ {
390
+ name: "Eval Usage",
391
+ category: "injection",
392
+ pattern: /\beval\s*\(/g,
393
+ severity: "critical",
394
+ description: "Code execution - major security risk"
395
+ },
396
+ {
397
+ name: "Function Constructor",
398
+ category: "injection",
399
+ pattern: /new\s+Function\s*\(/g,
400
+ severity: "high",
401
+ description: "Dynamic code execution risk"
233
402
  }
234
403
  ];
235
404
  var IGNORE_PATTERNS = [
@@ -270,7 +439,9 @@ var SCANNABLE_EXTENSIONS = [
270
439
  ".sql",
271
440
  ".sh",
272
441
  ".bash",
273
- ".zsh"
442
+ ".zsh",
443
+ ".vue",
444
+ ".svelte"
274
445
  ];
275
446
 
276
447
  // src/scanner/index.ts
@@ -300,9 +471,14 @@ function isScannable(filePath) {
300
471
  const ext = path.extname(filePath).toLowerCase();
301
472
  return SCANNABLE_EXTENSIONS.includes(ext);
302
473
  }
474
+ function isDocOrTestFile(filePath) {
475
+ const lower = filePath.toLowerCase();
476
+ return lower.includes(".test.") || lower.includes(".spec.") || lower.includes("__tests__") || lower.includes("/test/") || lower.includes("/tests/") || lower.endsWith(".md") || lower.includes("/docs/");
477
+ }
303
478
  function scanFile(filePath, content) {
304
479
  const issues = [];
305
480
  const relativePath = filePath;
481
+ const isDocFile = isDocOrTestFile(filePath);
306
482
  for (const pattern of SECRET_PATTERNS) {
307
483
  pattern.pattern.lastIndex = 0;
308
484
  let match;
@@ -320,47 +496,73 @@ function scanFile(filePath, content) {
320
496
  });
321
497
  }
322
498
  }
323
- for (const pattern of PII_PATTERNS) {
499
+ if (!isDocFile) {
500
+ for (const pattern of PII_PATTERNS) {
501
+ pattern.pattern.lastIndex = 0;
502
+ let match;
503
+ while ((match = pattern.pattern.exec(content)) !== null) {
504
+ const matchStr = match[0];
505
+ if (isLikelyFalsePositive(matchStr, pattern.name, filePath)) {
506
+ continue;
507
+ }
508
+ const pos = getPosition(content, match.index);
509
+ issues.push({
510
+ type: "pii",
511
+ severity: pattern.severity,
512
+ name: pattern.name,
513
+ file: relativePath,
514
+ line: pos.line,
515
+ column: pos.column,
516
+ match: redact(matchStr, 3)
517
+ });
518
+ }
519
+ }
520
+ }
521
+ for (const pattern of ROUTE_PATTERNS) {
324
522
  pattern.pattern.lastIndex = 0;
325
523
  let match;
326
524
  while ((match = pattern.pattern.exec(content)) !== null) {
327
- const matchStr = match[0];
328
- if (isLikelyFalsePositive(matchStr, pattern.name)) {
329
- continue;
330
- }
331
525
  const pos = getPosition(content, match.index);
332
526
  issues.push({
333
- type: "pii",
527
+ type: "route",
334
528
  severity: pattern.severity,
335
529
  name: pattern.name,
336
530
  file: relativePath,
337
531
  line: pos.line,
338
532
  column: pos.column,
339
- match: redact(matchStr, 3)
533
+ match: match[0],
534
+ description: pattern.description
340
535
  });
341
536
  }
342
537
  }
343
- for (const pattern of ROUTE_PATTERNS) {
538
+ for (const pattern of VULNERABILITY_PATTERNS) {
539
+ if (pattern.category === "debug" && isDocFile) {
540
+ continue;
541
+ }
344
542
  pattern.pattern.lastIndex = 0;
345
543
  let match;
346
544
  while ((match = pattern.pattern.exec(content)) !== null) {
545
+ if (pattern.category === "debug" && pattern.name === "Console Log Statement") {
546
+ if (match[0].includes("error") || match[0].includes("warn")) {
547
+ continue;
548
+ }
549
+ }
347
550
  const pos = getPosition(content, match.index);
348
551
  issues.push({
349
- type: "route",
552
+ type: "vulnerability",
553
+ category: pattern.category,
350
554
  severity: pattern.severity,
351
555
  name: pattern.name,
352
556
  file: relativePath,
353
557
  line: pos.line,
354
558
  column: pos.column,
355
- match: match[0],
559
+ match: match[0].length > 50 ? match[0].slice(0, 50) + "..." : match[0],
356
560
  description: pattern.description
357
561
  });
358
562
  }
359
563
  }
360
564
  const fileName = path.basename(filePath);
361
565
  if (fileName.startsWith(".env") && !fileName.includes(".example")) {
362
- const gitignorePath = path.join(path.dirname(filePath), ".gitignore");
363
- const gitignoreExists = fs.existsSync(gitignorePath);
364
566
  issues.push({
365
567
  type: "config",
366
568
  severity: "high",
@@ -369,20 +571,14 @@ function scanFile(filePath, content) {
369
571
  line: 1,
370
572
  column: 1,
371
573
  match: fileName,
372
- description: gitignoreExists ? "Verify this file is in .gitignore" : "Add .env* to .gitignore"
574
+ description: "Add .env* to .gitignore"
373
575
  });
374
576
  }
375
577
  return issues;
376
578
  }
377
- function isLikelyFalsePositive(match, patternName) {
579
+ function isLikelyFalsePositive(match, patternName, filePath) {
378
580
  if (patternName === "Email Address") {
379
- const falseDomains = [
380
- "example.com",
381
- "example.org",
382
- "test.com",
383
- "localhost",
384
- "placeholder.com"
385
- ];
581
+ const falseDomains = ["example.com", "example.org", "test.com", "localhost", "placeholder.com"];
386
582
  if (falseDomains.some((d) => match.includes(d))) {
387
583
  return true;
388
584
  }
@@ -424,13 +620,12 @@ function calculateScore(issues) {
424
620
  const critical = issues.filter((i) => i.severity === "critical").length;
425
621
  const high = issues.filter((i) => i.severity === "high").length;
426
622
  const medium = issues.filter((i) => i.severity === "medium").length;
427
- const total = issues.length;
428
623
  if (critical > 0) return "F";
429
624
  if (high >= 3) return "F";
430
625
  if (high >= 2) return "D";
431
626
  if (high >= 1 || medium >= 5) return "C";
432
627
  if (medium >= 2) return "B";
433
- if (total === 0) return "A";
628
+ if (issues.length === 0) return "A";
434
629
  return "B";
435
630
  }
436
631
  function getTierDescription(score) {
@@ -444,7 +639,7 @@ function getTierDescription(score) {
444
639
  case "D":
445
640
  return "Poor. Significant security issues detected.";
446
641
  case "F":
447
- return "Critical! Your app is leaking secrets.";
642
+ return "Critical! Major security vulnerabilities found.";
448
643
  default:
449
644
  return "";
450
645
  }
@@ -487,6 +682,7 @@ async function scan(targetPath) {
487
682
  pii: issues.filter((i) => i.type === "pii").length,
488
683
  routes: issues.filter((i) => i.type === "route").length,
489
684
  config: issues.filter((i) => i.type === "config").length,
685
+ vulnerabilities: issues.filter((i) => i.type === "vulnerability").length,
490
686
  critical: issues.filter((i) => i.severity === "critical").length,
491
687
  high: issues.filter((i) => i.severity === "high").length,
492
688
  medium: issues.filter((i) => i.severity === "medium").length,
@@ -496,7 +692,7 @@ async function scan(targetPath) {
496
692
  }
497
693
 
498
694
  // src/cli.ts
499
- var VERSION = "0.1.0";
695
+ var VERSION = "0.2.0";
500
696
  var scoreStyles = {
501
697
  A: { color: chalk.green },
502
698
  B: { color: chalk.blue },
@@ -514,7 +710,8 @@ var typeLabels = {
514
710
  secret: "SECRETS",
515
711
  pii: "PII",
516
712
  route: "ROUTES",
517
- config: "CONFIG"
713
+ config: "CONFIG",
714
+ vulnerability: "VULNERABILITIES"
518
715
  };
519
716
  function printBanner() {
520
717
  console.log();
@@ -594,6 +791,11 @@ function printFixes(issues) {
594
791
  const hasSecrets = issues.some((i) => i.type === "secret");
595
792
  const hasPII = issues.some((i) => i.type === "pii");
596
793
  const hasConfig = issues.some((i) => i.type === "config");
794
+ const hasVulnerabilities = issues.some((i) => i.type === "vulnerability");
795
+ const hasXSS = issues.some((i) => i.category === "xss");
796
+ const hasInjection = issues.some((i) => i.category === "injection");
797
+ const hasCORS = issues.some((i) => i.category === "cors");
798
+ const hasDebug = issues.some((i) => i.category === "debug");
597
799
  if (hasSecrets) {
598
800
  console.log(chalk.gray(" - Use environment variables for secrets"));
599
801
  console.log(chalk.gray(" - Never commit API keys to version control"));
@@ -604,6 +806,21 @@ function printFixes(issues) {
604
806
  if (hasPII) {
605
807
  console.log(chalk.gray(" - Remove personal data from source code"));
606
808
  }
809
+ if (hasXSS) {
810
+ console.log(chalk.gray(" - Sanitize user input before rendering HTML"));
811
+ console.log(chalk.gray(" - Use textContent instead of innerHTML"));
812
+ }
813
+ if (hasInjection) {
814
+ console.log(chalk.gray(" - Use parameterized queries for SQL"));
815
+ console.log(chalk.gray(" - Avoid using eval and dynamic code execution"));
816
+ }
817
+ if (hasCORS) {
818
+ console.log(chalk.gray(" - Configure CORS with specific allowed origins"));
819
+ }
820
+ if (hasDebug) {
821
+ console.log(chalk.gray(" - Remove console.log statements before production"));
822
+ console.log(chalk.gray(" - Use environment variables for debug flags"));
823
+ }
607
824
  console.log();
608
825
  }
609
826
  function printFooter() {
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/scanner/index.ts","../src/scanner/patterns.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { program } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { scan, type ScanResult, type ScanIssue } from './scanner/index.js';\n\nconst VERSION = '0.1.0';\n\n// Score colors\nconst scoreStyles: Record<string, { color: typeof chalk.green }> = {\n A: { color: chalk.green },\n B: { color: chalk.blue },\n C: { color: chalk.yellow },\n D: { color: chalk.red },\n F: { color: chalk.bgRed.white },\n};\n\nconst severityColors: Record<string, typeof chalk.red> = {\n critical: chalk.bgRed.white,\n high: chalk.red,\n medium: chalk.yellow,\n low: chalk.blue,\n};\n\nconst typeLabels: Record<string, string> = {\n secret: 'SECRETS',\n pii: 'PII',\n route: 'ROUTES',\n config: 'CONFIG',\n};\n\n/**\n * Print the banner\n */\nfunction printBanner(): void {\n console.log();\n console.log(chalk.cyan.bold(' Cencori Scan'));\n console.log(chalk.gray(` v${VERSION}`));\n console.log();\n}\n\n/**\n * Print the score box\n */\nfunction printScore(result: ScanResult): void {\n const style = scoreStyles[result.score];\n const scoreText = `${result.score}-Tier`;\n const content = ` Security Score: ${scoreText}`;\n\n console.log();\n console.log(chalk.gray(' ┌─────────────────────────────────────────────┐'));\n console.log(chalk.gray(' │') + style.color.bold(content.padEnd(45)) + chalk.gray('│'));\n console.log(chalk.gray(' └─────────────────────────────────────────────┘'));\n console.log();\n console.log(chalk.gray(` ${result.tierDescription}`));\n console.log();\n}\n\n/**\n * Print issues grouped by type\n */\nfunction printIssues(issues: ScanIssue[]): void {\n if (issues.length === 0) {\n console.log(chalk.green(' No security issues found.'));\n console.log();\n return;\n }\n\n // Group by type\n const grouped: Record<string, ScanIssue[]> = {};\n for (const issue of issues) {\n if (!grouped[issue.type]) {\n grouped[issue.type] = [];\n }\n grouped[issue.type].push(issue);\n }\n\n // Print each group\n for (const [type, typeIssues] of Object.entries(grouped)) {\n const label = typeLabels[type] || type.toUpperCase();\n\n console.log(` ${chalk.bold(label)} (${typeIssues.length})`);\n\n for (let i = 0; i < typeIssues.length; i++) {\n const issue = typeIssues[i];\n const isLast = i === typeIssues.length - 1;\n const prefix = isLast ? ' └─' : ' ├─';\n const severityColor = severityColors[issue.severity];\n\n console.log(\n chalk.gray(prefix) + ' ' +\n chalk.gray(`${issue.file}:${issue.line}`) + ' ' +\n severityColor(issue.match)\n );\n\n if (issue.description) {\n const descPrefix = isLast ? ' ' : ' │ ';\n console.log(chalk.gray(descPrefix) + chalk.dim(issue.description));\n }\n }\n console.log();\n }\n}\n\n/**\n * Print summary stats\n */\nfunction printSummary(result: ScanResult): void {\n const { summary } = result;\n\n console.log(chalk.gray(' ─────────────────────────────────────────────'));\n console.log();\n console.log(` ${chalk.bold('Summary')}`);\n console.log(` Files scanned: ${chalk.cyan(result.filesScanned)}`);\n console.log(` Scan time: ${chalk.cyan(result.scanDuration + 'ms')}`);\n console.log();\n\n if (summary.critical > 0) {\n console.log(` ${chalk.bgRed.white(' CRITICAL ')} ${summary.critical} issues`);\n }\n if (summary.high > 0) {\n console.log(` ${chalk.red(' HIGH ')} ${summary.high} issues`);\n }\n if (summary.medium > 0) {\n console.log(` ${chalk.yellow(' MEDIUM ')} ${summary.medium} issues`);\n }\n if (summary.low > 0) {\n console.log(` ${chalk.blue(' LOW ')} ${summary.low} issues`);\n }\n console.log();\n}\n\n/**\n * Print fix suggestions\n */\nfunction printFixes(issues: ScanIssue[]): void {\n if (issues.length === 0) return;\n\n console.log(` ${chalk.bold('Recommendations:')}`);\n\n const hasSecrets = issues.some(i => i.type === 'secret');\n const hasPII = issues.some(i => i.type === 'pii');\n const hasConfig = issues.some(i => i.type === 'config');\n\n if (hasSecrets) {\n console.log(chalk.gray(' - Use environment variables for secrets'));\n console.log(chalk.gray(' - Never commit API keys to version control'));\n }\n if (hasConfig) {\n console.log(chalk.gray(' - Add .env* to .gitignore'));\n }\n if (hasPII) {\n console.log(chalk.gray(' - Remove personal data from source code'));\n }\n\n console.log();\n}\n\n/**\n * Print footer with links\n */\nfunction printFooter(): void {\n console.log(chalk.gray(' ─────────────────────────────────────────────'));\n console.log();\n console.log(` Share: ${chalk.cyan('https://scan.cencori.com')}`);\n console.log(` Docs: ${chalk.cyan('https://cencori.com/docs')}`);\n console.log();\n}\n\n/**\n * Main CLI function\n */\nasync function main(): Promise<void> {\n program\n .name('cencori-scan')\n .description('Security scanner for AI apps. Detect secrets, PII, and exposed routes.')\n .version(VERSION)\n .argument('[path]', 'Path to scan', '.')\n .option('-j, --json', 'Output results as JSON')\n .option('-q, --quiet', 'Only output the score')\n .option('--no-color', 'Disable colored output')\n .action(async (targetPath: string, options: { json?: boolean; quiet?: boolean }) => {\n if (options.json) {\n // JSON output mode\n const result = await scan(targetPath);\n console.log(JSON.stringify(result, null, 2));\n process.exit(result.score === 'A' || result.score === 'B' ? 0 : 1);\n return;\n }\n\n printBanner();\n\n const spinner = ora({\n text: 'Scanning for security issues...',\n color: 'cyan',\n }).start();\n\n try {\n const result = await scan(targetPath);\n\n spinner.succeed(`Scanned ${result.filesScanned} files`);\n\n if (options.quiet) {\n const style = scoreStyles[result.score];\n console.log(`\\n Score: ${style.color.bold(result.score + '-Tier')}\\n`);\n process.exit(result.score === 'A' || result.score === 'B' ? 0 : 1);\n return;\n }\n\n printScore(result);\n printIssues(result.issues);\n printSummary(result);\n printFixes(result.issues);\n printFooter();\n\n // Exit with error code if issues found\n process.exit(result.score === 'A' || result.score === 'B' ? 0 : 1);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(chalk.red(`\\n Error: ${error instanceof Error ? error.message : 'Unknown error'}`));\n process.exit(1);\n }\n });\n\n program.parse();\n}\n\nmain();\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/cli.ts","../src/scanner/index.ts","../src/scanner/patterns.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { program } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { scan, type ScanResult, type ScanIssue } from './scanner/index.js';\n\nconst VERSION = '0.2.0';\n\n// Score colors\nconst scoreStyles: Record<string, { color: typeof chalk.green }> = {\n A: { color: chalk.green },\n B: { color: chalk.blue },\n C: { color: chalk.yellow },\n D: { color: chalk.red },\n F: { color: chalk.bgRed.white },\n};\n\nconst severityColors: Record<string, typeof chalk.red> = {\n critical: chalk.bgRed.white,\n high: chalk.red,\n medium: chalk.yellow,\n low: chalk.blue,\n};\n\nconst typeLabels: Record<string, string> = {\n secret: 'SECRETS',\n pii: 'PII',\n route: 'ROUTES',\n config: 'CONFIG',\n vulnerability: 'VULNERABILITIES',\n};\n\n/**\n * Print the banner\n */\nfunction printBanner(): void {\n console.log();\n console.log(chalk.cyan.bold(' Cencori Scan'));\n console.log(chalk.gray(` v${VERSION}`));\n console.log();\n}\n\n/**\n * Print the score box\n */\nfunction printScore(result: ScanResult): void {\n const style = scoreStyles[result.score];\n const scoreText = `${result.score}-Tier`;\n const content = ` Security Score: ${scoreText}`;\n\n console.log();\n console.log(chalk.gray(' ┌─────────────────────────────────────────────┐'));\n console.log(chalk.gray(' │') + style.color.bold(content.padEnd(45)) + chalk.gray('│'));\n console.log(chalk.gray(' └─────────────────────────────────────────────┘'));\n console.log();\n console.log(chalk.gray(` ${result.tierDescription}`));\n console.log();\n}\n\n/**\n * Print issues grouped by type\n */\nfunction printIssues(issues: ScanIssue[]): void {\n if (issues.length === 0) {\n console.log(chalk.green(' No security issues found.'));\n console.log();\n return;\n }\n\n // Group by type\n const grouped: Record<string, ScanIssue[]> = {};\n for (const issue of issues) {\n if (!grouped[issue.type]) {\n grouped[issue.type] = [];\n }\n grouped[issue.type].push(issue);\n }\n\n // Print each group\n for (const [type, typeIssues] of Object.entries(grouped)) {\n const label = typeLabels[type] || type.toUpperCase();\n\n console.log(` ${chalk.bold(label)} (${typeIssues.length})`);\n\n for (let i = 0; i < typeIssues.length; i++) {\n const issue = typeIssues[i];\n const isLast = i === typeIssues.length - 1;\n const prefix = isLast ? ' └─' : ' ├─';\n const severityColor = severityColors[issue.severity];\n\n console.log(\n chalk.gray(prefix) + ' ' +\n chalk.gray(`${issue.file}:${issue.line}`) + ' ' +\n severityColor(issue.match)\n );\n\n if (issue.description) {\n const descPrefix = isLast ? ' ' : ' │ ';\n console.log(chalk.gray(descPrefix) + chalk.dim(issue.description));\n }\n }\n console.log();\n }\n}\n\n/**\n * Print summary stats\n */\nfunction printSummary(result: ScanResult): void {\n const { summary } = result;\n\n console.log(chalk.gray(' ─────────────────────────────────────────────'));\n console.log();\n console.log(` ${chalk.bold('Summary')}`);\n console.log(` Files scanned: ${chalk.cyan(result.filesScanned)}`);\n console.log(` Scan time: ${chalk.cyan(result.scanDuration + 'ms')}`);\n console.log();\n\n if (summary.critical > 0) {\n console.log(` ${chalk.bgRed.white(' CRITICAL ')} ${summary.critical} issues`);\n }\n if (summary.high > 0) {\n console.log(` ${chalk.red(' HIGH ')} ${summary.high} issues`);\n }\n if (summary.medium > 0) {\n console.log(` ${chalk.yellow(' MEDIUM ')} ${summary.medium} issues`);\n }\n if (summary.low > 0) {\n console.log(` ${chalk.blue(' LOW ')} ${summary.low} issues`);\n }\n console.log();\n}\n\n/**\n * Print fix suggestions\n */\nfunction printFixes(issues: ScanIssue[]): void {\n if (issues.length === 0) return;\n\n console.log(` ${chalk.bold('Recommendations:')}`);\n\n const hasSecrets = issues.some(i => i.type === 'secret');\n const hasPII = issues.some(i => i.type === 'pii');\n const hasConfig = issues.some(i => i.type === 'config');\n const hasVulnerabilities = issues.some(i => i.type === 'vulnerability');\n const hasXSS = issues.some(i => i.category === 'xss');\n const hasInjection = issues.some(i => i.category === 'injection');\n const hasCORS = issues.some(i => i.category === 'cors');\n const hasDebug = issues.some(i => i.category === 'debug');\n\n if (hasSecrets) {\n console.log(chalk.gray(' - Use environment variables for secrets'));\n console.log(chalk.gray(' - Never commit API keys to version control'));\n }\n if (hasConfig) {\n console.log(chalk.gray(' - Add .env* to .gitignore'));\n }\n if (hasPII) {\n console.log(chalk.gray(' - Remove personal data from source code'));\n }\n if (hasXSS) {\n console.log(chalk.gray(' - Sanitize user input before rendering HTML'));\n console.log(chalk.gray(' - Use textContent instead of innerHTML'));\n }\n if (hasInjection) {\n console.log(chalk.gray(' - Use parameterized queries for SQL'));\n console.log(chalk.gray(' - Avoid using eval and dynamic code execution'));\n }\n if (hasCORS) {\n console.log(chalk.gray(' - Configure CORS with specific allowed origins'));\n }\n if (hasDebug) {\n console.log(chalk.gray(' - Remove console.log statements before production'));\n console.log(chalk.gray(' - Use environment variables for debug flags'));\n }\n\n console.log();\n}\n\n/**\n * Print footer with links\n */\nfunction printFooter(): void {\n console.log(chalk.gray(' ─────────────────────────────────────────────'));\n console.log();\n console.log(` Share: ${chalk.cyan('https://scan.cencori.com')}`);\n console.log(` Docs: ${chalk.cyan('https://cencori.com/docs')}`);\n console.log();\n}\n\n/**\n * Main CLI function\n */\nasync function main(): Promise<void> {\n program\n .name('cencori-scan')\n .description('Security scanner for AI apps. Detect secrets, PII, and exposed routes.')\n .version(VERSION)\n .argument('[path]', 'Path to scan', '.')\n .option('-j, --json', 'Output results as JSON')\n .option('-q, --quiet', 'Only output the score')\n .option('--no-color', 'Disable colored output')\n .action(async (targetPath: string, options: { json?: boolean; quiet?: boolean }) => {\n if (options.json) {\n // JSON output mode\n const result = await scan(targetPath);\n console.log(JSON.stringify(result, null, 2));\n process.exit(result.score === 'A' || result.score === 'B' ? 0 : 1);\n return;\n }\n\n printBanner();\n\n const spinner = ora({\n text: 'Scanning for security issues...',\n color: 'cyan',\n }).start();\n\n try {\n const result = await scan(targetPath);\n\n spinner.succeed(`Scanned ${result.filesScanned} files`);\n\n if (options.quiet) {\n const style = scoreStyles[result.score];\n console.log(`\\n Score: ${style.color.bold(result.score + '-Tier')}\\n`);\n process.exit(result.score === 'A' || result.score === 'B' ? 0 : 1);\n return;\n }\n\n printScore(result);\n printIssues(result.issues);\n printSummary(result);\n printFixes(result.issues);\n printFooter();\n\n // Exit with error code if issues found\n process.exit(result.score === 'A' || result.score === 'B' ? 0 : 1);\n } catch (error) {\n spinner.fail('Scan failed');\n console.error(chalk.red(`\\n Error: ${error instanceof Error ? error.message : 'Unknown error'}`));\n process.exit(1);\n }\n });\n\n program.parse();\n}\n\nmain();\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":[]}