@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 +111 -31
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +112 -32
- package/dist/cli.mjs.map +1 -1
- package/package.json +2 -2
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
|
|
724
|
-
|
|
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 =
|
|
772
|
+
const apiKey = getEffectiveApiKey();
|
|
728
773
|
if (!apiKey) {
|
|
729
|
-
throw new Error("
|
|
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 =
|
|
840
|
+
const apiKey = getEffectiveApiKey();
|
|
796
841
|
if (!apiKey) {
|
|
797
|
-
throw new Error("
|
|
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 =
|
|
878
|
-
|
|
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
|
|
887
|
-
var
|
|
888
|
-
var VERSION = "0.3.
|
|
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 =
|
|
1035
|
-
const content =
|
|
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
|
|
701
|
-
|
|
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 =
|
|
749
|
+
const apiKey = getEffectiveApiKey();
|
|
705
750
|
if (!apiKey) {
|
|
706
|
-
throw new Error("
|
|
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 =
|
|
817
|
+
const apiKey = getEffectiveApiKey();
|
|
773
818
|
if (!apiKey) {
|
|
774
|
-
throw new Error("
|
|
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 =
|
|
855
|
-
|
|
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
|
|
864
|
-
import * as
|
|
865
|
-
var VERSION = "0.3.
|
|
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 =
|
|
1012
|
-
const content =
|
|
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.
|
|
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
|
+
}
|