@cencori/scan 0.3.0 → 0.3.1

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 CHANGED
@@ -716,17 +716,62 @@ async function scan(targetPath) {
716
716
  }
717
717
 
718
718
  // src/ai/index.ts
719
+ var fs2 = __toESM(require("fs"));
720
+ var path2 = __toESM(require("path"));
721
+ var os = __toESM(require("os"));
719
722
  var CENCORI_API_URL = "https://api.cencori.com/v1";
723
+ var CONFIG_FILE = ".cencorirc";
724
+ function getConfigPath() {
725
+ return path2.join(os.homedir(), CONFIG_FILE);
726
+ }
727
+ function loadApiKeyFromConfig() {
728
+ try {
729
+ const configPath = getConfigPath();
730
+ if (fs2.existsSync(configPath)) {
731
+ const content = fs2.readFileSync(configPath, "utf-8");
732
+ const lines = content.split("\n");
733
+ for (const line of lines) {
734
+ if (line.startsWith("api_key=")) {
735
+ return line.slice("api_key=".length).trim();
736
+ }
737
+ }
738
+ }
739
+ } catch {
740
+ }
741
+ return void 0;
742
+ }
743
+ function saveApiKey(apiKey) {
744
+ const configPath = getConfigPath();
745
+ fs2.writeFileSync(configPath, `api_key=${apiKey}
746
+ `, { mode: 384 });
747
+ }
720
748
  function getApiKey() {
721
- return process.env.CENCORI_API_KEY;
749
+ return process.env.CENCORI_API_KEY || loadApiKeyFromConfig();
750
+ }
751
+ var sessionApiKey;
752
+ function setSessionApiKey(apiKey) {
753
+ sessionApiKey = apiKey;
754
+ }
755
+ function getEffectiveApiKey() {
756
+ return sessionApiKey || getApiKey();
722
757
  }
723
- function isAIAvailable() {
724
- return !!getApiKey();
758
+ async function validateApiKey(apiKey) {
759
+ try {
760
+ const response = await fetch(`${CENCORI_API_URL}/models`, {
761
+ method: "GET",
762
+ headers: {
763
+ "Authorization": `Bearer ${apiKey}`
764
+ }
765
+ });
766
+ return response.ok;
767
+ } catch {
768
+ return false;
769
+ }
725
770
  }
726
771
  async function analyzeIssues(issues, fileContents) {
727
- const apiKey = getApiKey();
772
+ const apiKey = getEffectiveApiKey();
728
773
  if (!apiKey) {
729
- throw new Error("CENCORI_API_KEY not set");
774
+ throw new Error("No API key available");
730
775
  }
731
776
  const results = [];
732
777
  for (const issue of issues) {
@@ -792,9 +837,9 @@ Is this a real security issue or a false positive (e.g., test data, example code
792
837
  return results;
793
838
  }
794
839
  async function generateFixes(issues, fileContents) {
795
- const apiKey = getApiKey();
840
+ const apiKey = getEffectiveApiKey();
796
841
  if (!apiKey) {
797
- throw new Error("CENCORI_API_KEY not set");
842
+ throw new Error("No API key available");
798
843
  }
799
844
  const results = [];
800
845
  for (const issue of issues) {
@@ -862,8 +907,6 @@ Generate a secure fix.`
862
907
  return results;
863
908
  }
864
909
  async function applyFixes(fixes, fileContents) {
865
- const fs3 = await import("fs");
866
- const path3 = await import("path");
867
910
  for (const fix of fixes) {
868
911
  if (fix.fixedCode === fix.originalCode) {
869
912
  continue;
@@ -874,8 +917,8 @@ async function applyFixes(fixes, fileContents) {
874
917
  }
875
918
  const newContent = content.replace(fix.originalCode, fix.fixedCode);
876
919
  if (newContent !== content) {
877
- const filePath = path3.resolve(fix.issue.file);
878
- fs3.writeFileSync(filePath, newContent, "utf-8");
920
+ const filePath = path2.resolve(fix.issue.file);
921
+ fs2.writeFileSync(filePath, newContent, "utf-8");
879
922
  fix.applied = true;
880
923
  }
881
924
  }
@@ -883,9 +926,9 @@ async function applyFixes(fixes, fileContents) {
883
926
  }
884
927
 
885
928
  // src/cli.ts
886
- var fs2 = __toESM(require("fs"));
887
- var path2 = __toESM(require("path"));
888
- var VERSION = "0.3.0";
929
+ var fs3 = __toESM(require("fs"));
930
+ var path3 = __toESM(require("path"));
931
+ var VERSION = "0.3.1";
889
932
  var scoreStyles = {
890
933
  A: { color: import_chalk.default.green },
891
934
  B: { color: import_chalk.default.blue },
@@ -1008,17 +1051,6 @@ function printRecommendations(issues) {
1008
1051
  }
1009
1052
  console.log();
1010
1053
  }
1011
- function printAIUpsell() {
1012
- console.log(import_chalk.default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1013
- console.log();
1014
- console.log(` ${import_chalk.default.cyan.bold("Upgrade to Cencori Pro")}`);
1015
- console.log(import_chalk.default.gray(" Get AI-powered auto-fix for all issues:"));
1016
- console.log();
1017
- console.log(` 1. Get your API key at ${import_chalk.default.cyan("https://cencori.com/dashboard")}`);
1018
- console.log(` 2. Run: ${import_chalk.default.cyan("export CENCORI_API_KEY=your_key")}`);
1019
- console.log(` 3. Scan again to unlock AI auto-fix`);
1020
- console.log();
1021
- }
1022
1054
  function printFooter() {
1023
1055
  console.log(import_chalk.default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1024
1056
  console.log();
@@ -1031,21 +1063,41 @@ function loadFileContents(issues, basePath) {
1031
1063
  const uniqueFiles = [...new Set(issues.map((i) => i.file))];
1032
1064
  for (const file of uniqueFiles) {
1033
1065
  try {
1034
- const fullPath = path2.resolve(basePath, file);
1035
- const content = fs2.readFileSync(fullPath, "utf-8");
1066
+ const fullPath = path3.resolve(basePath, file);
1067
+ const content = fs3.readFileSync(fullPath, "utf-8");
1036
1068
  contents.set(file, content);
1037
1069
  } catch {
1038
1070
  }
1039
1071
  }
1040
1072
  return contents;
1041
1073
  }
1074
+ async function promptForApiKey() {
1075
+ console.log();
1076
+ console.log(import_chalk.default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1077
+ console.log();
1078
+ console.log(` ${import_chalk.default.cyan.bold("Cencori Pro")}`);
1079
+ console.log(import_chalk.default.gray(" AI-powered auto-fix requires an API key."));
1080
+ console.log();
1081
+ console.log(` Get your free API key at:`);
1082
+ console.log(` ${import_chalk.default.cyan("https://cencori.com/dashboard")} \u2192 API Keys`);
1083
+ console.log();
1084
+ try {
1085
+ const apiKey = await (0, import_prompts.password)({
1086
+ message: "Enter your Cencori API key:",
1087
+ mask: "*"
1088
+ });
1089
+ if (!apiKey || apiKey.trim() === "") {
1090
+ console.log(import_chalk.default.yellow(" No API key entered. Skipping auto-fix."));
1091
+ return void 0;
1092
+ }
1093
+ return apiKey.trim();
1094
+ } catch {
1095
+ return void 0;
1096
+ }
1097
+ }
1042
1098
  async function handleAutoFix(result, targetPath) {
1043
1099
  if (result.issues.length === 0) return;
1044
1100
  console.log();
1045
- if (!isAIAvailable()) {
1046
- printAIUpsell();
1047
- return;
1048
- }
1049
1101
  const shouldFix = await (0, import_prompts.confirm)({
1050
1102
  message: "Would you like Cencori to auto-fix these issues?",
1051
1103
  default: false
@@ -1056,6 +1108,34 @@ async function handleAutoFix(result, targetPath) {
1056
1108
  console.log();
1057
1109
  return;
1058
1110
  }
1111
+ let apiKey = getApiKey();
1112
+ if (!apiKey) {
1113
+ apiKey = await promptForApiKey();
1114
+ if (!apiKey) {
1115
+ console.log();
1116
+ return;
1117
+ }
1118
+ const validatingSpinner = (0, import_ora.default)({
1119
+ text: "Validating API key...",
1120
+ color: "cyan"
1121
+ }).start();
1122
+ const isValid = await validateApiKey(apiKey);
1123
+ if (!isValid) {
1124
+ validatingSpinner.fail("Invalid API key");
1125
+ console.log(import_chalk.default.red(" The API key could not be validated. Please check and try again."));
1126
+ console.log();
1127
+ return;
1128
+ }
1129
+ validatingSpinner.succeed("API key validated");
1130
+ try {
1131
+ saveApiKey(apiKey);
1132
+ console.log(import_chalk.default.green(" \u2714 API key saved to ~/.cencorirc"));
1133
+ } catch {
1134
+ }
1135
+ setSessionApiKey(apiKey);
1136
+ } else {
1137
+ console.log(import_chalk.default.gray(" Using saved API key..."));
1138
+ }
1059
1139
  const fileContents = loadFileContents(result.issues, targetPath);
1060
1140
  const analyzeSpinner = (0, import_ora.default)({
1061
1141
  text: "Analyzing issues with AI...",
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/scanner/index.ts","../src/scanner/patterns.ts","../src/ai/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { program } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { confirm } from '@inquirer/prompts';\nimport { scan, type ScanResult, type ScanIssue } from './scanner/index.js';\nimport { isAIAvailable, analyzeIssues, generateFixes, applyFixes } from './ai/index.js';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nconst VERSION = '0.3.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 recommendations\n */\nfunction printRecommendations(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 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\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 }\n if (hasInjection) {\n console.log(chalk.gray(' - Use parameterized queries for SQL'));\n }\n if (hasCORS) {\n console.log(chalk.gray(' - Configure CORS with specific allowed origins'));\n }\n\n console.log();\n}\n\n/**\n * Print upsell for AI features\n */\nfunction printAIUpsell(): void {\n console.log(chalk.gray(' ─────────────────────────────────────────────'));\n console.log();\n console.log(` ${chalk.cyan.bold('Upgrade to Cencori Pro')}`);\n console.log(chalk.gray(' Get AI-powered auto-fix for all issues:'));\n console.log();\n console.log(` 1. Get your API key at ${chalk.cyan('https://cencori.com/dashboard')}`);\n console.log(` 2. Run: ${chalk.cyan('export CENCORI_API_KEY=your_key')}`);\n console.log(` 3. Scan again to unlock AI auto-fix`);\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 * Load file contents for AI analysis\n */\nfunction loadFileContents(issues: ScanIssue[], basePath: string): Map<string, string> {\n const contents = new Map<string, string>();\n const uniqueFiles = [...new Set(issues.map(i => i.file))];\n\n for (const file of uniqueFiles) {\n try {\n const fullPath = path.resolve(basePath, file);\n const content = fs.readFileSync(fullPath, 'utf-8');\n contents.set(file, content);\n } catch {\n // Skip files that can't be read\n }\n }\n\n return contents;\n}\n\n/**\n * Handle AI auto-fix flow\n */\nasync function handleAutoFix(\n result: ScanResult,\n targetPath: string\n): Promise<void> {\n if (result.issues.length === 0) return;\n\n console.log();\n\n // Check if AI is available\n if (!isAIAvailable()) {\n printAIUpsell();\n return;\n }\n\n // Ask user if they want to auto-fix\n const shouldFix = await confirm({\n message: 'Would you like Cencori to auto-fix these issues?',\n default: false,\n });\n\n if (!shouldFix) {\n console.log();\n console.log(chalk.gray(' Skipped auto-fix. Run again anytime to fix issues.'));\n console.log();\n return;\n }\n\n // Load file contents\n const fileContents = loadFileContents(result.issues, targetPath);\n\n // Analyze with AI\n const analyzeSpinner = ora({\n text: 'Analyzing issues with AI...',\n color: 'cyan',\n }).start();\n\n try {\n const analysis = await analyzeIssues(result.issues, fileContents);\n\n // Filter out false positives\n const realIssues = analysis.filter(a => !a.isFalsePositive);\n const falsePositives = analysis.filter(a => a.isFalsePositive);\n\n if (falsePositives.length > 0) {\n analyzeSpinner.succeed(`${chalk.green(falsePositives.length)} false positives filtered`);\n } else {\n analyzeSpinner.succeed('Analysis complete');\n }\n\n if (realIssues.length === 0) {\n console.log(chalk.green(' All issues were false positives!'));\n return;\n }\n\n // Generate fixes\n const fixSpinner = ora({\n text: 'Generating fixes...',\n color: 'cyan',\n }).start();\n\n const fixes = await generateFixes(\n realIssues.map(a => a.issue),\n fileContents\n );\n\n fixSpinner.succeed(`Generated ${fixes.length} fixes`);\n\n // Apply fixes\n const applySpinner = ora({\n text: 'Applying fixes...',\n color: 'cyan',\n }).start();\n\n const appliedFixes = await applyFixes(fixes, fileContents);\n const appliedCount = appliedFixes.filter(f => f.applied).length;\n\n applySpinner.succeed(`Applied ${appliedCount}/${fixes.length} fixes`);\n\n // Show what was fixed\n console.log();\n console.log(` ${chalk.bold('Applied fixes:')}`);\n for (const fix of appliedFixes.filter(f => f.applied)) {\n console.log(chalk.green(` ✔ ${fix.issue.file}:${fix.issue.line}`));\n console.log(chalk.gray(` ${fix.explanation}`));\n }\n\n const notApplied = appliedFixes.filter(f => !f.applied);\n if (notApplied.length > 0) {\n console.log();\n console.log(` ${chalk.yellow(`${notApplied.length} issues require manual review`)}`);\n }\n\n console.log();\n } catch (error) {\n analyzeSpinner.fail('Auto-fix failed');\n console.error(chalk.red(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`));\n console.log();\n }\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-prompt', 'Skip interactive prompts')\n .option('--no-color', 'Disable colored output')\n .action(async (targetPath: string, options: { json?: boolean; quiet?: boolean; prompt?: boolean }) => {\n if (options.json) {\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 printRecommendations(result.issues);\n\n // Interactive auto-fix prompt (unless --no-prompt)\n if (options.prompt !== false && result.issues.length > 0) {\n await handleAutoFix(result, targetPath);\n }\n\n printFooter();\n\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","/**\n * AI-powered analysis and auto-fix module\n * Uses Cencori API for LLM intelligence\n */\n\nimport type { ScanIssue } from '../scanner/index.js';\n\nconst CENCORI_API_URL = 'https://api.cencori.com/v1';\n\nexport interface AnalysisResult {\n issue: ScanIssue;\n isFalsePositive: boolean;\n confidence: number;\n reason: string;\n}\n\nexport interface FixResult {\n issue: ScanIssue;\n originalCode: string;\n fixedCode: string;\n explanation: string;\n applied: boolean;\n}\n\n/**\n * Check if API key is available\n */\nexport function getApiKey(): string | undefined {\n return process.env.CENCORI_API_KEY;\n}\n\n/**\n * Check if AI features are available\n */\nexport function isAIAvailable(): boolean {\n return !!getApiKey();\n}\n\n/**\n * Analyze issues with AI to filter false positives\n */\nexport async function analyzeIssues(\n issues: ScanIssue[],\n fileContents: Map<string, string>\n): Promise<AnalysisResult[]> {\n const apiKey = getApiKey();\n if (!apiKey) {\n throw new Error('CENCORI_API_KEY not set');\n }\n\n const results: AnalysisResult[] = [];\n\n for (const issue of issues) {\n const content = fileContents.get(issue.file) || '';\n const lines = content.split('\\n');\n const startLine = Math.max(0, issue.line - 3);\n const endLine = Math.min(lines.length, issue.line + 3);\n const context = lines.slice(startLine, endLine).join('\\n');\n\n try {\n const response = await fetch(`${CENCORI_API_URL}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'gpt-4o-mini',\n messages: [\n {\n role: 'system',\n content: `You are a security analyst. Analyze code findings and determine if they are real security issues or false positives. Respond in JSON format: {\"isFalsePositive\": boolean, \"confidence\": number (0-100), \"reason\": \"brief explanation\"}`,\n },\n {\n role: 'user',\n content: `Analyze this security finding:\nType: ${issue.type}\nName: ${issue.name}\nMatch: ${issue.match}\nFile: ${issue.file}:${issue.line}\nContext:\n\\`\\`\\`\n${context}\n\\`\\`\\`\n\nIs this a real security issue or a false positive (e.g., test data, example code, documentation)?`,\n },\n ],\n temperature: 0,\n max_tokens: 150,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status}`);\n }\n\n const data = await response.json() as {\n choices: Array<{ message: { content: string } }>;\n };\n const content_response = data.choices[0]?.message?.content || '{}';\n\n // Parse JSON response\n const parsed = JSON.parse(content_response);\n results.push({\n issue,\n isFalsePositive: parsed.isFalsePositive || false,\n confidence: parsed.confidence || 50,\n reason: parsed.reason || 'Unable to analyze',\n });\n } catch {\n // If analysis fails, assume it's a real issue\n results.push({\n issue,\n isFalsePositive: false,\n confidence: 50,\n reason: 'Analysis failed - treating as potential issue',\n });\n }\n }\n\n return results;\n}\n\n/**\n * Generate fixes for issues using AI\n */\nexport async function generateFixes(\n issues: ScanIssue[],\n fileContents: Map<string, string>\n): Promise<FixResult[]> {\n const apiKey = getApiKey();\n if (!apiKey) {\n throw new Error('CENCORI_API_KEY not set');\n }\n\n const results: FixResult[] = [];\n\n for (const issue of issues) {\n const content = fileContents.get(issue.file) || '';\n const lines = content.split('\\n');\n const startLine = Math.max(0, issue.line - 5);\n const endLine = Math.min(lines.length, issue.line + 5);\n const codeSnippet = lines.slice(startLine, endLine).join('\\n');\n\n try {\n const response = await fetch(`${CENCORI_API_URL}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'gpt-4o-mini',\n messages: [\n {\n role: 'system',\n content: `You are a security engineer. Generate secure code fixes. For secrets, use environment variables. For XSS, use sanitization. Respond in JSON: {\"fixedCode\": \"the fixed code snippet\", \"explanation\": \"what was changed\"}`,\n },\n {\n role: 'user',\n content: `Fix this security issue:\nType: ${issue.type}\nName: ${issue.name}\nFile: ${issue.file}:${issue.line}\n\nCode to fix:\n\\`\\`\\`\n${codeSnippet}\n\\`\\`\\`\n\nGenerate a secure fix.`,\n },\n ],\n temperature: 0,\n max_tokens: 500,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status}`);\n }\n\n const data = await response.json() as {\n choices: Array<{ message: { content: string } }>;\n };\n const content_response = data.choices[0]?.message?.content || '{}';\n\n const parsed = JSON.parse(content_response);\n results.push({\n issue,\n originalCode: codeSnippet,\n fixedCode: parsed.fixedCode || codeSnippet,\n explanation: parsed.explanation || 'No explanation provided',\n applied: false,\n });\n } catch {\n results.push({\n issue,\n originalCode: codeSnippet,\n fixedCode: codeSnippet,\n explanation: 'Unable to generate fix - manual review required',\n applied: false,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Apply fixes to files\n */\nexport async function applyFixes(\n fixes: FixResult[],\n fileContents: Map<string, string>\n): Promise<FixResult[]> {\n const fs = await import('fs');\n const path = await import('path');\n\n for (const fix of fixes) {\n if (fix.fixedCode === fix.originalCode) {\n continue;\n }\n\n const content = fileContents.get(fix.issue.file);\n if (!content) {\n continue;\n }\n\n // Replace the original code with the fixed code\n const newContent = content.replace(fix.originalCode, fix.fixedCode);\n\n if (newContent !== content) {\n const filePath = path.resolve(fix.issue.file);\n fs.writeFileSync(filePath, newContent, 'utf-8');\n fix.applied = true;\n }\n }\n\n return fixes;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,uBAAwB;AACxB,mBAAkB;AAClB,iBAAgB;AAChB,qBAAwB;;;ACLxB,SAAoB;AACpB,WAAsB;AACtB,kBAAqB;;;ACQd,IAAM,kBAAmC;AAAA;AAAA,EAE5C;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AACJ;AAWO,IAAM,eAA6B;AAAA,EACtC;AAAA,IACI,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AACJ;AAaO,IAAM,iBAAiC;AAAA;AAAA,EAE1C;AAAA,IACI,MAAM;AAAA,IACN,WAAW;AAAA,IACX,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,WAAW;AAAA,IACX,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,WAAW;AAAA,IACX,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AACJ;AAaO,IAAM,yBAAiD;AAAA;AAAA,EAE1D;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AACJ;AAKO,IAAM,kBAAkB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AAKO,IAAM,uBAAuB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;;;ADjbA,SAAS,OAAO,OAAe,YAAoB,GAAW;AAC1D,MAAI,MAAM,UAAU,YAAY,GAAG;AAC/B,WAAO,IAAI,OAAO,MAAM,MAAM;AAAA,EAClC;AACA,SAAO,MAAM,MAAM,GAAG,SAAS,IAAI,SAAS,MAAM,MAAM,CAAC,SAAS;AACtE;AAKA,SAAS,YAAY,SAAiB,OAAiD;AACnF,QAAM,QAAQ,QAAQ,MAAM,GAAG,KAAK,EAAE,MAAM,IAAI;AAChD,SAAO;AAAA,IACH,MAAM,MAAM;AAAA,IACZ,QAAQ,MAAM,MAAM,SAAS,CAAC,EAAE,SAAS;AAAA,EAC7C;AACJ;AAKA,SAAS,aAAa,UAA2B;AAC7C,QAAM,aAAa,SAAS,QAAQ,OAAO,GAAG;AAC9C,SAAO,gBAAgB,KAAK,aAAW;AACnC,QAAI,QAAQ,WAAW,GAAG,GAAG;AACzB,aAAO,WAAW,SAAS,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC/C;AACA,WAAO,WAAW,SAAS,OAAO;AAAA,EACtC,CAAC;AACL;AAKA,SAAS,YAAY,UAA2B;AAC5C,QAAM,MAAW,aAAQ,QAAQ,EAAE,YAAY;AAC/C,SAAO,qBAAqB,SAAS,GAAG;AAC5C;AAKA,SAAS,gBAAgB,UAA2B;AAChD,QAAM,QAAQ,SAAS,YAAY;AACnC,SACI,MAAM,SAAS,QAAQ,KACvB,MAAM,SAAS,QAAQ,KACvB,MAAM,SAAS,WAAW,KAC1B,MAAM,SAAS,QAAQ,KACvB,MAAM,SAAS,SAAS,KACxB,MAAM,SAAS,KAAK,KACpB,MAAM,SAAS,QAAQ;AAE/B;AAKA,SAAS,SAAS,UAAkB,SAA8B;AAC9D,QAAM,SAAsB,CAAC;AAC7B,QAAM,eAAe;AACrB,QAAM,YAAY,gBAAgB,QAAQ;AAG1C,aAAW,WAAW,iBAAiB;AACnC,YAAQ,QAAQ,YAAY;AAC5B,QAAI;AACJ,YAAQ,QAAQ,QAAQ,QAAQ,KAAK,OAAO,OAAO,MAAM;AACrD,YAAM,MAAM,YAAY,SAAS,MAAM,KAAK;AAC5C,aAAO,KAAK;AAAA,QACR,MAAM;AAAA,QACN,UAAU,QAAQ;AAAA,QAClB,MAAM,QAAQ;AAAA,QACd,UAAU,QAAQ;AAAA,QAClB,MAAM;AAAA,QACN,MAAM,IAAI;AAAA,QACV,QAAQ,IAAI;AAAA,QACZ,OAAO,OAAO,MAAM,CAAC,CAAC;AAAA,MAC1B,CAAC;AAAA,IACL;AAAA,EACJ;AAGA,MAAI,CAAC,WAAW;AACZ,eAAW,WAAW,cAAc;AAChC,cAAQ,QAAQ,YAAY;AAC5B,UAAI;AACJ,cAAQ,QAAQ,QAAQ,QAAQ,KAAK,OAAO,OAAO,MAAM;AACrD,cAAM,WAAW,MAAM,CAAC;AACxB,YAAI,sBAAsB,UAAU,QAAQ,MAAM,QAAQ,GAAG;AACzD;AAAA,QACJ;AAEA,cAAM,MAAM,YAAY,SAAS,MAAM,KAAK;AAC5C,eAAO,KAAK;AAAA,UACR,MAAM;AAAA,UACN,UAAU,QAAQ;AAAA,UAClB,MAAM,QAAQ;AAAA,UACd,MAAM;AAAA,UACN,MAAM,IAAI;AAAA,UACV,QAAQ,IAAI;AAAA,UACZ,OAAO,OAAO,UAAU,CAAC;AAAA,QAC7B,CAAC;AAAA,MACL;AAAA,IACJ;AAAA,EACJ;AAGA,aAAW,WAAW,gBAAgB;AAClC,YAAQ,QAAQ,YAAY;AAC5B,QAAI;AACJ,YAAQ,QAAQ,QAAQ,QAAQ,KAAK,OAAO,OAAO,MAAM;AACrD,YAAM,MAAM,YAAY,SAAS,MAAM,KAAK;AAC5C,aAAO,KAAK;AAAA,QACR,MAAM;AAAA,QACN,UAAU,QAAQ;AAAA,QAClB,MAAM,QAAQ;AAAA,QACd,MAAM;AAAA,QACN,MAAM,IAAI;AAAA,QACV,QAAQ,IAAI;AAAA,QACZ,OAAO,MAAM,CAAC;AAAA,QACd,aAAa,QAAQ;AAAA,MACzB,CAAC;AAAA,IACL;AAAA,EACJ;AAGA,aAAW,WAAW,wBAAwB;AAE1C,QAAI,QAAQ,aAAa,WAAW,WAAW;AAC3C;AAAA,IACJ;AAEA,YAAQ,QAAQ,YAAY;AAC5B,QAAI;AACJ,YAAQ,QAAQ,QAAQ,QAAQ,KAAK,OAAO,OAAO,MAAM;AAErD,UAAI,QAAQ,aAAa,WAAW,QAAQ,SAAS,yBAAyB;AAE1E,YAAI,MAAM,CAAC,EAAE,SAAS,OAAO,KAAK,MAAM,CAAC,EAAE,SAAS,MAAM,GAAG;AACzD;AAAA,QACJ;AAAA,MACJ;AAEA,YAAM,MAAM,YAAY,SAAS,MAAM,KAAK;AAC5C,aAAO,KAAK;AAAA,QACR,MAAM;AAAA,QACN,UAAU,QAAQ;AAAA,QAClB,UAAU,QAAQ;AAAA,QAClB,MAAM,QAAQ;AAAA,QACd,MAAM;AAAA,QACN,MAAM,IAAI;AAAA,QACV,QAAQ,IAAI;AAAA,QACZ,OAAO,MAAM,CAAC,EAAE,SAAS,KAAK,MAAM,CAAC,EAAE,MAAM,GAAG,EAAE,IAAI,QAAQ,MAAM,CAAC;AAAA,QACrE,aAAa,QAAQ;AAAA,MACzB,CAAC;AAAA,IACL;AAAA,EACJ;AAGA,QAAM,WAAgB,cAAS,QAAQ;AACvC,MAAI,SAAS,WAAW,MAAM,KAAK,CAAC,SAAS,SAAS,UAAU,GAAG;AAC/D,WAAO,KAAK;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,aAAa;AAAA,IACjB,CAAC;AAAA,EACL;AAEA,SAAO;AACX;AAKA,SAAS,sBAAsB,OAAe,aAAqB,UAA2B;AAE1F,MAAI,gBAAgB,iBAAiB;AACjC,UAAM,eAAe,CAAC,eAAe,eAAe,YAAY,aAAa,iBAAiB;AAC9F,QAAI,aAAa,KAAK,OAAK,MAAM,SAAS,CAAC,CAAC,GAAG;AAC3C,aAAO;AAAA,IACX;AAEA,UAAM,iBAAiB;AAAA,MACnB;AAAA,MAAY;AAAA,MAAS;AAAA,MAAS;AAAA,MAAY;AAAA,MAAU;AAAA,MACpD;AAAA,MAAY;AAAA,MAAa;AAAA,MAAU;AAAA,MAAS;AAAA,MAC5C;AAAA,MAAe;AAAA,MAAa;AAAA,MAAY;AAAA,IAC5C;AACA,QAAI,eAAe,KAAK,OAAK,MAAM,YAAY,EAAE,WAAW,CAAC,CAAC,GAAG;AAC7D,aAAO;AAAA,IACX;AAAA,EACJ;AAGA,MAAI,gBAAgB,cAAc;AAC9B,UAAM,WAAW,CAAC,WAAW,aAAa,YAAY,SAAS,SAAS;AACxE,QAAI,SAAS,KAAK,QAAM,MAAM,WAAW,EAAE,CAAC,GAAG;AAC3C,aAAO;AAAA,IACX;AAAA,EACJ;AAGA,MAAI,YAAY,SAAS,cAAc,GAAG;AACtC,QAAI,MAAM,SAAS,KAAK,KAAK,MAAM,SAAS,SAAS,KAAK,MAAM,SAAS,SAAS,GAAG;AACjF,aAAO;AAAA,IACX;AAAA,EACJ;AAEA,SAAO;AACX;AAKA,SAAS,eAAe,QAAkD;AACtE,QAAM,WAAW,OAAO,OAAO,OAAK,EAAE,aAAa,UAAU,EAAE;AAC/D,QAAM,OAAO,OAAO,OAAO,OAAK,EAAE,aAAa,MAAM,EAAE;AACvD,QAAM,SAAS,OAAO,OAAO,OAAK,EAAE,aAAa,QAAQ,EAAE;AAE3D,MAAI,WAAW,EAAG,QAAO;AACzB,MAAI,QAAQ,EAAG,QAAO;AACtB,MAAI,QAAQ,EAAG,QAAO;AACtB,MAAI,QAAQ,KAAK,UAAU,EAAG,QAAO;AACrC,MAAI,UAAU,EAAG,QAAO;AACxB,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO;AACX;AAKA,SAAS,mBAAmB,OAAuB;AAC/C,UAAQ,OAAO;AAAA,IACX,KAAK;AAAK,aAAO;AAAA,IACjB,KAAK;AAAK,aAAO;AAAA,IACjB,KAAK;AAAK,aAAO;AAAA,IACjB,KAAK;AAAK,aAAO;AAAA,IACjB,KAAK;AAAK,aAAO;AAAA,IACjB;AAAS,aAAO;AAAA,EACpB;AACJ;AAKA,eAAsB,KAAK,YAAyC;AAChE,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,eAAoB,aAAQ,UAAU;AAE5C,QAAM,QAAQ,UAAM,kBAAK,QAAQ;AAAA,IAC7B,KAAK;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EACd,CAAC;AAED,QAAM,SAAsB,CAAC;AAC7B,MAAI,eAAe;AAEnB,aAAW,QAAQ,OAAO;AACtB,QAAI,CAAC,YAAY,IAAI,KAAK,aAAa,IAAI,GAAG;AAC1C;AAAA,IACJ;AAEA,QAAI;AACA,YAAM,UAAa,gBAAa,MAAM,OAAO;AAC7C,YAAM,eAAoB,cAAS,cAAc,IAAI;AACrD,YAAM,aAAa,SAAS,cAAc,OAAO;AACjD,aAAO,KAAK,GAAG,UAAU;AACzB;AAAA,IACJ,QAAQ;AACJ;AAAA,IACJ;AAAA,EACJ;AAEA,QAAM,QAAQ,eAAe,MAAM;AACnC,QAAM,eAAe,KAAK,IAAI,IAAI;AAElC,SAAO;AAAA,IACH;AAAA,IACA,iBAAiB,mBAAmB,KAAK;AAAA,IACzC;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,MACL,SAAS,OAAO,OAAO,OAAK,EAAE,SAAS,QAAQ,EAAE;AAAA,MACjD,KAAK,OAAO,OAAO,OAAK,EAAE,SAAS,KAAK,EAAE;AAAA,MAC1C,QAAQ,OAAO,OAAO,OAAK,EAAE,SAAS,OAAO,EAAE;AAAA,MAC/C,QAAQ,OAAO,OAAO,OAAK,EAAE,SAAS,QAAQ,EAAE;AAAA,MAChD,iBAAiB,OAAO,OAAO,OAAK,EAAE,SAAS,eAAe,EAAE;AAAA,MAChE,UAAU,OAAO,OAAO,OAAK,EAAE,aAAa,UAAU,EAAE;AAAA,MACxD,MAAM,OAAO,OAAO,OAAK,EAAE,aAAa,MAAM,EAAE;AAAA,MAChD,QAAQ,OAAO,OAAO,OAAK,EAAE,aAAa,QAAQ,EAAE;AAAA,MACpD,KAAK,OAAO,OAAO,OAAK,EAAE,aAAa,KAAK,EAAE;AAAA,IAClD;AAAA,EACJ;AACJ;;;AExVA,IAAM,kBAAkB;AAoBjB,SAAS,YAAgC;AAC5C,SAAO,QAAQ,IAAI;AACvB;AAKO,SAAS,gBAAyB;AACrC,SAAO,CAAC,CAAC,UAAU;AACvB;AAKA,eAAsB,cAClB,QACA,cACyB;AACzB,QAAM,SAAS,UAAU;AACzB,MAAI,CAAC,QAAQ;AACT,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC7C;AAEA,QAAM,UAA4B,CAAC;AAEnC,aAAW,SAAS,QAAQ;AACxB,UAAM,UAAU,aAAa,IAAI,MAAM,IAAI,KAAK;AAChD,UAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,UAAM,YAAY,KAAK,IAAI,GAAG,MAAM,OAAO,CAAC;AAC5C,UAAM,UAAU,KAAK,IAAI,MAAM,QAAQ,MAAM,OAAO,CAAC;AACrD,UAAM,UAAU,MAAM,MAAM,WAAW,OAAO,EAAE,KAAK,IAAI;AAEzD,QAAI;AACA,YAAM,WAAW,MAAM,MAAM,GAAG,eAAe,qBAAqB;AAAA,QAChE,QAAQ;AAAA,QACR,SAAS;AAAA,UACL,gBAAgB;AAAA,UAChB,iBAAiB,UAAU,MAAM;AAAA,QACrC;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACjB,OAAO;AAAA,UACP,UAAU;AAAA,YACN;AAAA,cACI,MAAM;AAAA,cACN,SAAS;AAAA,YACb;AAAA,YACA;AAAA,cACI,MAAM;AAAA,cACN,SAAS;AAAA,QAC7B,MAAM,IAAI;AAAA,QACV,MAAM,IAAI;AAAA,SACT,MAAM,KAAK;AAAA,QACZ,MAAM,IAAI,IAAI,MAAM,IAAI;AAAA;AAAA;AAAA,EAG9B,OAAO;AAAA;AAAA;AAAA;AAAA,YAIe;AAAA,UACJ;AAAA,UACA,aAAa;AAAA,UACb,YAAY;AAAA,QAChB,CAAC;AAAA,MACL,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AACd,cAAM,IAAI,MAAM,cAAc,SAAS,MAAM,EAAE;AAAA,MACnD;AAEA,YAAM,OAAO,MAAM,SAAS,KAAK;AAGjC,YAAM,mBAAmB,KAAK,QAAQ,CAAC,GAAG,SAAS,WAAW;AAG9D,YAAM,SAAS,KAAK,MAAM,gBAAgB;AAC1C,cAAQ,KAAK;AAAA,QACT;AAAA,QACA,iBAAiB,OAAO,mBAAmB;AAAA,QAC3C,YAAY,OAAO,cAAc;AAAA,QACjC,QAAQ,OAAO,UAAU;AAAA,MAC7B,CAAC;AAAA,IACL,QAAQ;AAEJ,cAAQ,KAAK;AAAA,QACT;AAAA,QACA,iBAAiB;AAAA,QACjB,YAAY;AAAA,QACZ,QAAQ;AAAA,MACZ,CAAC;AAAA,IACL;AAAA,EACJ;AAEA,SAAO;AACX;AAKA,eAAsB,cAClB,QACA,cACoB;AACpB,QAAM,SAAS,UAAU;AACzB,MAAI,CAAC,QAAQ;AACT,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC7C;AAEA,QAAM,UAAuB,CAAC;AAE9B,aAAW,SAAS,QAAQ;AACxB,UAAM,UAAU,aAAa,IAAI,MAAM,IAAI,KAAK;AAChD,UAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,UAAM,YAAY,KAAK,IAAI,GAAG,MAAM,OAAO,CAAC;AAC5C,UAAM,UAAU,KAAK,IAAI,MAAM,QAAQ,MAAM,OAAO,CAAC;AACrD,UAAM,cAAc,MAAM,MAAM,WAAW,OAAO,EAAE,KAAK,IAAI;AAE7D,QAAI;AACA,YAAM,WAAW,MAAM,MAAM,GAAG,eAAe,qBAAqB;AAAA,QAChE,QAAQ;AAAA,QACR,SAAS;AAAA,UACL,gBAAgB;AAAA,UAChB,iBAAiB,UAAU,MAAM;AAAA,QACrC;AAAA,QACA,MAAM,KAAK,UAAU;AAAA,UACjB,OAAO;AAAA,UACP,UAAU;AAAA,YACN;AAAA,cACI,MAAM;AAAA,cACN,SAAS;AAAA,YACb;AAAA,YACA;AAAA,cACI,MAAM;AAAA,cACN,SAAS;AAAA,QAC7B,MAAM,IAAI;AAAA,QACV,MAAM,IAAI;AAAA,QACV,MAAM,IAAI,IAAI,MAAM,IAAI;AAAA;AAAA;AAAA;AAAA,EAI9B,WAAW;AAAA;AAAA;AAAA;AAAA,YAIW;AAAA,UACJ;AAAA,UACA,aAAa;AAAA,UACb,YAAY;AAAA,QAChB,CAAC;AAAA,MACL,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AACd,cAAM,IAAI,MAAM,cAAc,SAAS,MAAM,EAAE;AAAA,MACnD;AAEA,YAAM,OAAO,MAAM,SAAS,KAAK;AAGjC,YAAM,mBAAmB,KAAK,QAAQ,CAAC,GAAG,SAAS,WAAW;AAE9D,YAAM,SAAS,KAAK,MAAM,gBAAgB;AAC1C,cAAQ,KAAK;AAAA,QACT;AAAA,QACA,cAAc;AAAA,QACd,WAAW,OAAO,aAAa;AAAA,QAC/B,aAAa,OAAO,eAAe;AAAA,QACnC,SAAS;AAAA,MACb,CAAC;AAAA,IACL,QAAQ;AACJ,cAAQ,KAAK;AAAA,QACT;AAAA,QACA,cAAc;AAAA,QACd,WAAW;AAAA,QACX,aAAa;AAAA,QACb,SAAS;AAAA,MACb,CAAC;AAAA,IACL;AAAA,EACJ;AAEA,SAAO;AACX;AAKA,eAAsB,WAClB,OACA,cACoB;AACpB,QAAMA,MAAK,MAAM,OAAO,IAAI;AAC5B,QAAMC,QAAO,MAAM,OAAO,MAAM;AAEhC,aAAW,OAAO,OAAO;AACrB,QAAI,IAAI,cAAc,IAAI,cAAc;AACpC;AAAA,IACJ;AAEA,UAAM,UAAU,aAAa,IAAI,IAAI,MAAM,IAAI;AAC/C,QAAI,CAAC,SAAS;AACV;AAAA,IACJ;AAGA,UAAM,aAAa,QAAQ,QAAQ,IAAI,cAAc,IAAI,SAAS;AAElE,QAAI,eAAe,SAAS;AACxB,YAAM,WAAWA,MAAK,QAAQ,IAAI,MAAM,IAAI;AAC5C,MAAAD,IAAG,cAAc,UAAU,YAAY,OAAO;AAC9C,UAAI,UAAU;AAAA,IAClB;AAAA,EACJ;AAEA,SAAO;AACX;;;AHzOA,IAAAE,MAAoB;AACpB,IAAAC,QAAsB;AAEtB,IAAM,UAAU;AAGhB,IAAM,cAA6D;AAAA,EAC/D,GAAG,EAAE,OAAO,aAAAC,QAAM,MAAM;AAAA,EACxB,GAAG,EAAE,OAAO,aAAAA,QAAM,KAAK;AAAA,EACvB,GAAG,EAAE,OAAO,aAAAA,QAAM,OAAO;AAAA,EACzB,GAAG,EAAE,OAAO,aAAAA,QAAM,IAAI;AAAA,EACtB,GAAG,EAAE,OAAO,aAAAA,QAAM,MAAM,MAAM;AAClC;AAEA,IAAM,iBAAmD;AAAA,EACrD,UAAU,aAAAA,QAAM,MAAM;AAAA,EACtB,MAAM,aAAAA,QAAM;AAAA,EACZ,QAAQ,aAAAA,QAAM;AAAA,EACd,KAAK,aAAAA,QAAM;AACf;AAEA,IAAM,aAAqC;AAAA,EACvC,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,eAAe;AACnB;AAKA,SAAS,cAAoB;AACzB,UAAQ,IAAI;AACZ,UAAQ,IAAI,aAAAA,QAAM,KAAK,KAAK,gBAAgB,CAAC;AAC7C,UAAQ,IAAI,aAAAA,QAAM,KAAK,MAAM,OAAO,EAAE,CAAC;AACvC,UAAQ,IAAI;AAChB;AAKA,SAAS,WAAW,QAA0B;AAC1C,QAAM,QAAQ,YAAY,OAAO,KAAK;AACtC,QAAM,YAAY,GAAG,OAAO,KAAK;AACjC,QAAM,UAAU,sBAAsB,SAAS;AAE/C,UAAQ,IAAI;AACZ,UAAQ,IAAI,aAAAA,QAAM,KAAK,8RAAmD,CAAC;AAC3E,UAAQ,IAAI,aAAAA,QAAM,KAAK,UAAK,IAAI,MAAM,MAAM,KAAK,QAAQ,OAAO,EAAE,CAAC,IAAI,aAAAA,QAAM,KAAK,QAAG,CAAC;AACtF,UAAQ,IAAI,aAAAA,QAAM,KAAK,8RAAmD,CAAC;AAC3E,UAAQ,IAAI;AACZ,UAAQ,IAAI,aAAAA,QAAM,KAAK,KAAK,OAAO,eAAe,EAAE,CAAC;AACrD,UAAQ,IAAI;AAChB;AAKA,SAAS,YAAY,QAA2B;AAC5C,MAAI,OAAO,WAAW,GAAG;AACrB,YAAQ,IAAI,aAAAA,QAAM,MAAM,6BAA6B,CAAC;AACtD,YAAQ,IAAI;AACZ;AAAA,EACJ;AAGA,QAAM,UAAuC,CAAC;AAC9C,aAAW,SAAS,QAAQ;AACxB,QAAI,CAAC,QAAQ,MAAM,IAAI,GAAG;AACtB,cAAQ,MAAM,IAAI,IAAI,CAAC;AAAA,IAC3B;AACA,YAAQ,MAAM,IAAI,EAAE,KAAK,KAAK;AAAA,EAClC;AAGA,aAAW,CAAC,MAAM,UAAU,KAAK,OAAO,QAAQ,OAAO,GAAG;AACtD,UAAM,QAAQ,WAAW,IAAI,KAAK,KAAK,YAAY;AAEnD,YAAQ,IAAI,KAAK,aAAAA,QAAM,KAAK,KAAK,CAAC,KAAK,WAAW,MAAM,GAAG;AAE3D,aAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AACxC,YAAM,QAAQ,WAAW,CAAC;AAC1B,YAAM,SAAS,MAAM,WAAW,SAAS;AACzC,YAAM,SAAS,SAAS,mBAAS;AACjC,YAAM,gBAAgB,eAAe,MAAM,QAAQ;AAEnD,cAAQ;AAAA,QACJ,aAAAA,QAAM,KAAK,MAAM,IAAI,MACrB,aAAAA,QAAM,KAAK,GAAG,MAAM,IAAI,IAAI,MAAM,IAAI,EAAE,IAAI,OAC5C,cAAc,MAAM,KAAK;AAAA,MAC7B;AAEA,UAAI,MAAM,aAAa;AACnB,cAAM,aAAa,SAAS,UAAU;AACtC,gBAAQ,IAAI,aAAAA,QAAM,KAAK,UAAU,IAAI,aAAAA,QAAM,IAAI,MAAM,WAAW,CAAC;AAAA,MACrE;AAAA,IACJ;AACA,YAAQ,IAAI;AAAA,EAChB;AACJ;AAKA,SAAS,aAAa,QAA0B;AAC5C,QAAM,EAAE,QAAQ,IAAI;AAEpB,UAAQ,IAAI,aAAAA,QAAM,KAAK,kRAAiD,CAAC;AACzE,UAAQ,IAAI;AACZ,UAAQ,IAAI,KAAK,aAAAA,QAAM,KAAK,SAAS,CAAC,EAAE;AACxC,UAAQ,IAAI,sBAAsB,aAAAA,QAAM,KAAK,OAAO,YAAY,CAAC,EAAE;AACnE,UAAQ,IAAI,kBAAkB,aAAAA,QAAM,KAAK,OAAO,eAAe,IAAI,CAAC,EAAE;AACtE,UAAQ,IAAI;AAEZ,MAAI,QAAQ,WAAW,GAAG;AACtB,YAAQ,IAAI,OAAO,aAAAA,QAAM,MAAM,MAAM,YAAY,CAAC,IAAI,QAAQ,QAAQ,SAAS;AAAA,EACnF;AACA,MAAI,QAAQ,OAAO,GAAG;AAClB,YAAQ,IAAI,OAAO,aAAAA,QAAM,IAAI,YAAY,CAAC,IAAI,QAAQ,IAAI,SAAS;AAAA,EACvE;AACA,MAAI,QAAQ,SAAS,GAAG;AACpB,YAAQ,IAAI,OAAO,aAAAA,QAAM,OAAO,WAAW,CAAC,IAAI,QAAQ,MAAM,SAAS;AAAA,EAC3E;AACA,MAAI,QAAQ,MAAM,GAAG;AACjB,YAAQ,IAAI,OAAO,aAAAA,QAAM,KAAK,YAAY,CAAC,IAAI,QAAQ,GAAG,SAAS;AAAA,EACvE;AACA,UAAQ,IAAI;AAChB;AAKA,SAAS,qBAAqB,QAA2B;AACrD,MAAI,OAAO,WAAW,EAAG;AAEzB,UAAQ,IAAI,KAAK,aAAAA,QAAM,KAAK,kBAAkB,CAAC,EAAE;AAEjD,QAAM,aAAa,OAAO,KAAK,OAAK,EAAE,SAAS,QAAQ;AACvD,QAAM,SAAS,OAAO,KAAK,OAAK,EAAE,SAAS,KAAK;AAChD,QAAM,YAAY,OAAO,KAAK,OAAK,EAAE,SAAS,QAAQ;AACtD,QAAM,SAAS,OAAO,KAAK,OAAK,EAAE,aAAa,KAAK;AACpD,QAAM,eAAe,OAAO,KAAK,OAAK,EAAE,aAAa,WAAW;AAChE,QAAM,UAAU,OAAO,KAAK,OAAK,EAAE,aAAa,MAAM;AAEtD,MAAI,YAAY;AACZ,YAAQ,IAAI,aAAAA,QAAM,KAAK,6CAA6C,CAAC;AACrE,YAAQ,IAAI,aAAAA,QAAM,KAAK,gDAAgD,CAAC;AAAA,EAC5E;AACA,MAAI,WAAW;AACX,YAAQ,IAAI,aAAAA,QAAM,KAAK,+BAA+B,CAAC;AAAA,EAC3D;AACA,MAAI,QAAQ;AACR,YAAQ,IAAI,aAAAA,QAAM,KAAK,6CAA6C,CAAC;AAAA,EACzE;AACA,MAAI,QAAQ;AACR,YAAQ,IAAI,aAAAA,QAAM,KAAK,iDAAiD,CAAC;AAAA,EAC7E;AACA,MAAI,cAAc;AACd,YAAQ,IAAI,aAAAA,QAAM,KAAK,yCAAyC,CAAC;AAAA,EACrE;AACA,MAAI,SAAS;AACT,YAAQ,IAAI,aAAAA,QAAM,KAAK,oDAAoD,CAAC;AAAA,EAChF;AAEA,UAAQ,IAAI;AAChB;AAKA,SAAS,gBAAsB;AAC3B,UAAQ,IAAI,aAAAA,QAAM,KAAK,kRAAiD,CAAC;AACzE,UAAQ,IAAI;AACZ,UAAQ,IAAI,KAAK,aAAAA,QAAM,KAAK,KAAK,wBAAwB,CAAC,EAAE;AAC5D,UAAQ,IAAI,aAAAA,QAAM,KAAK,2CAA2C,CAAC;AACnE,UAAQ,IAAI;AACZ,UAAQ,IAAI,4BAA4B,aAAAA,QAAM,KAAK,+BAA+B,CAAC,EAAE;AACrF,UAAQ,IAAI,aAAa,aAAAA,QAAM,KAAK,iCAAiC,CAAC,EAAE;AACxE,UAAQ,IAAI,uCAAuC;AACnD,UAAQ,IAAI;AAChB;AAKA,SAAS,cAAoB;AACzB,UAAQ,IAAI,aAAAA,QAAM,KAAK,kRAAiD,CAAC;AACzE,UAAQ,IAAI;AACZ,UAAQ,IAAI,YAAY,aAAAA,QAAM,KAAK,0BAA0B,CAAC,EAAE;AAChE,UAAQ,IAAI,YAAY,aAAAA,QAAM,KAAK,0BAA0B,CAAC,EAAE;AAChE,UAAQ,IAAI;AAChB;AAKA,SAAS,iBAAiB,QAAqB,UAAuC;AAClF,QAAM,WAAW,oBAAI,IAAoB;AACzC,QAAM,cAAc,CAAC,GAAG,IAAI,IAAI,OAAO,IAAI,OAAK,EAAE,IAAI,CAAC,CAAC;AAExD,aAAW,QAAQ,aAAa;AAC5B,QAAI;AACA,YAAM,WAAgB,cAAQ,UAAU,IAAI;AAC5C,YAAM,UAAa,iBAAa,UAAU,OAAO;AACjD,eAAS,IAAI,MAAM,OAAO;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACJ;AAEA,SAAO;AACX;AAKA,eAAe,cACX,QACA,YACa;AACb,MAAI,OAAO,OAAO,WAAW,EAAG;AAEhC,UAAQ,IAAI;AAGZ,MAAI,CAAC,cAAc,GAAG;AAClB,kBAAc;AACd;AAAA,EACJ;AAGA,QAAM,YAAY,UAAM,wBAAQ;AAAA,IAC5B,SAAS;AAAA,IACT,SAAS;AAAA,EACb,CAAC;AAED,MAAI,CAAC,WAAW;AACZ,YAAQ,IAAI;AACZ,YAAQ,IAAI,aAAAA,QAAM,KAAK,sDAAsD,CAAC;AAC9E,YAAQ,IAAI;AACZ;AAAA,EACJ;AAGA,QAAM,eAAe,iBAAiB,OAAO,QAAQ,UAAU;AAG/D,QAAM,qBAAiB,WAAAC,SAAI;AAAA,IACvB,MAAM;AAAA,IACN,OAAO;AAAA,EACX,CAAC,EAAE,MAAM;AAET,MAAI;AACA,UAAM,WAAW,MAAM,cAAc,OAAO,QAAQ,YAAY;AAGhE,UAAM,aAAa,SAAS,OAAO,OAAK,CAAC,EAAE,eAAe;AAC1D,UAAM,iBAAiB,SAAS,OAAO,OAAK,EAAE,eAAe;AAE7D,QAAI,eAAe,SAAS,GAAG;AAC3B,qBAAe,QAAQ,GAAG,aAAAD,QAAM,MAAM,eAAe,MAAM,CAAC,2BAA2B;AAAA,IAC3F,OAAO;AACH,qBAAe,QAAQ,mBAAmB;AAAA,IAC9C;AAEA,QAAI,WAAW,WAAW,GAAG;AACzB,cAAQ,IAAI,aAAAA,QAAM,MAAM,oCAAoC,CAAC;AAC7D;AAAA,IACJ;AAGA,UAAM,iBAAa,WAAAC,SAAI;AAAA,MACnB,MAAM;AAAA,MACN,OAAO;AAAA,IACX,CAAC,EAAE,MAAM;AAET,UAAM,QAAQ,MAAM;AAAA,MAChB,WAAW,IAAI,OAAK,EAAE,KAAK;AAAA,MAC3B;AAAA,IACJ;AAEA,eAAW,QAAQ,aAAa,MAAM,MAAM,QAAQ;AAGpD,UAAM,mBAAe,WAAAA,SAAI;AAAA,MACrB,MAAM;AAAA,MACN,OAAO;AAAA,IACX,CAAC,EAAE,MAAM;AAET,UAAM,eAAe,MAAM,WAAW,OAAO,YAAY;AACzD,UAAM,eAAe,aAAa,OAAO,OAAK,EAAE,OAAO,EAAE;AAEzD,iBAAa,QAAQ,WAAW,YAAY,IAAI,MAAM,MAAM,QAAQ;AAGpE,YAAQ,IAAI;AACZ,YAAQ,IAAI,KAAK,aAAAD,QAAM,KAAK,gBAAgB,CAAC,EAAE;AAC/C,eAAW,OAAO,aAAa,OAAO,OAAK,EAAE,OAAO,GAAG;AACnD,cAAQ,IAAI,aAAAA,QAAM,MAAM,cAAS,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC;AACpE,cAAQ,IAAI,aAAAA,QAAM,KAAK,SAAS,IAAI,WAAW,EAAE,CAAC;AAAA,IACtD;AAEA,UAAM,aAAa,aAAa,OAAO,OAAK,CAAC,EAAE,OAAO;AACtD,QAAI,WAAW,SAAS,GAAG;AACvB,cAAQ,IAAI;AACZ,cAAQ,IAAI,KAAK,aAAAA,QAAM,OAAO,GAAG,WAAW,MAAM,+BAA+B,CAAC,EAAE;AAAA,IACxF;AAEA,YAAQ,IAAI;AAAA,EAChB,SAAS,OAAO;AACZ,mBAAe,KAAK,iBAAiB;AACrC,YAAQ,MAAM,aAAAA,QAAM,IAAI,YAAY,iBAAiB,QAAQ,MAAM,UAAU,eAAe,EAAE,CAAC;AAC/F,YAAQ,IAAI;AAAA,EAChB;AACJ;AAKA,eAAe,OAAsB;AACjC,2BACK,KAAK,cAAc,EACnB,YAAY,wEAAwE,EACpF,QAAQ,OAAO,EACf,SAAS,UAAU,gBAAgB,GAAG,EACtC,OAAO,cAAc,wBAAwB,EAC7C,OAAO,eAAe,uBAAuB,EAC7C,OAAO,eAAe,0BAA0B,EAChD,OAAO,cAAc,wBAAwB,EAC7C,OAAO,OAAO,YAAoB,YAAmE;AAClG,QAAI,QAAQ,MAAM;AACd,YAAM,SAAS,MAAM,KAAK,UAAU;AACpC,cAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC3C,cAAQ,KAAK,OAAO,UAAU,OAAO,OAAO,UAAU,MAAM,IAAI,CAAC;AACjE;AAAA,IACJ;AAEA,gBAAY;AAEZ,UAAM,cAAU,WAAAC,SAAI;AAAA,MAChB,MAAM;AAAA,MACN,OAAO;AAAA,IACX,CAAC,EAAE,MAAM;AAET,QAAI;AACA,YAAM,SAAS,MAAM,KAAK,UAAU;AAEpC,cAAQ,QAAQ,WAAW,OAAO,YAAY,QAAQ;AAEtD,UAAI,QAAQ,OAAO;AACf,cAAM,QAAQ,YAAY,OAAO,KAAK;AACtC,gBAAQ,IAAI;AAAA,WAAc,MAAM,MAAM,KAAK,OAAO,QAAQ,OAAO,CAAC;AAAA,CAAI;AACtE,gBAAQ,KAAK,OAAO,UAAU,OAAO,OAAO,UAAU,MAAM,IAAI,CAAC;AACjE;AAAA,MACJ;AAEA,iBAAW,MAAM;AACjB,kBAAY,OAAO,MAAM;AACzB,mBAAa,MAAM;AACnB,2BAAqB,OAAO,MAAM;AAGlC,UAAI,QAAQ,WAAW,SAAS,OAAO,OAAO,SAAS,GAAG;AACtD,cAAM,cAAc,QAAQ,UAAU;AAAA,MAC1C;AAEA,kBAAY;AAEZ,cAAQ,KAAK,OAAO,UAAU,OAAO,OAAO,UAAU,MAAM,IAAI,CAAC;AAAA,IACrE,SAAS,OAAO;AACZ,cAAQ,KAAK,aAAa;AAC1B,cAAQ,MAAM,aAAAD,QAAM,IAAI;AAAA,WAAc,iBAAiB,QAAQ,MAAM,UAAU,eAAe,EAAE,CAAC;AACjG,cAAQ,KAAK,CAAC;AAAA,IAClB;AAAA,EACJ,CAAC;AAEL,2BAAQ,MAAM;AAClB;AAEA,KAAK;","names":["fs","path","fs","path","chalk","ora"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/scanner/index.ts","../src/scanner/patterns.ts","../src/ai/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { program } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { confirm, password } from '@inquirer/prompts';\nimport { scan, type ScanResult, type ScanIssue } from './scanner/index.js';\nimport {\n getApiKey,\n setSessionApiKey,\n saveApiKey,\n validateApiKey,\n analyzeIssues,\n generateFixes,\n applyFixes,\n} from './ai/index.js';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nconst VERSION = '0.3.1';\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 recommendations\n */\nfunction printRecommendations(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 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\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 }\n if (hasInjection) {\n console.log(chalk.gray(' - Use parameterized queries for SQL'));\n }\n if (hasCORS) {\n console.log(chalk.gray(' - Configure CORS with specific allowed origins'));\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 * Load file contents for AI analysis\n */\nfunction loadFileContents(issues: ScanIssue[], basePath: string): Map<string, string> {\n const contents = new Map<string, string>();\n const uniqueFiles = [...new Set(issues.map(i => i.file))];\n\n for (const file of uniqueFiles) {\n try {\n const fullPath = path.resolve(basePath, file);\n const content = fs.readFileSync(fullPath, 'utf-8');\n contents.set(file, content);\n } catch {\n // Skip files that can't be read\n }\n }\n\n return contents;\n}\n\n/**\n * Prompt user for API key (hidden input)\n */\nasync function promptForApiKey(): Promise<string | undefined> {\n console.log();\n console.log(chalk.gray(' ─────────────────────────────────────────────'));\n console.log();\n console.log(` ${chalk.cyan.bold('Cencori Pro')}`);\n console.log(chalk.gray(' AI-powered auto-fix requires an API key.'));\n console.log();\n console.log(` Get your free API key at:`);\n console.log(` ${chalk.cyan('https://cencori.com/dashboard')} → API Keys`);\n console.log();\n\n try {\n const apiKey = await password({\n message: 'Enter your Cencori API key:',\n mask: '*',\n });\n\n if (!apiKey || apiKey.trim() === '') {\n console.log(chalk.yellow(' No API key entered. Skipping auto-fix.'));\n return undefined;\n }\n\n return apiKey.trim();\n } catch {\n return undefined;\n }\n}\n\n/**\n * Handle AI auto-fix flow\n */\nasync function handleAutoFix(\n result: ScanResult,\n targetPath: string\n): Promise<void> {\n if (result.issues.length === 0) return;\n\n console.log();\n\n // Ask user if they want to auto-fix\n const shouldFix = await confirm({\n message: 'Would you like Cencori to auto-fix these issues?',\n default: false,\n });\n\n if (!shouldFix) {\n console.log();\n console.log(chalk.gray(' Skipped auto-fix. Run again anytime to fix issues.'));\n console.log();\n return;\n }\n\n // Check if we have an API key\n let apiKey = getApiKey();\n\n if (!apiKey) {\n // Prompt for API key\n apiKey = await promptForApiKey();\n\n if (!apiKey) {\n console.log();\n return;\n }\n\n // Validate the API key\n const validatingSpinner = ora({\n text: 'Validating API key...',\n color: 'cyan',\n }).start();\n\n const isValid = await validateApiKey(apiKey);\n\n if (!isValid) {\n validatingSpinner.fail('Invalid API key');\n console.log(chalk.red(' The API key could not be validated. Please check and try again.'));\n console.log();\n return;\n }\n\n validatingSpinner.succeed('API key validated');\n\n // Save the API key for future use\n try {\n saveApiKey(apiKey);\n console.log(chalk.green(' ✔ API key saved to ~/.cencorirc'));\n } catch {\n // Non-fatal, just won't be saved\n }\n\n // Set for current session\n setSessionApiKey(apiKey);\n } else {\n console.log(chalk.gray(' Using saved API key...'));\n }\n\n // Load file contents\n const fileContents = loadFileContents(result.issues, targetPath);\n\n // Analyze with AI\n const analyzeSpinner = ora({\n text: 'Analyzing issues with AI...',\n color: 'cyan',\n }).start();\n\n try {\n const analysis = await analyzeIssues(result.issues, fileContents);\n\n // Filter out false positives\n const realIssues = analysis.filter(a => !a.isFalsePositive);\n const falsePositives = analysis.filter(a => a.isFalsePositive);\n\n if (falsePositives.length > 0) {\n analyzeSpinner.succeed(`${chalk.green(falsePositives.length)} false positives filtered`);\n } else {\n analyzeSpinner.succeed('Analysis complete');\n }\n\n if (realIssues.length === 0) {\n console.log(chalk.green(' All issues were false positives!'));\n return;\n }\n\n // Generate fixes\n const fixSpinner = ora({\n text: 'Generating fixes...',\n color: 'cyan',\n }).start();\n\n const fixes = await generateFixes(\n realIssues.map(a => a.issue),\n fileContents\n );\n\n fixSpinner.succeed(`Generated ${fixes.length} fixes`);\n\n // Apply fixes\n const applySpinner = ora({\n text: 'Applying fixes...',\n color: 'cyan',\n }).start();\n\n const appliedFixes = await applyFixes(fixes, fileContents);\n const appliedCount = appliedFixes.filter(f => f.applied).length;\n\n applySpinner.succeed(`Applied ${appliedCount}/${fixes.length} fixes`);\n\n // Show what was fixed\n console.log();\n console.log(` ${chalk.bold('Applied fixes:')}`);\n for (const fix of appliedFixes.filter(f => f.applied)) {\n console.log(chalk.green(` ✔ ${fix.issue.file}:${fix.issue.line}`));\n console.log(chalk.gray(` ${fix.explanation}`));\n }\n\n const notApplied = appliedFixes.filter(f => !f.applied);\n if (notApplied.length > 0) {\n console.log();\n console.log(` ${chalk.yellow(`${notApplied.length} issues require manual review`)}`);\n }\n\n console.log();\n } catch (error) {\n analyzeSpinner.fail('Auto-fix failed');\n console.error(chalk.red(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`));\n console.log();\n }\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-prompt', 'Skip interactive prompts')\n .option('--no-color', 'Disable colored output')\n .action(async (targetPath: string, options: { json?: boolean; quiet?: boolean; prompt?: boolean }) => {\n if (options.json) {\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 printRecommendations(result.issues);\n\n // Interactive auto-fix prompt (unless --no-prompt)\n if (options.prompt !== false && result.issues.length > 0) {\n await handleAutoFix(result, targetPath);\n }\n\n printFooter();\n\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","/**\n * AI-powered analysis and auto-fix module\n * Uses Cencori API for LLM intelligence\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport type { ScanIssue } from '../scanner/index.js';\n\nconst CENCORI_API_URL = 'https://api.cencori.com/v1';\nconst CONFIG_FILE = '.cencorirc';\n\nexport interface AnalysisResult {\n issue: ScanIssue;\n isFalsePositive: boolean;\n confidence: number;\n reason: string;\n}\n\nexport interface FixResult {\n issue: ScanIssue;\n originalCode: string;\n fixedCode: string;\n explanation: string;\n applied: boolean;\n}\n\n/**\n * Get the config file path\n */\nfunction getConfigPath(): string {\n return path.join(os.homedir(), CONFIG_FILE);\n}\n\n/**\n * Load API key from config file\n */\nfunction loadApiKeyFromConfig(): string | undefined {\n try {\n const configPath = getConfigPath();\n if (fs.existsSync(configPath)) {\n const content = fs.readFileSync(configPath, 'utf-8');\n const lines = content.split('\\n');\n for (const line of lines) {\n if (line.startsWith('api_key=')) {\n return line.slice('api_key='.length).trim();\n }\n }\n }\n } catch {\n // Ignore config read errors\n }\n return undefined;\n}\n\n/**\n * Save API key to config file\n */\nexport function saveApiKey(apiKey: string): void {\n const configPath = getConfigPath();\n fs.writeFileSync(configPath, `api_key=${apiKey}\\n`, { mode: 0o600 });\n}\n\n/**\n * Get API key (from env var, config file, or undefined)\n */\nexport function getApiKey(): string | undefined {\n // Priority: env var > config file\n return process.env.CENCORI_API_KEY || loadApiKeyFromConfig();\n}\n\n/**\n * Set API key for current session (used after prompting user)\n */\nlet sessionApiKey: string | undefined;\n\nexport function setSessionApiKey(apiKey: string): void {\n sessionApiKey = apiKey;\n}\n\n/**\n * Get API key including session key\n */\nfunction getEffectiveApiKey(): string | undefined {\n return sessionApiKey || getApiKey();\n}\n\n/**\n * Check if AI features are available\n */\nexport function isAIAvailable(): boolean {\n return !!getEffectiveApiKey();\n}\n\n/**\n * Validate API key by making a test request\n */\nexport async function validateApiKey(apiKey: string): Promise<boolean> {\n try {\n const response = await fetch(`${CENCORI_API_URL}/models`, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${apiKey}`,\n },\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n\n/**\n * Analyze issues with AI to filter false positives\n */\nexport async function analyzeIssues(\n issues: ScanIssue[],\n fileContents: Map<string, string>\n): Promise<AnalysisResult[]> {\n const apiKey = getEffectiveApiKey();\n if (!apiKey) {\n throw new Error('No API key available');\n }\n\n const results: AnalysisResult[] = [];\n\n for (const issue of issues) {\n const content = fileContents.get(issue.file) || '';\n const lines = content.split('\\n');\n const startLine = Math.max(0, issue.line - 3);\n const endLine = Math.min(lines.length, issue.line + 3);\n const context = lines.slice(startLine, endLine).join('\\n');\n\n try {\n const response = await fetch(`${CENCORI_API_URL}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'gpt-4o-mini',\n messages: [\n {\n role: 'system',\n content: `You are a security analyst. Analyze code findings and determine if they are real security issues or false positives. Respond in JSON format: {\"isFalsePositive\": boolean, \"confidence\": number (0-100), \"reason\": \"brief explanation\"}`,\n },\n {\n role: 'user',\n content: `Analyze this security finding:\nType: ${issue.type}\nName: ${issue.name}\nMatch: ${issue.match}\nFile: ${issue.file}:${issue.line}\nContext:\n\\`\\`\\`\n${context}\n\\`\\`\\`\n\nIs this a real security issue or a false positive (e.g., test data, example code, documentation)?`,\n },\n ],\n temperature: 0,\n max_tokens: 150,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status}`);\n }\n\n const data = await response.json() as {\n choices: Array<{ message: { content: string } }>;\n };\n const content_response = data.choices[0]?.message?.content || '{}';\n\n // Parse JSON response\n const parsed = JSON.parse(content_response);\n results.push({\n issue,\n isFalsePositive: parsed.isFalsePositive || false,\n confidence: parsed.confidence || 50,\n reason: parsed.reason || 'Unable to analyze',\n });\n } catch {\n // If analysis fails, assume it's a real issue\n results.push({\n issue,\n isFalsePositive: false,\n confidence: 50,\n reason: 'Analysis failed - treating as potential issue',\n });\n }\n }\n\n return results;\n}\n\n/**\n * Generate fixes for issues using AI\n */\nexport async function generateFixes(\n issues: ScanIssue[],\n fileContents: Map<string, string>\n): Promise<FixResult[]> {\n const apiKey = getEffectiveApiKey();\n if (!apiKey) {\n throw new Error('No API key available');\n }\n\n const results: FixResult[] = [];\n\n for (const issue of issues) {\n const content = fileContents.get(issue.file) || '';\n const lines = content.split('\\n');\n const startLine = Math.max(0, issue.line - 5);\n const endLine = Math.min(lines.length, issue.line + 5);\n const codeSnippet = lines.slice(startLine, endLine).join('\\n');\n\n try {\n const response = await fetch(`${CENCORI_API_URL}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'gpt-4o-mini',\n messages: [\n {\n role: 'system',\n content: `You are a security engineer. Generate secure code fixes. For secrets, use environment variables. For XSS, use sanitization. Respond in JSON: {\"fixedCode\": \"the fixed code snippet\", \"explanation\": \"what was changed\"}`,\n },\n {\n role: 'user',\n content: `Fix this security issue:\nType: ${issue.type}\nName: ${issue.name}\nFile: ${issue.file}:${issue.line}\n\nCode to fix:\n\\`\\`\\`\n${codeSnippet}\n\\`\\`\\`\n\nGenerate a secure fix.`,\n },\n ],\n temperature: 0,\n max_tokens: 500,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status}`);\n }\n\n const data = await response.json() as {\n choices: Array<{ message: { content: string } }>;\n };\n const content_response = data.choices[0]?.message?.content || '{}';\n\n const parsed = JSON.parse(content_response);\n results.push({\n issue,\n originalCode: codeSnippet,\n fixedCode: parsed.fixedCode || codeSnippet,\n explanation: parsed.explanation || 'No explanation provided',\n applied: false,\n });\n } catch {\n results.push({\n issue,\n originalCode: codeSnippet,\n fixedCode: codeSnippet,\n explanation: 'Unable to generate fix - manual review required',\n applied: false,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Apply fixes to files\n */\nexport async function applyFixes(\n fixes: FixResult[],\n fileContents: Map<string, string>\n): Promise<FixResult[]> {\n for (const fix of fixes) {\n if (fix.fixedCode === fix.originalCode) {\n continue;\n }\n\n const content = fileContents.get(fix.issue.file);\n if (!content) {\n continue;\n }\n\n // Replace the original code with the fixed code\n const newContent = content.replace(fix.originalCode, fix.fixedCode);\n\n if (newContent !== content) {\n const filePath = path.resolve(fix.issue.file);\n fs.writeFileSync(filePath, newContent, 'utf-8');\n fix.applied = true;\n }\n }\n\n return fixes;\n}\n"],"mappings":"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","names":["fs","path","fs","path","chalk","ora"]}
package/dist/cli.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  import { program } from "commander";
5
5
  import chalk from "chalk";
6
6
  import ora from "ora";
7
- import { confirm } from "@inquirer/prompts";
7
+ import { confirm, password } from "@inquirer/prompts";
8
8
 
9
9
  // src/scanner/index.ts
10
10
  import * as fs from "fs";
@@ -693,17 +693,62 @@ async function scan(targetPath) {
693
693
  }
694
694
 
695
695
  // src/ai/index.ts
696
+ import * as fs2 from "fs";
697
+ import * as path2 from "path";
698
+ import * as os from "os";
696
699
  var CENCORI_API_URL = "https://api.cencori.com/v1";
700
+ var CONFIG_FILE = ".cencorirc";
701
+ function getConfigPath() {
702
+ return path2.join(os.homedir(), CONFIG_FILE);
703
+ }
704
+ function loadApiKeyFromConfig() {
705
+ try {
706
+ const configPath = getConfigPath();
707
+ if (fs2.existsSync(configPath)) {
708
+ const content = fs2.readFileSync(configPath, "utf-8");
709
+ const lines = content.split("\n");
710
+ for (const line of lines) {
711
+ if (line.startsWith("api_key=")) {
712
+ return line.slice("api_key=".length).trim();
713
+ }
714
+ }
715
+ }
716
+ } catch {
717
+ }
718
+ return void 0;
719
+ }
720
+ function saveApiKey(apiKey) {
721
+ const configPath = getConfigPath();
722
+ fs2.writeFileSync(configPath, `api_key=${apiKey}
723
+ `, { mode: 384 });
724
+ }
697
725
  function getApiKey() {
698
- return process.env.CENCORI_API_KEY;
726
+ return process.env.CENCORI_API_KEY || loadApiKeyFromConfig();
727
+ }
728
+ var sessionApiKey;
729
+ function setSessionApiKey(apiKey) {
730
+ sessionApiKey = apiKey;
731
+ }
732
+ function getEffectiveApiKey() {
733
+ return sessionApiKey || getApiKey();
699
734
  }
700
- function isAIAvailable() {
701
- return !!getApiKey();
735
+ async function validateApiKey(apiKey) {
736
+ try {
737
+ const response = await fetch(`${CENCORI_API_URL}/models`, {
738
+ method: "GET",
739
+ headers: {
740
+ "Authorization": `Bearer ${apiKey}`
741
+ }
742
+ });
743
+ return response.ok;
744
+ } catch {
745
+ return false;
746
+ }
702
747
  }
703
748
  async function analyzeIssues(issues, fileContents) {
704
- const apiKey = getApiKey();
749
+ const apiKey = getEffectiveApiKey();
705
750
  if (!apiKey) {
706
- throw new Error("CENCORI_API_KEY not set");
751
+ throw new Error("No API key available");
707
752
  }
708
753
  const results = [];
709
754
  for (const issue of issues) {
@@ -769,9 +814,9 @@ Is this a real security issue or a false positive (e.g., test data, example code
769
814
  return results;
770
815
  }
771
816
  async function generateFixes(issues, fileContents) {
772
- const apiKey = getApiKey();
817
+ const apiKey = getEffectiveApiKey();
773
818
  if (!apiKey) {
774
- throw new Error("CENCORI_API_KEY not set");
819
+ throw new Error("No API key available");
775
820
  }
776
821
  const results = [];
777
822
  for (const issue of issues) {
@@ -839,8 +884,6 @@ Generate a secure fix.`
839
884
  return results;
840
885
  }
841
886
  async function applyFixes(fixes, fileContents) {
842
- const fs3 = await import("fs");
843
- const path3 = await import("path");
844
887
  for (const fix of fixes) {
845
888
  if (fix.fixedCode === fix.originalCode) {
846
889
  continue;
@@ -851,8 +894,8 @@ async function applyFixes(fixes, fileContents) {
851
894
  }
852
895
  const newContent = content.replace(fix.originalCode, fix.fixedCode);
853
896
  if (newContent !== content) {
854
- const filePath = path3.resolve(fix.issue.file);
855
- fs3.writeFileSync(filePath, newContent, "utf-8");
897
+ const filePath = path2.resolve(fix.issue.file);
898
+ fs2.writeFileSync(filePath, newContent, "utf-8");
856
899
  fix.applied = true;
857
900
  }
858
901
  }
@@ -860,9 +903,9 @@ async function applyFixes(fixes, fileContents) {
860
903
  }
861
904
 
862
905
  // src/cli.ts
863
- import * as fs2 from "fs";
864
- import * as path2 from "path";
865
- var VERSION = "0.3.0";
906
+ import * as fs3 from "fs";
907
+ import * as path3 from "path";
908
+ var VERSION = "0.3.1";
866
909
  var scoreStyles = {
867
910
  A: { color: chalk.green },
868
911
  B: { color: chalk.blue },
@@ -985,17 +1028,6 @@ function printRecommendations(issues) {
985
1028
  }
986
1029
  console.log();
987
1030
  }
988
- function printAIUpsell() {
989
- console.log(chalk.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
990
- console.log();
991
- console.log(` ${chalk.cyan.bold("Upgrade to Cencori Pro")}`);
992
- console.log(chalk.gray(" Get AI-powered auto-fix for all issues:"));
993
- console.log();
994
- console.log(` 1. Get your API key at ${chalk.cyan("https://cencori.com/dashboard")}`);
995
- console.log(` 2. Run: ${chalk.cyan("export CENCORI_API_KEY=your_key")}`);
996
- console.log(` 3. Scan again to unlock AI auto-fix`);
997
- console.log();
998
- }
999
1031
  function printFooter() {
1000
1032
  console.log(chalk.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1001
1033
  console.log();
@@ -1008,21 +1040,41 @@ function loadFileContents(issues, basePath) {
1008
1040
  const uniqueFiles = [...new Set(issues.map((i) => i.file))];
1009
1041
  for (const file of uniqueFiles) {
1010
1042
  try {
1011
- const fullPath = path2.resolve(basePath, file);
1012
- const content = fs2.readFileSync(fullPath, "utf-8");
1043
+ const fullPath = path3.resolve(basePath, file);
1044
+ const content = fs3.readFileSync(fullPath, "utf-8");
1013
1045
  contents.set(file, content);
1014
1046
  } catch {
1015
1047
  }
1016
1048
  }
1017
1049
  return contents;
1018
1050
  }
1051
+ async function promptForApiKey() {
1052
+ console.log();
1053
+ console.log(chalk.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1054
+ console.log();
1055
+ console.log(` ${chalk.cyan.bold("Cencori Pro")}`);
1056
+ console.log(chalk.gray(" AI-powered auto-fix requires an API key."));
1057
+ console.log();
1058
+ console.log(` Get your free API key at:`);
1059
+ console.log(` ${chalk.cyan("https://cencori.com/dashboard")} \u2192 API Keys`);
1060
+ console.log();
1061
+ try {
1062
+ const apiKey = await password({
1063
+ message: "Enter your Cencori API key:",
1064
+ mask: "*"
1065
+ });
1066
+ if (!apiKey || apiKey.trim() === "") {
1067
+ console.log(chalk.yellow(" No API key entered. Skipping auto-fix."));
1068
+ return void 0;
1069
+ }
1070
+ return apiKey.trim();
1071
+ } catch {
1072
+ return void 0;
1073
+ }
1074
+ }
1019
1075
  async function handleAutoFix(result, targetPath) {
1020
1076
  if (result.issues.length === 0) return;
1021
1077
  console.log();
1022
- if (!isAIAvailable()) {
1023
- printAIUpsell();
1024
- return;
1025
- }
1026
1078
  const shouldFix = await confirm({
1027
1079
  message: "Would you like Cencori to auto-fix these issues?",
1028
1080
  default: false
@@ -1033,6 +1085,34 @@ async function handleAutoFix(result, targetPath) {
1033
1085
  console.log();
1034
1086
  return;
1035
1087
  }
1088
+ let apiKey = getApiKey();
1089
+ if (!apiKey) {
1090
+ apiKey = await promptForApiKey();
1091
+ if (!apiKey) {
1092
+ console.log();
1093
+ return;
1094
+ }
1095
+ const validatingSpinner = ora({
1096
+ text: "Validating API key...",
1097
+ color: "cyan"
1098
+ }).start();
1099
+ const isValid = await validateApiKey(apiKey);
1100
+ if (!isValid) {
1101
+ validatingSpinner.fail("Invalid API key");
1102
+ console.log(chalk.red(" The API key could not be validated. Please check and try again."));
1103
+ console.log();
1104
+ return;
1105
+ }
1106
+ validatingSpinner.succeed("API key validated");
1107
+ try {
1108
+ saveApiKey(apiKey);
1109
+ console.log(chalk.green(" \u2714 API key saved to ~/.cencorirc"));
1110
+ } catch {
1111
+ }
1112
+ setSessionApiKey(apiKey);
1113
+ } else {
1114
+ console.log(chalk.gray(" Using saved API key..."));
1115
+ }
1036
1116
  const fileContents = loadFileContents(result.issues, targetPath);
1037
1117
  const analyzeSpinner = ora({
1038
1118
  text: "Analyzing issues with AI...",
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/scanner/index.ts","../src/scanner/patterns.ts","../src/ai/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { program } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { confirm } from '@inquirer/prompts';\nimport { scan, type ScanResult, type ScanIssue } from './scanner/index.js';\nimport { isAIAvailable, analyzeIssues, generateFixes, applyFixes } from './ai/index.js';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nconst VERSION = '0.3.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 recommendations\n */\nfunction printRecommendations(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 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\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 }\n if (hasInjection) {\n console.log(chalk.gray(' - Use parameterized queries for SQL'));\n }\n if (hasCORS) {\n console.log(chalk.gray(' - Configure CORS with specific allowed origins'));\n }\n\n console.log();\n}\n\n/**\n * Print upsell for AI features\n */\nfunction printAIUpsell(): void {\n console.log(chalk.gray(' ─────────────────────────────────────────────'));\n console.log();\n console.log(` ${chalk.cyan.bold('Upgrade to Cencori Pro')}`);\n console.log(chalk.gray(' Get AI-powered auto-fix for all issues:'));\n console.log();\n console.log(` 1. Get your API key at ${chalk.cyan('https://cencori.com/dashboard')}`);\n console.log(` 2. Run: ${chalk.cyan('export CENCORI_API_KEY=your_key')}`);\n console.log(` 3. Scan again to unlock AI auto-fix`);\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 * Load file contents for AI analysis\n */\nfunction loadFileContents(issues: ScanIssue[], basePath: string): Map<string, string> {\n const contents = new Map<string, string>();\n const uniqueFiles = [...new Set(issues.map(i => i.file))];\n\n for (const file of uniqueFiles) {\n try {\n const fullPath = path.resolve(basePath, file);\n const content = fs.readFileSync(fullPath, 'utf-8');\n contents.set(file, content);\n } catch {\n // Skip files that can't be read\n }\n }\n\n return contents;\n}\n\n/**\n * Handle AI auto-fix flow\n */\nasync function handleAutoFix(\n result: ScanResult,\n targetPath: string\n): Promise<void> {\n if (result.issues.length === 0) return;\n\n console.log();\n\n // Check if AI is available\n if (!isAIAvailable()) {\n printAIUpsell();\n return;\n }\n\n // Ask user if they want to auto-fix\n const shouldFix = await confirm({\n message: 'Would you like Cencori to auto-fix these issues?',\n default: false,\n });\n\n if (!shouldFix) {\n console.log();\n console.log(chalk.gray(' Skipped auto-fix. Run again anytime to fix issues.'));\n console.log();\n return;\n }\n\n // Load file contents\n const fileContents = loadFileContents(result.issues, targetPath);\n\n // Analyze with AI\n const analyzeSpinner = ora({\n text: 'Analyzing issues with AI...',\n color: 'cyan',\n }).start();\n\n try {\n const analysis = await analyzeIssues(result.issues, fileContents);\n\n // Filter out false positives\n const realIssues = analysis.filter(a => !a.isFalsePositive);\n const falsePositives = analysis.filter(a => a.isFalsePositive);\n\n if (falsePositives.length > 0) {\n analyzeSpinner.succeed(`${chalk.green(falsePositives.length)} false positives filtered`);\n } else {\n analyzeSpinner.succeed('Analysis complete');\n }\n\n if (realIssues.length === 0) {\n console.log(chalk.green(' All issues were false positives!'));\n return;\n }\n\n // Generate fixes\n const fixSpinner = ora({\n text: 'Generating fixes...',\n color: 'cyan',\n }).start();\n\n const fixes = await generateFixes(\n realIssues.map(a => a.issue),\n fileContents\n );\n\n fixSpinner.succeed(`Generated ${fixes.length} fixes`);\n\n // Apply fixes\n const applySpinner = ora({\n text: 'Applying fixes...',\n color: 'cyan',\n }).start();\n\n const appliedFixes = await applyFixes(fixes, fileContents);\n const appliedCount = appliedFixes.filter(f => f.applied).length;\n\n applySpinner.succeed(`Applied ${appliedCount}/${fixes.length} fixes`);\n\n // Show what was fixed\n console.log();\n console.log(` ${chalk.bold('Applied fixes:')}`);\n for (const fix of appliedFixes.filter(f => f.applied)) {\n console.log(chalk.green(` ✔ ${fix.issue.file}:${fix.issue.line}`));\n console.log(chalk.gray(` ${fix.explanation}`));\n }\n\n const notApplied = appliedFixes.filter(f => !f.applied);\n if (notApplied.length > 0) {\n console.log();\n console.log(` ${chalk.yellow(`${notApplied.length} issues require manual review`)}`);\n }\n\n console.log();\n } catch (error) {\n analyzeSpinner.fail('Auto-fix failed');\n console.error(chalk.red(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`));\n console.log();\n }\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-prompt', 'Skip interactive prompts')\n .option('--no-color', 'Disable colored output')\n .action(async (targetPath: string, options: { json?: boolean; quiet?: boolean; prompt?: boolean }) => {\n if (options.json) {\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 printRecommendations(result.issues);\n\n // Interactive auto-fix prompt (unless --no-prompt)\n if (options.prompt !== false && result.issues.length > 0) {\n await handleAutoFix(result, targetPath);\n }\n\n printFooter();\n\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","/**\n * AI-powered analysis and auto-fix module\n * Uses Cencori API for LLM intelligence\n */\n\nimport type { ScanIssue } from '../scanner/index.js';\n\nconst CENCORI_API_URL = 'https://api.cencori.com/v1';\n\nexport interface AnalysisResult {\n issue: ScanIssue;\n isFalsePositive: boolean;\n confidence: number;\n reason: string;\n}\n\nexport interface FixResult {\n issue: ScanIssue;\n originalCode: string;\n fixedCode: string;\n explanation: string;\n applied: boolean;\n}\n\n/**\n * Check if API key is available\n */\nexport function getApiKey(): string | undefined {\n return process.env.CENCORI_API_KEY;\n}\n\n/**\n * Check if AI features are available\n */\nexport function isAIAvailable(): boolean {\n return !!getApiKey();\n}\n\n/**\n * Analyze issues with AI to filter false positives\n */\nexport async function analyzeIssues(\n issues: ScanIssue[],\n fileContents: Map<string, string>\n): Promise<AnalysisResult[]> {\n const apiKey = getApiKey();\n if (!apiKey) {\n throw new Error('CENCORI_API_KEY not set');\n }\n\n const results: AnalysisResult[] = [];\n\n for (const issue of issues) {\n const content = fileContents.get(issue.file) || '';\n const lines = content.split('\\n');\n const startLine = Math.max(0, issue.line - 3);\n const endLine = Math.min(lines.length, issue.line + 3);\n const context = lines.slice(startLine, endLine).join('\\n');\n\n try {\n const response = await fetch(`${CENCORI_API_URL}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'gpt-4o-mini',\n messages: [\n {\n role: 'system',\n content: `You are a security analyst. Analyze code findings and determine if they are real security issues or false positives. Respond in JSON format: {\"isFalsePositive\": boolean, \"confidence\": number (0-100), \"reason\": \"brief explanation\"}`,\n },\n {\n role: 'user',\n content: `Analyze this security finding:\nType: ${issue.type}\nName: ${issue.name}\nMatch: ${issue.match}\nFile: ${issue.file}:${issue.line}\nContext:\n\\`\\`\\`\n${context}\n\\`\\`\\`\n\nIs this a real security issue or a false positive (e.g., test data, example code, documentation)?`,\n },\n ],\n temperature: 0,\n max_tokens: 150,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status}`);\n }\n\n const data = await response.json() as {\n choices: Array<{ message: { content: string } }>;\n };\n const content_response = data.choices[0]?.message?.content || '{}';\n\n // Parse JSON response\n const parsed = JSON.parse(content_response);\n results.push({\n issue,\n isFalsePositive: parsed.isFalsePositive || false,\n confidence: parsed.confidence || 50,\n reason: parsed.reason || 'Unable to analyze',\n });\n } catch {\n // If analysis fails, assume it's a real issue\n results.push({\n issue,\n isFalsePositive: false,\n confidence: 50,\n reason: 'Analysis failed - treating as potential issue',\n });\n }\n }\n\n return results;\n}\n\n/**\n * Generate fixes for issues using AI\n */\nexport async function generateFixes(\n issues: ScanIssue[],\n fileContents: Map<string, string>\n): Promise<FixResult[]> {\n const apiKey = getApiKey();\n if (!apiKey) {\n throw new Error('CENCORI_API_KEY not set');\n }\n\n const results: FixResult[] = [];\n\n for (const issue of issues) {\n const content = fileContents.get(issue.file) || '';\n const lines = content.split('\\n');\n const startLine = Math.max(0, issue.line - 5);\n const endLine = Math.min(lines.length, issue.line + 5);\n const codeSnippet = lines.slice(startLine, endLine).join('\\n');\n\n try {\n const response = await fetch(`${CENCORI_API_URL}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'gpt-4o-mini',\n messages: [\n {\n role: 'system',\n content: `You are a security engineer. Generate secure code fixes. For secrets, use environment variables. For XSS, use sanitization. Respond in JSON: {\"fixedCode\": \"the fixed code snippet\", \"explanation\": \"what was changed\"}`,\n },\n {\n role: 'user',\n content: `Fix this security issue:\nType: ${issue.type}\nName: ${issue.name}\nFile: ${issue.file}:${issue.line}\n\nCode to fix:\n\\`\\`\\`\n${codeSnippet}\n\\`\\`\\`\n\nGenerate a secure fix.`,\n },\n ],\n temperature: 0,\n max_tokens: 500,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status}`);\n }\n\n const data = await response.json() as {\n choices: Array<{ message: { content: string } }>;\n };\n const content_response = data.choices[0]?.message?.content || '{}';\n\n const parsed = JSON.parse(content_response);\n results.push({\n issue,\n originalCode: codeSnippet,\n fixedCode: parsed.fixedCode || codeSnippet,\n explanation: parsed.explanation || 'No explanation provided',\n applied: false,\n });\n } catch {\n results.push({\n issue,\n originalCode: codeSnippet,\n fixedCode: codeSnippet,\n explanation: 'Unable to generate fix - manual review required',\n applied: false,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Apply fixes to files\n */\nexport async function applyFixes(\n fixes: FixResult[],\n fileContents: Map<string, string>\n): Promise<FixResult[]> {\n const fs = await import('fs');\n const path = await import('path');\n\n for (const fix of fixes) {\n if (fix.fixedCode === fix.originalCode) {\n continue;\n }\n\n const content = fileContents.get(fix.issue.file);\n if (!content) {\n continue;\n }\n\n // Replace the original code with the fixed code\n const newContent = content.replace(fix.originalCode, fix.fixedCode);\n\n if (newContent !== content) {\n const filePath = path.resolve(fix.issue.file);\n fs.writeFileSync(filePath, newContent, 'utf-8');\n fix.applied = true;\n }\n }\n\n return fixes;\n}\n"],"mappings":"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","names":["fs","path","fs","path"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/scanner/index.ts","../src/scanner/patterns.ts","../src/ai/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { program } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { confirm, password } from '@inquirer/prompts';\nimport { scan, type ScanResult, type ScanIssue } from './scanner/index.js';\nimport {\n getApiKey,\n setSessionApiKey,\n saveApiKey,\n validateApiKey,\n analyzeIssues,\n generateFixes,\n applyFixes,\n} from './ai/index.js';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nconst VERSION = '0.3.1';\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 recommendations\n */\nfunction printRecommendations(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 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\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 }\n if (hasInjection) {\n console.log(chalk.gray(' - Use parameterized queries for SQL'));\n }\n if (hasCORS) {\n console.log(chalk.gray(' - Configure CORS with specific allowed origins'));\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 * Load file contents for AI analysis\n */\nfunction loadFileContents(issues: ScanIssue[], basePath: string): Map<string, string> {\n const contents = new Map<string, string>();\n const uniqueFiles = [...new Set(issues.map(i => i.file))];\n\n for (const file of uniqueFiles) {\n try {\n const fullPath = path.resolve(basePath, file);\n const content = fs.readFileSync(fullPath, 'utf-8');\n contents.set(file, content);\n } catch {\n // Skip files that can't be read\n }\n }\n\n return contents;\n}\n\n/**\n * Prompt user for API key (hidden input)\n */\nasync function promptForApiKey(): Promise<string | undefined> {\n console.log();\n console.log(chalk.gray(' ─────────────────────────────────────────────'));\n console.log();\n console.log(` ${chalk.cyan.bold('Cencori Pro')}`);\n console.log(chalk.gray(' AI-powered auto-fix requires an API key.'));\n console.log();\n console.log(` Get your free API key at:`);\n console.log(` ${chalk.cyan('https://cencori.com/dashboard')} → API Keys`);\n console.log();\n\n try {\n const apiKey = await password({\n message: 'Enter your Cencori API key:',\n mask: '*',\n });\n\n if (!apiKey || apiKey.trim() === '') {\n console.log(chalk.yellow(' No API key entered. Skipping auto-fix.'));\n return undefined;\n }\n\n return apiKey.trim();\n } catch {\n return undefined;\n }\n}\n\n/**\n * Handle AI auto-fix flow\n */\nasync function handleAutoFix(\n result: ScanResult,\n targetPath: string\n): Promise<void> {\n if (result.issues.length === 0) return;\n\n console.log();\n\n // Ask user if they want to auto-fix\n const shouldFix = await confirm({\n message: 'Would you like Cencori to auto-fix these issues?',\n default: false,\n });\n\n if (!shouldFix) {\n console.log();\n console.log(chalk.gray(' Skipped auto-fix. Run again anytime to fix issues.'));\n console.log();\n return;\n }\n\n // Check if we have an API key\n let apiKey = getApiKey();\n\n if (!apiKey) {\n // Prompt for API key\n apiKey = await promptForApiKey();\n\n if (!apiKey) {\n console.log();\n return;\n }\n\n // Validate the API key\n const validatingSpinner = ora({\n text: 'Validating API key...',\n color: 'cyan',\n }).start();\n\n const isValid = await validateApiKey(apiKey);\n\n if (!isValid) {\n validatingSpinner.fail('Invalid API key');\n console.log(chalk.red(' The API key could not be validated. Please check and try again.'));\n console.log();\n return;\n }\n\n validatingSpinner.succeed('API key validated');\n\n // Save the API key for future use\n try {\n saveApiKey(apiKey);\n console.log(chalk.green(' ✔ API key saved to ~/.cencorirc'));\n } catch {\n // Non-fatal, just won't be saved\n }\n\n // Set for current session\n setSessionApiKey(apiKey);\n } else {\n console.log(chalk.gray(' Using saved API key...'));\n }\n\n // Load file contents\n const fileContents = loadFileContents(result.issues, targetPath);\n\n // Analyze with AI\n const analyzeSpinner = ora({\n text: 'Analyzing issues with AI...',\n color: 'cyan',\n }).start();\n\n try {\n const analysis = await analyzeIssues(result.issues, fileContents);\n\n // Filter out false positives\n const realIssues = analysis.filter(a => !a.isFalsePositive);\n const falsePositives = analysis.filter(a => a.isFalsePositive);\n\n if (falsePositives.length > 0) {\n analyzeSpinner.succeed(`${chalk.green(falsePositives.length)} false positives filtered`);\n } else {\n analyzeSpinner.succeed('Analysis complete');\n }\n\n if (realIssues.length === 0) {\n console.log(chalk.green(' All issues were false positives!'));\n return;\n }\n\n // Generate fixes\n const fixSpinner = ora({\n text: 'Generating fixes...',\n color: 'cyan',\n }).start();\n\n const fixes = await generateFixes(\n realIssues.map(a => a.issue),\n fileContents\n );\n\n fixSpinner.succeed(`Generated ${fixes.length} fixes`);\n\n // Apply fixes\n const applySpinner = ora({\n text: 'Applying fixes...',\n color: 'cyan',\n }).start();\n\n const appliedFixes = await applyFixes(fixes, fileContents);\n const appliedCount = appliedFixes.filter(f => f.applied).length;\n\n applySpinner.succeed(`Applied ${appliedCount}/${fixes.length} fixes`);\n\n // Show what was fixed\n console.log();\n console.log(` ${chalk.bold('Applied fixes:')}`);\n for (const fix of appliedFixes.filter(f => f.applied)) {\n console.log(chalk.green(` ✔ ${fix.issue.file}:${fix.issue.line}`));\n console.log(chalk.gray(` ${fix.explanation}`));\n }\n\n const notApplied = appliedFixes.filter(f => !f.applied);\n if (notApplied.length > 0) {\n console.log();\n console.log(` ${chalk.yellow(`${notApplied.length} issues require manual review`)}`);\n }\n\n console.log();\n } catch (error) {\n analyzeSpinner.fail('Auto-fix failed');\n console.error(chalk.red(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`));\n console.log();\n }\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-prompt', 'Skip interactive prompts')\n .option('--no-color', 'Disable colored output')\n .action(async (targetPath: string, options: { json?: boolean; quiet?: boolean; prompt?: boolean }) => {\n if (options.json) {\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 printRecommendations(result.issues);\n\n // Interactive auto-fix prompt (unless --no-prompt)\n if (options.prompt !== false && result.issues.length > 0) {\n await handleAutoFix(result, targetPath);\n }\n\n printFooter();\n\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","/**\n * AI-powered analysis and auto-fix module\n * Uses Cencori API for LLM intelligence\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport type { ScanIssue } from '../scanner/index.js';\n\nconst CENCORI_API_URL = 'https://api.cencori.com/v1';\nconst CONFIG_FILE = '.cencorirc';\n\nexport interface AnalysisResult {\n issue: ScanIssue;\n isFalsePositive: boolean;\n confidence: number;\n reason: string;\n}\n\nexport interface FixResult {\n issue: ScanIssue;\n originalCode: string;\n fixedCode: string;\n explanation: string;\n applied: boolean;\n}\n\n/**\n * Get the config file path\n */\nfunction getConfigPath(): string {\n return path.join(os.homedir(), CONFIG_FILE);\n}\n\n/**\n * Load API key from config file\n */\nfunction loadApiKeyFromConfig(): string | undefined {\n try {\n const configPath = getConfigPath();\n if (fs.existsSync(configPath)) {\n const content = fs.readFileSync(configPath, 'utf-8');\n const lines = content.split('\\n');\n for (const line of lines) {\n if (line.startsWith('api_key=')) {\n return line.slice('api_key='.length).trim();\n }\n }\n }\n } catch {\n // Ignore config read errors\n }\n return undefined;\n}\n\n/**\n * Save API key to config file\n */\nexport function saveApiKey(apiKey: string): void {\n const configPath = getConfigPath();\n fs.writeFileSync(configPath, `api_key=${apiKey}\\n`, { mode: 0o600 });\n}\n\n/**\n * Get API key (from env var, config file, or undefined)\n */\nexport function getApiKey(): string | undefined {\n // Priority: env var > config file\n return process.env.CENCORI_API_KEY || loadApiKeyFromConfig();\n}\n\n/**\n * Set API key for current session (used after prompting user)\n */\nlet sessionApiKey: string | undefined;\n\nexport function setSessionApiKey(apiKey: string): void {\n sessionApiKey = apiKey;\n}\n\n/**\n * Get API key including session key\n */\nfunction getEffectiveApiKey(): string | undefined {\n return sessionApiKey || getApiKey();\n}\n\n/**\n * Check if AI features are available\n */\nexport function isAIAvailable(): boolean {\n return !!getEffectiveApiKey();\n}\n\n/**\n * Validate API key by making a test request\n */\nexport async function validateApiKey(apiKey: string): Promise<boolean> {\n try {\n const response = await fetch(`${CENCORI_API_URL}/models`, {\n method: 'GET',\n headers: {\n 'Authorization': `Bearer ${apiKey}`,\n },\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n\n/**\n * Analyze issues with AI to filter false positives\n */\nexport async function analyzeIssues(\n issues: ScanIssue[],\n fileContents: Map<string, string>\n): Promise<AnalysisResult[]> {\n const apiKey = getEffectiveApiKey();\n if (!apiKey) {\n throw new Error('No API key available');\n }\n\n const results: AnalysisResult[] = [];\n\n for (const issue of issues) {\n const content = fileContents.get(issue.file) || '';\n const lines = content.split('\\n');\n const startLine = Math.max(0, issue.line - 3);\n const endLine = Math.min(lines.length, issue.line + 3);\n const context = lines.slice(startLine, endLine).join('\\n');\n\n try {\n const response = await fetch(`${CENCORI_API_URL}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'gpt-4o-mini',\n messages: [\n {\n role: 'system',\n content: `You are a security analyst. Analyze code findings and determine if they are real security issues or false positives. Respond in JSON format: {\"isFalsePositive\": boolean, \"confidence\": number (0-100), \"reason\": \"brief explanation\"}`,\n },\n {\n role: 'user',\n content: `Analyze this security finding:\nType: ${issue.type}\nName: ${issue.name}\nMatch: ${issue.match}\nFile: ${issue.file}:${issue.line}\nContext:\n\\`\\`\\`\n${context}\n\\`\\`\\`\n\nIs this a real security issue or a false positive (e.g., test data, example code, documentation)?`,\n },\n ],\n temperature: 0,\n max_tokens: 150,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status}`);\n }\n\n const data = await response.json() as {\n choices: Array<{ message: { content: string } }>;\n };\n const content_response = data.choices[0]?.message?.content || '{}';\n\n // Parse JSON response\n const parsed = JSON.parse(content_response);\n results.push({\n issue,\n isFalsePositive: parsed.isFalsePositive || false,\n confidence: parsed.confidence || 50,\n reason: parsed.reason || 'Unable to analyze',\n });\n } catch {\n // If analysis fails, assume it's a real issue\n results.push({\n issue,\n isFalsePositive: false,\n confidence: 50,\n reason: 'Analysis failed - treating as potential issue',\n });\n }\n }\n\n return results;\n}\n\n/**\n * Generate fixes for issues using AI\n */\nexport async function generateFixes(\n issues: ScanIssue[],\n fileContents: Map<string, string>\n): Promise<FixResult[]> {\n const apiKey = getEffectiveApiKey();\n if (!apiKey) {\n throw new Error('No API key available');\n }\n\n const results: FixResult[] = [];\n\n for (const issue of issues) {\n const content = fileContents.get(issue.file) || '';\n const lines = content.split('\\n');\n const startLine = Math.max(0, issue.line - 5);\n const endLine = Math.min(lines.length, issue.line + 5);\n const codeSnippet = lines.slice(startLine, endLine).join('\\n');\n\n try {\n const response = await fetch(`${CENCORI_API_URL}/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: 'gpt-4o-mini',\n messages: [\n {\n role: 'system',\n content: `You are a security engineer. Generate secure code fixes. For secrets, use environment variables. For XSS, use sanitization. Respond in JSON: {\"fixedCode\": \"the fixed code snippet\", \"explanation\": \"what was changed\"}`,\n },\n {\n role: 'user',\n content: `Fix this security issue:\nType: ${issue.type}\nName: ${issue.name}\nFile: ${issue.file}:${issue.line}\n\nCode to fix:\n\\`\\`\\`\n${codeSnippet}\n\\`\\`\\`\n\nGenerate a secure fix.`,\n },\n ],\n temperature: 0,\n max_tokens: 500,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status}`);\n }\n\n const data = await response.json() as {\n choices: Array<{ message: { content: string } }>;\n };\n const content_response = data.choices[0]?.message?.content || '{}';\n\n const parsed = JSON.parse(content_response);\n results.push({\n issue,\n originalCode: codeSnippet,\n fixedCode: parsed.fixedCode || codeSnippet,\n explanation: parsed.explanation || 'No explanation provided',\n applied: false,\n });\n } catch {\n results.push({\n issue,\n originalCode: codeSnippet,\n fixedCode: codeSnippet,\n explanation: 'Unable to generate fix - manual review required',\n applied: false,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Apply fixes to files\n */\nexport async function applyFixes(\n fixes: FixResult[],\n fileContents: Map<string, string>\n): Promise<FixResult[]> {\n for (const fix of fixes) {\n if (fix.fixedCode === fix.originalCode) {\n continue;\n }\n\n const content = fileContents.get(fix.issue.file);\n if (!content) {\n continue;\n }\n\n // Replace the original code with the fixed code\n const newContent = content.replace(fix.originalCode, fix.fixedCode);\n\n if (newContent !== content) {\n const filePath = path.resolve(fix.issue.file);\n fs.writeFileSync(filePath, newContent, 'utf-8');\n fix.applied = true;\n }\n }\n\n return fixes;\n}\n"],"mappings":"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","names":["fs","path","fs","path"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cencori/scan",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Security scanner for AI apps. Detect hardcoded secrets, PII leaks, and exposed routes.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -57,4 +57,4 @@
57
57
  "tsup": "^8.0.0",
58
58
  "typescript": "^5.3.0"
59
59
  }
60
- }
60
+ }