@cencori/scan 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,85 +1,171 @@
1
1
  # @cencori/scan
2
2
 
3
- Security scanner for AI apps. Detect hardcoded secrets, PII leaks, and exposed routes.
3
+ **Security scanner for AI apps.** Detect hardcoded secrets, PII leaks, exposed routes, and security vulnerabilities — with AI-powered auto-fix.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@cencori/scan.svg)](https://www.npmjs.com/package/@cencori/scan)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Quick Start
9
+
10
+ ```bash
11
+ npx @cencori/scan
12
+ ```
13
+
14
+ That's it. Run it in any project directory to instantly scan for security issues.
15
+
16
+ ## Features
17
+
18
+ - 🔍 **Pattern-based scanning** - Detects 50+ types of secrets, PII, and vulnerabilities
19
+ - 🤖 **AI-powered auto-fix** - Automatically fixes issues with one command
20
+ - ⚡ **Fast** - Scans thousands of files in seconds
21
+ - 🎯 **Zero config** - Works out of the box
22
+ - 📊 **Security scoring** - A through F tier grading
4
23
 
5
24
  ## Installation
6
25
 
7
26
  ```bash
8
- # Run directly with npx
27
+ # Run directly (recommended)
9
28
  npx @cencori/scan
10
29
 
11
30
  # Or install globally
12
31
  npm install -g @cencori/scan
32
+
33
+ # Or as a dev dependency
34
+ npm install -D @cencori/scan
13
35
  ```
14
36
 
15
37
  ## Usage
16
38
 
39
+ ### Basic Scan
40
+
17
41
  ```bash
18
42
  # Scan current directory
19
- cencori-scan
43
+ npx @cencori/scan
20
44
 
21
45
  # Scan specific path
22
- cencori-scan ./my-project
46
+ npx @cencori/scan ./my-project
23
47
 
24
- # Output JSON
25
- cencori-scan --json
48
+ # Output JSON (for CI/CD)
49
+ npx @cencori/scan --json
26
50
 
27
51
  # Quiet mode (score only)
28
- cencori-scan --quiet
52
+ npx @cencori/scan --quiet
53
+
54
+ # Skip interactive prompts
55
+ npx @cencori/scan --no-prompt
56
+ ```
57
+
58
+ ### AI Auto-Fix (Pro)
59
+
60
+ After scanning, you'll be prompted:
61
+
62
+ ```
63
+ ? Would you like Cencori to auto-fix these issues? (y/n)
64
+ ```
65
+
66
+ Enter `y` and you'll be asked for your API key (if not already saved):
67
+
68
+ ```
69
+ ? Enter your Cencori API key: ************************
29
70
  ```
30
71
 
72
+ The AI will:
73
+ 1. Analyze each issue for false positives
74
+ 2. Generate secure code fixes
75
+ 3. Apply fixes automatically
76
+
77
+ Your API key is saved to `~/.cencorirc` for future scans.
78
+
79
+ **Get your free API key at [cencori.com/dashboard](https://cencori.com/dashboard)**
80
+
31
81
  ## What It Detects
32
82
 
33
- ### API Keys & Secrets
34
- - OpenAI, Anthropic, Google AI
35
- - Supabase, Firebase
36
- - Stripe, AWS, GitHub
37
- - And 20+ more providers
83
+ ### 🔐 API Keys & Secrets
84
+
85
+ | Provider | Pattern |
86
+ |----------|---------|
87
+ | OpenAI | `sk-...`, `sk-proj-...` |
88
+ | Anthropic | `sk-ant-...` |
89
+ | Google AI | `AIza...` |
90
+ | Supabase | `eyJh...` (service role) |
91
+ | Stripe | `sk_live_...`, `sk_test_...` |
92
+ | AWS | `AKIA...` |
93
+ | GitHub | `ghp_...`, `gho_...` |
94
+ | Firebase | `firebase-adminsdk-...` |
95
+ | And 20+ more... | |
38
96
 
39
- ### PII (Personal Identifiable Information)
40
- - Email addresses
97
+ ### 👤 PII (Personal Identifiable Information)
98
+
99
+ - Email addresses in code
41
100
  - Phone numbers
42
101
  - Social Security Numbers
43
102
  - Credit card numbers
44
103
 
45
- ### Exposed Routes
46
- - Next.js API routes without auth
47
- - Express routes without middleware
48
- - Sensitive files in public folders
104
+ ### 🛣️ Exposed Routes
105
+
106
+ - Next.js API routes without authentication
107
+ - Express routes without auth middleware
108
+ - Sensitive files in `/public` folders
109
+ - Dashboard/admin routes without protection
110
+
111
+ ### ⚠️ Security Vulnerabilities
112
+
113
+ - SQL injection patterns
114
+ - XSS vulnerabilities (innerHTML, dangerouslySetInnerHTML)
115
+ - Insecure CORS configuration (`Access-Control-Allow-Origin: *`)
116
+ - Hardcoded passwords
117
+ - Debug modes in production
49
118
 
50
119
  ## Security Score
51
120
 
52
- | Score | Meaning |
53
- |-------|---------|
54
- | A-Tier | Excellent - No issues found |
55
- | B-Tier | Good - Minor improvements needed |
56
- | C-Tier | Fair - Some concerns |
57
- | D-Tier | Poor - Significant issues |
58
- | F-Tier | Critical - Leaking secrets |
121
+ | Score | Meaning | Action Required |
122
+ |-------|---------|-----------------|
123
+ | **A-Tier** | Excellent | No security issues detected |
124
+ | **B-Tier** | Good | Minor improvements recommended |
125
+ | **C-Tier** | Fair | Some concerns need attention |
126
+ | **D-Tier** | Poor | Significant issues found |
127
+ | **F-Tier** | Critical | Secrets or major vulnerabilities exposed |
59
128
 
60
129
  ## Example Output
61
130
 
62
131
  ```
63
- Cencori Scan
64
- v0.1.0
132
+ Cencori Scan
133
+ v0.3.4
134
+
135
+ ✔ Scanned 142 files
136
+
137
+ ┌─────────────────────────────────────────────┐
138
+ │ Security Score: D-Tier │
139
+ └─────────────────────────────────────────────┘
65
140
 
66
- Scanned 142 files
141
+ Poor! Significant security issues found.
67
142
 
68
- ┌─────────────────────────────────────────────┐
69
- │ Security Score: F-Tier │
70
- └─────────────────────────────────────────────┘
143
+ SECRETS (3)
144
+ ├─ src/api.ts:12 sk-proj-****
145
+ │ Hardcoded API key - use environment variables
146
+ ├─ src/lib.ts:5 eyJh****
147
+ │ Supabase service role key exposed
148
+ └─ .env.local:3 ANTH****
149
+ Anthropic API key in tracked file
71
150
 
72
- SECRETS (3)
73
- ├─ src/api.ts:12 sk-proj-****
74
- ├─ src/lib.ts:5 eyJh****
75
- └─ .env.local:3 ANTH****
151
+ VULNERABILITIES (2)
152
+ ├─ src/db.ts:45 `SELECT * FROM users WHERE id = ${userId}`
153
+ │ Potential SQL injection - use parameterized queries
154
+ └─ src/page.tsx:23 dangerouslySetInnerHTML={{ __html: content }}
155
+ XSS vulnerability - sanitize content first
76
156
 
77
- Recommendations:
78
- - Use environment variables for secrets
79
- - Never commit API keys to version control
157
+ ─────────────────────────────────────────────
80
158
 
81
- Share: https://scan.cencori.com
82
- Docs: https://cencori.com/docs
159
+ Summary
160
+ Files scanned: 142
161
+ Scan time: 89ms
162
+
163
+ Recommendations:
164
+ - Use environment variables for secrets
165
+ - Never commit API keys to version control
166
+ - Sanitize user input before rendering HTML
167
+
168
+ ? Would you like Cencori to auto-fix these issues? (y/n)
83
169
  ```
84
170
 
85
171
  ## Programmatic Usage
@@ -89,10 +175,106 @@ import { scan } from '@cencori/scan';
89
175
 
90
176
  const result = await scan('./my-project');
91
177
 
92
- console.log(result.score); // 'A' | 'B' | 'C' | 'D' | 'F'
93
- console.log(result.issues); // Array of detected issues
178
+ console.log(result.score); // 'A' | 'B' | 'C' | 'D' | 'F'
179
+ console.log(result.issues); // Array of detected issues
180
+ console.log(result.filesScanned); // Number of files scanned
181
+ console.log(result.scanDuration); // Time in milliseconds
182
+ ```
183
+
184
+ ### TypeScript Types
185
+
186
+ ```typescript
187
+ interface ScanResult {
188
+ score: 'A' | 'B' | 'C' | 'D' | 'F';
189
+ tierDescription: string;
190
+ issues: ScanIssue[];
191
+ filesScanned: number;
192
+ scanDuration: number;
193
+ summary: {
194
+ critical: number;
195
+ high: number;
196
+ medium: number;
197
+ low: number;
198
+ };
199
+ }
200
+
201
+ interface ScanIssue {
202
+ type: 'secret' | 'pii' | 'route' | 'config' | 'vulnerability';
203
+ severity: 'critical' | 'high' | 'medium' | 'low';
204
+ name: string;
205
+ match: string;
206
+ file: string;
207
+ line: number;
208
+ description?: string;
209
+ }
210
+ ```
211
+
212
+ ## CI/CD Integration
213
+
214
+ ### GitHub Actions
215
+
216
+ ```yaml
217
+ name: Security Scan
218
+
219
+ on: [push, pull_request]
220
+
221
+ jobs:
222
+ scan:
223
+ runs-on: ubuntu-latest
224
+ steps:
225
+ - uses: actions/checkout@v4
226
+ - name: Run Cencori Scan
227
+ run: npx @cencori/scan --json > scan-results.json
228
+ - name: Check for failures
229
+ run: |
230
+ SCORE=$(jq -r '.score' scan-results.json)
231
+ if [[ "$SCORE" == "F" ]]; then
232
+ echo "Security scan failed with F-Tier score"
233
+ exit 1
234
+ fi
94
235
  ```
95
236
 
237
+ ### Pre-commit Hook
238
+
239
+ Add to `.husky/pre-commit`:
240
+
241
+ ```bash
242
+ #!/bin/sh
243
+ npx @cencori/scan --quiet --no-prompt
244
+ ```
245
+
246
+ ## Configuration
247
+
248
+ ### Environment Variables
249
+
250
+ | Variable | Description |
251
+ |----------|-------------|
252
+ | `CENCORI_API_KEY` | API key for AI features (optional) |
253
+
254
+ ### Config File
255
+
256
+ API keys are automatically saved to `~/.cencorirc`:
257
+
258
+ ```
259
+ api_key=your_cencori_api_key
260
+ ```
261
+
262
+ ## Privacy
263
+
264
+ Cencori Scan collects **anonymous usage metrics** to improve the product:
265
+ - Number of files scanned
266
+ - Number of issues found
267
+ - Security score
268
+ - Platform (macOS/Linux/Windows)
269
+
270
+ **No code, file paths, or sensitive data is ever transmitted.**
271
+
272
+ ## Links
273
+
274
+ - **Documentation**: [cencori.com/docs](https://cencori.com/docs)
275
+ - **Dashboard**: [cencori.com/dashboard](https://cencori.com/dashboard)
276
+ - **Web Scanner**: [scan.cencori.com](https://scan.cencori.com)
277
+
96
278
  ## License
97
279
 
98
- MIT - Cencori
280
+ MIT - [Cencori](https://cencori.com)
package/dist/cli.js CHANGED
@@ -926,16 +926,25 @@ async function applyFixes(fixes, fileContents) {
926
926
  }
927
927
 
928
928
  // src/telemetry.ts
929
- var TELEMETRY_URL = "https://api.cencori.com/v1/telemetry/scan";
929
+ var TELEMETRY_URL = "https://cencori.com/api/v1/telemetry/scan";
930
+ var pendingTelemetry = null;
930
931
  function sendTelemetry(data) {
931
- fetch(TELEMETRY_URL, {
932
+ pendingTelemetry = fetch(TELEMETRY_URL, {
932
933
  method: "POST",
933
934
  headers: {
934
935
  "Content-Type": "application/json"
935
936
  },
936
937
  body: JSON.stringify(data)
938
+ }).then(() => {
937
939
  }).catch(() => {
938
940
  });
941
+ return pendingTelemetry;
942
+ }
943
+ async function flushTelemetry() {
944
+ if (pendingTelemetry) {
945
+ await pendingTelemetry;
946
+ pendingTelemetry = null;
947
+ }
939
948
  }
940
949
  function buildTelemetryData(result, version, hasApiKey) {
941
950
  const breakdown = {
@@ -967,7 +976,7 @@ function buildTelemetryData(result, version, hasApiKey) {
967
976
  // src/cli.ts
968
977
  var fs3 = __toESM(require("fs"));
969
978
  var path3 = __toESM(require("path"));
970
- var VERSION = "0.3.4";
979
+ var VERSION = "0.3.6";
971
980
  var scoreStyles = {
972
981
  A: { color: import_chalk.default.green },
973
982
  B: { color: import_chalk.default.blue },
@@ -1231,7 +1240,9 @@ async function main() {
1231
1240
  import_commander.program.name("cencori-scan").description("Security scanner for AI apps. Detect secrets, PII, and exposed routes.").version(VERSION).argument("[path]", "Path to scan", ".").option("-j, --json", "Output results as JSON").option("-q, --quiet", "Only output the score").option("--no-prompt", "Skip interactive prompts").option("--no-color", "Disable colored output").action(async (targetPath, options) => {
1232
1241
  if (options.json) {
1233
1242
  const result = await scan(targetPath);
1243
+ sendTelemetry(buildTelemetryData(result, VERSION, !!getApiKey()));
1234
1244
  console.log(JSON.stringify(result, null, 2));
1245
+ await flushTelemetry();
1235
1246
  process.exit(result.score === "A" || result.score === "B" ? 0 : 1);
1236
1247
  return;
1237
1248
  }
@@ -1249,6 +1260,7 @@ async function main() {
1249
1260
  console.log(`
1250
1261
  Score: ${style.color.bold(result.score + "-Tier")}
1251
1262
  `);
1263
+ await flushTelemetry();
1252
1264
  process.exit(result.score === "A" || result.score === "B" ? 0 : 1);
1253
1265
  return;
1254
1266
  }
@@ -1260,11 +1272,13 @@ async function main() {
1260
1272
  await handleAutoFix(result, targetPath);
1261
1273
  }
1262
1274
  printFooter();
1275
+ await flushTelemetry();
1263
1276
  process.exit(result.score === "A" || result.score === "B" ? 0 : 1);
1264
1277
  } catch (error) {
1265
1278
  spinner.fail("Scan failed");
1266
1279
  console.error(import_chalk.default.red(`
1267
1280
  Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1281
+ await flushTelemetry();
1268
1282
  process.exit(1);
1269
1283
  }
1270
1284
  });
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","../src/telemetry.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 { sendTelemetry, buildTelemetryData } from './telemetry.js';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nconst VERSION = '0.3.4';\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 // Send telemetry silently in background (fire and forget)\n sendTelemetry(buildTelemetryData(result, VERSION, !!getApiKey()));\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: 'meta-llama/llama-4-scout-17b-16e-instruct',\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: 'meta-llama/llama-4-scout-17b-16e-instruct',\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","/**\n * Silent telemetry module for Cencori Scan\n * Sends anonymous usage metrics - no code or sensitive data\n */\n\nconst TELEMETRY_URL = 'https://api.cencori.com/v1/telemetry/scan';\n\nexport interface TelemetryData {\n event: 'scan_completed';\n version: string;\n platform: string;\n filesScanned: number;\n issuesFound: number;\n score: string;\n hasApiKey: boolean;\n scanDuration: number;\n issueBreakdown: {\n secrets: number;\n pii: number;\n routes: number;\n config: number;\n vulnerabilities: number;\n };\n}\n\n/**\n * Send telemetry data silently in the background\n * This is fire-and-forget - errors are ignored\n */\nexport function sendTelemetry(data: TelemetryData): void {\n // Fire and forget - don't await, don't block\n fetch(TELEMETRY_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(data),\n }).catch(() => {\n // Silently ignore any errors\n // Telemetry should never affect user experience\n });\n}\n\n/**\n * Build telemetry data from scan result\n */\nexport function buildTelemetryData(\n result: {\n filesScanned: number;\n issues: Array<{ type: string }>;\n score: string;\n scanDuration: number;\n },\n version: string,\n hasApiKey: boolean\n): TelemetryData {\n // Count issues by type\n const breakdown = {\n secrets: 0,\n pii: 0,\n routes: 0,\n config: 0,\n vulnerabilities: 0,\n };\n\n for (const issue of result.issues) {\n const type = issue.type as keyof typeof breakdown;\n if (type in breakdown) {\n breakdown[type]++;\n }\n }\n\n return {\n event: 'scan_completed',\n version,\n platform: process.platform,\n filesScanned: result.filesScanned,\n issuesFound: result.issues.length,\n score: result.score,\n hasApiKey,\n scanDuration: result.scanDuration,\n issueBreakdown: breakdown,\n };\n}\n"],"mappings":"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","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","../src/telemetry.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 { sendTelemetry, buildTelemetryData, flushTelemetry } from './telemetry.js';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nconst VERSION = '0.3.6';\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 // Send telemetry for JSON mode too\n sendTelemetry(buildTelemetryData(result, VERSION, !!getApiKey()));\n console.log(JSON.stringify(result, null, 2));\n // Wait for telemetry to complete before exiting\n await flushTelemetry();\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 // Send telemetry silently in background\n sendTelemetry(buildTelemetryData(result, VERSION, !!getApiKey()));\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 // Wait for telemetry to complete before exiting\n await flushTelemetry();\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 // Wait for telemetry to complete before exiting\n await flushTelemetry();\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 // Wait for any pending telemetry before exiting\n await flushTelemetry();\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: 'meta-llama/llama-4-scout-17b-16e-instruct',\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: 'meta-llama/llama-4-scout-17b-16e-instruct',\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","/**\n * Silent telemetry module for Cencori Scan\n * Sends anonymous usage metrics - no code or sensitive data\n */\n\nconst TELEMETRY_URL = 'https://cencori.com/api/v1/telemetry/scan';\n\nexport interface TelemetryData {\n event: 'scan_completed';\n version: string;\n platform: string;\n filesScanned: number;\n issuesFound: number;\n score: string;\n hasApiKey: boolean;\n scanDuration: number;\n issueBreakdown: {\n secrets: number;\n pii: number;\n routes: number;\n config: number;\n vulnerabilities: number;\n };\n}\n\n// Store the pending telemetry promise so we can ensure it completes before exit\nlet pendingTelemetry: Promise<void> | null = null;\n\n/**\n * Send telemetry data in the background\n * Returns a promise that resolves when the request completes\n */\nexport function sendTelemetry(data: TelemetryData): Promise<void> {\n pendingTelemetry = fetch(TELEMETRY_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(data),\n })\n .then(() => {\n // Success - do nothing\n })\n .catch(() => {\n // Silently ignore any errors\n // Telemetry should never affect user experience\n });\n\n return pendingTelemetry;\n}\n\n/**\n * Wait for any pending telemetry to complete\n * Call this before process exit to ensure telemetry is sent\n */\nexport async function flushTelemetry(): Promise<void> {\n if (pendingTelemetry) {\n await pendingTelemetry;\n pendingTelemetry = null;\n }\n}\n\n/**\n * Build telemetry data from scan result\n */\nexport function buildTelemetryData(\n result: {\n filesScanned: number;\n issues: Array<{ type: string }>;\n score: string;\n scanDuration: number;\n },\n version: string,\n hasApiKey: boolean\n): TelemetryData {\n // Count issues by type\n const breakdown = {\n secrets: 0,\n pii: 0,\n routes: 0,\n config: 0,\n vulnerabilities: 0,\n };\n\n for (const issue of result.issues) {\n const type = issue.type as keyof typeof breakdown;\n if (type in breakdown) {\n breakdown[type]++;\n }\n }\n\n return {\n event: 'scan_completed',\n version,\n platform: process.platform,\n filesScanned: result.filesScanned,\n issuesFound: result.issues.length,\n score: result.score,\n hasApiKey,\n scanDuration: result.scanDuration,\n issueBreakdown: breakdown,\n };\n}\n"],"mappings":"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","names":["fs","path","fs","path","chalk","ora"]}
package/dist/cli.mjs CHANGED
@@ -903,16 +903,25 @@ async function applyFixes(fixes, fileContents) {
903
903
  }
904
904
 
905
905
  // src/telemetry.ts
906
- var TELEMETRY_URL = "https://api.cencori.com/v1/telemetry/scan";
906
+ var TELEMETRY_URL = "https://cencori.com/api/v1/telemetry/scan";
907
+ var pendingTelemetry = null;
907
908
  function sendTelemetry(data) {
908
- fetch(TELEMETRY_URL, {
909
+ pendingTelemetry = fetch(TELEMETRY_URL, {
909
910
  method: "POST",
910
911
  headers: {
911
912
  "Content-Type": "application/json"
912
913
  },
913
914
  body: JSON.stringify(data)
915
+ }).then(() => {
914
916
  }).catch(() => {
915
917
  });
918
+ return pendingTelemetry;
919
+ }
920
+ async function flushTelemetry() {
921
+ if (pendingTelemetry) {
922
+ await pendingTelemetry;
923
+ pendingTelemetry = null;
924
+ }
916
925
  }
917
926
  function buildTelemetryData(result, version, hasApiKey) {
918
927
  const breakdown = {
@@ -944,7 +953,7 @@ function buildTelemetryData(result, version, hasApiKey) {
944
953
  // src/cli.ts
945
954
  import * as fs3 from "fs";
946
955
  import * as path3 from "path";
947
- var VERSION = "0.3.4";
956
+ var VERSION = "0.3.6";
948
957
  var scoreStyles = {
949
958
  A: { color: chalk.green },
950
959
  B: { color: chalk.blue },
@@ -1208,7 +1217,9 @@ async function main() {
1208
1217
  program.name("cencori-scan").description("Security scanner for AI apps. Detect secrets, PII, and exposed routes.").version(VERSION).argument("[path]", "Path to scan", ".").option("-j, --json", "Output results as JSON").option("-q, --quiet", "Only output the score").option("--no-prompt", "Skip interactive prompts").option("--no-color", "Disable colored output").action(async (targetPath, options) => {
1209
1218
  if (options.json) {
1210
1219
  const result = await scan(targetPath);
1220
+ sendTelemetry(buildTelemetryData(result, VERSION, !!getApiKey()));
1211
1221
  console.log(JSON.stringify(result, null, 2));
1222
+ await flushTelemetry();
1212
1223
  process.exit(result.score === "A" || result.score === "B" ? 0 : 1);
1213
1224
  return;
1214
1225
  }
@@ -1226,6 +1237,7 @@ async function main() {
1226
1237
  console.log(`
1227
1238
  Score: ${style.color.bold(result.score + "-Tier")}
1228
1239
  `);
1240
+ await flushTelemetry();
1229
1241
  process.exit(result.score === "A" || result.score === "B" ? 0 : 1);
1230
1242
  return;
1231
1243
  }
@@ -1237,11 +1249,13 @@ async function main() {
1237
1249
  await handleAutoFix(result, targetPath);
1238
1250
  }
1239
1251
  printFooter();
1252
+ await flushTelemetry();
1240
1253
  process.exit(result.score === "A" || result.score === "B" ? 0 : 1);
1241
1254
  } catch (error) {
1242
1255
  spinner.fail("Scan failed");
1243
1256
  console.error(chalk.red(`
1244
1257
  Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1258
+ await flushTelemetry();
1245
1259
  process.exit(1);
1246
1260
  }
1247
1261
  });
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","../src/telemetry.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 { sendTelemetry, buildTelemetryData } from './telemetry.js';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nconst VERSION = '0.3.4';\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 // Send telemetry silently in background (fire and forget)\n sendTelemetry(buildTelemetryData(result, VERSION, !!getApiKey()));\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: 'meta-llama/llama-4-scout-17b-16e-instruct',\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: 'meta-llama/llama-4-scout-17b-16e-instruct',\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","/**\n * Silent telemetry module for Cencori Scan\n * Sends anonymous usage metrics - no code or sensitive data\n */\n\nconst TELEMETRY_URL = 'https://api.cencori.com/v1/telemetry/scan';\n\nexport interface TelemetryData {\n event: 'scan_completed';\n version: string;\n platform: string;\n filesScanned: number;\n issuesFound: number;\n score: string;\n hasApiKey: boolean;\n scanDuration: number;\n issueBreakdown: {\n secrets: number;\n pii: number;\n routes: number;\n config: number;\n vulnerabilities: number;\n };\n}\n\n/**\n * Send telemetry data silently in the background\n * This is fire-and-forget - errors are ignored\n */\nexport function sendTelemetry(data: TelemetryData): void {\n // Fire and forget - don't await, don't block\n fetch(TELEMETRY_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(data),\n }).catch(() => {\n // Silently ignore any errors\n // Telemetry should never affect user experience\n });\n}\n\n/**\n * Build telemetry data from scan result\n */\nexport function buildTelemetryData(\n result: {\n filesScanned: number;\n issues: Array<{ type: string }>;\n score: string;\n scanDuration: number;\n },\n version: string,\n hasApiKey: boolean\n): TelemetryData {\n // Count issues by type\n const breakdown = {\n secrets: 0,\n pii: 0,\n routes: 0,\n config: 0,\n vulnerabilities: 0,\n };\n\n for (const issue of result.issues) {\n const type = issue.type as keyof typeof breakdown;\n if (type in breakdown) {\n breakdown[type]++;\n }\n }\n\n return {\n event: 'scan_completed',\n version,\n platform: process.platform,\n filesScanned: result.filesScanned,\n issuesFound: result.issues.length,\n score: result.score,\n hasApiKey,\n scanDuration: result.scanDuration,\n issueBreakdown: breakdown,\n };\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","../src/telemetry.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 { sendTelemetry, buildTelemetryData, flushTelemetry } from './telemetry.js';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nconst VERSION = '0.3.6';\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 // Send telemetry for JSON mode too\n sendTelemetry(buildTelemetryData(result, VERSION, !!getApiKey()));\n console.log(JSON.stringify(result, null, 2));\n // Wait for telemetry to complete before exiting\n await flushTelemetry();\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 // Send telemetry silently in background\n sendTelemetry(buildTelemetryData(result, VERSION, !!getApiKey()));\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 // Wait for telemetry to complete before exiting\n await flushTelemetry();\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 // Wait for telemetry to complete before exiting\n await flushTelemetry();\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 // Wait for any pending telemetry before exiting\n await flushTelemetry();\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: 'meta-llama/llama-4-scout-17b-16e-instruct',\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: 'meta-llama/llama-4-scout-17b-16e-instruct',\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","/**\n * Silent telemetry module for Cencori Scan\n * Sends anonymous usage metrics - no code or sensitive data\n */\n\nconst TELEMETRY_URL = 'https://cencori.com/api/v1/telemetry/scan';\n\nexport interface TelemetryData {\n event: 'scan_completed';\n version: string;\n platform: string;\n filesScanned: number;\n issuesFound: number;\n score: string;\n hasApiKey: boolean;\n scanDuration: number;\n issueBreakdown: {\n secrets: number;\n pii: number;\n routes: number;\n config: number;\n vulnerabilities: number;\n };\n}\n\n// Store the pending telemetry promise so we can ensure it completes before exit\nlet pendingTelemetry: Promise<void> | null = null;\n\n/**\n * Send telemetry data in the background\n * Returns a promise that resolves when the request completes\n */\nexport function sendTelemetry(data: TelemetryData): Promise<void> {\n pendingTelemetry = fetch(TELEMETRY_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(data),\n })\n .then(() => {\n // Success - do nothing\n })\n .catch(() => {\n // Silently ignore any errors\n // Telemetry should never affect user experience\n });\n\n return pendingTelemetry;\n}\n\n/**\n * Wait for any pending telemetry to complete\n * Call this before process exit to ensure telemetry is sent\n */\nexport async function flushTelemetry(): Promise<void> {\n if (pendingTelemetry) {\n await pendingTelemetry;\n pendingTelemetry = null;\n }\n}\n\n/**\n * Build telemetry data from scan result\n */\nexport function buildTelemetryData(\n result: {\n filesScanned: number;\n issues: Array<{ type: string }>;\n score: string;\n scanDuration: number;\n },\n version: string,\n hasApiKey: boolean\n): TelemetryData {\n // Count issues by type\n const breakdown = {\n secrets: 0,\n pii: 0,\n routes: 0,\n config: 0,\n vulnerabilities: 0,\n };\n\n for (const issue of result.issues) {\n const type = issue.type as keyof typeof breakdown;\n if (type in breakdown) {\n breakdown[type]++;\n }\n }\n\n return {\n event: 'scan_completed',\n version,\n platform: process.platform,\n filesScanned: result.filesScanned,\n issuesFound: result.issues.length,\n score: result.score,\n hasApiKey,\n scanDuration: result.scanDuration,\n issueBreakdown: breakdown,\n };\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.4",
3
+ "version": "0.3.6",
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",