prodlint 0.2.1 → 0.2.2
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 +86 -4
- package/package.json +12 -4
- package/dist/cli.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mcp.js.map +0 -1
package/README.md
CHANGED
|
@@ -20,6 +20,33 @@ prodlint catches what TypeScript and ESLint miss: **production readiness gaps**.
|
|
|
20
20
|
npx prodlint
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
## Example Output
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
prodlint v0.2.1
|
|
27
|
+
Scanned 142 files in 87ms
|
|
28
|
+
|
|
29
|
+
src/app/api/users/route.ts
|
|
30
|
+
8:1 CRIT API route has no authentication check auth-checks
|
|
31
|
+
8:1 WARN API route has no rate limiting rate-limiting
|
|
32
|
+
|
|
33
|
+
src/components/chat.tsx
|
|
34
|
+
24:5 CRIT Hardcoded Stripe secret key detected secrets
|
|
35
|
+
|
|
36
|
+
src/lib/db.ts
|
|
37
|
+
15:1 CRIT SQL query built with template literal interpolation sql-injection
|
|
38
|
+
|
|
39
|
+
Scores
|
|
40
|
+
security 40 ████████░░░░░░░░░░░░
|
|
41
|
+
reliability 70 ██████████████░░░░░░
|
|
42
|
+
performance 95 ███████████████████░
|
|
43
|
+
ai-quality 88 ██████████████████░░
|
|
44
|
+
|
|
45
|
+
Overall: 73/100
|
|
46
|
+
|
|
47
|
+
3 critical · 4 warnings · 2 info
|
|
48
|
+
```
|
|
49
|
+
|
|
23
50
|
## Usage
|
|
24
51
|
|
|
25
52
|
```bash
|
|
@@ -31,7 +58,7 @@ npx prodlint --ignore "*.test.ts" # Ignore patterns
|
|
|
31
58
|
|
|
32
59
|
## What It Checks
|
|
33
60
|
|
|
34
|
-
prodlint runs **11 rules** across
|
|
61
|
+
prodlint runs **11 rules** across 3 categories:
|
|
35
62
|
|
|
36
63
|
### Security
|
|
37
64
|
| Rule | Severity | What it detects |
|
|
@@ -64,9 +91,7 @@ Each category starts at 100 points. Deductions:
|
|
|
64
91
|
- **Warning**: -3 points
|
|
65
92
|
- **Info**: -1 point
|
|
66
93
|
|
|
67
|
-
Overall score = average of all
|
|
68
|
-
|
|
69
|
-
Exit code is `1` if any critical findings exist, `0` otherwise.
|
|
94
|
+
Overall score = average of all category scores. Exit code is `1` if any critical findings exist, `0` otherwise.
|
|
70
95
|
|
|
71
96
|
## Smart Detection
|
|
72
97
|
|
|
@@ -77,6 +102,63 @@ prodlint avoids common false positives:
|
|
|
77
102
|
- **TypeScript path aliases** — `@/`, `~/`, and custom tsconfig paths aren't flagged as hallucinated imports
|
|
78
103
|
- **Route exemptions** — auth, webhook, health, and cron routes are exempt from auth/rate-limit checks
|
|
79
104
|
|
|
105
|
+
## GitHub Action
|
|
106
|
+
|
|
107
|
+
Add prodlint to your CI pipeline. It posts a score summary as a PR comment and can fail builds below a threshold.
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
- uses: prodlint/prodlint@v1
|
|
111
|
+
with:
|
|
112
|
+
threshold: 70 # Fail if score < 70 (optional)
|
|
113
|
+
comment: true # Post PR comment (default: true)
|
|
114
|
+
ignore: '*.test.ts, __mocks__/**' # Ignore patterns (optional)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Inputs:**
|
|
118
|
+
| Input | Default | Description |
|
|
119
|
+
|-------|---------|-------------|
|
|
120
|
+
| `path` | `.` | Path to scan |
|
|
121
|
+
| `threshold` | `0` | Minimum score to pass (0-100) |
|
|
122
|
+
| `ignore` | `''` | Comma-separated glob patterns to ignore |
|
|
123
|
+
| `comment` | `true` | Post a PR comment with results |
|
|
124
|
+
|
|
125
|
+
**Outputs:**
|
|
126
|
+
| Output | Description |
|
|
127
|
+
|--------|-------------|
|
|
128
|
+
| `score` | Overall score (0-100) |
|
|
129
|
+
| `critical` | Number of critical findings |
|
|
130
|
+
|
|
131
|
+
## MCP Server
|
|
132
|
+
|
|
133
|
+
prodlint ships an MCP server for AI coding tools (Cursor, Claude Code, Windsurf, etc.).
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npx prodlint-mcp
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Claude Code
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
claude mcp add prodlint npx prodlint-mcp
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Cursor / Windsurf
|
|
146
|
+
|
|
147
|
+
Add to your MCP config:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"mcpServers": {
|
|
152
|
+
"prodlint": {
|
|
153
|
+
"command": "npx",
|
|
154
|
+
"args": ["prodlint-mcp"]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The MCP server exposes a single `scan` tool that accepts a project path and returns the full score breakdown with findings.
|
|
161
|
+
|
|
80
162
|
## Suppressing Findings
|
|
81
163
|
|
|
82
164
|
Suppress a single line:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prodlint",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Scan AI-generated projects for production readiness issues",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "prodlint contributors",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/prodlint/prodlint/issues"
|
|
14
14
|
},
|
|
15
|
-
"homepage": "https://
|
|
15
|
+
"homepage": "https://prodlint.com",
|
|
16
16
|
"bin": {
|
|
17
17
|
"prodlint": "./dist/cli.js",
|
|
18
18
|
"prodlint-mcp": "./dist/mcp.js"
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
|
-
"dist",
|
|
29
|
+
"dist/**/*.js",
|
|
30
|
+
"dist/**/*.d.ts",
|
|
30
31
|
"action.yml"
|
|
31
32
|
],
|
|
32
33
|
"scripts": {
|
|
@@ -57,14 +58,21 @@
|
|
|
57
58
|
"linter",
|
|
58
59
|
"security",
|
|
59
60
|
"ai",
|
|
61
|
+
"ai-code",
|
|
60
62
|
"vibe-coding",
|
|
61
63
|
"production-readiness",
|
|
62
64
|
"code-quality",
|
|
65
|
+
"code-scanner",
|
|
63
66
|
"next.js",
|
|
64
67
|
"react",
|
|
65
68
|
"typescript",
|
|
66
69
|
"static-analysis",
|
|
70
|
+
"cli",
|
|
71
|
+
"scanner",
|
|
67
72
|
"mcp",
|
|
68
|
-
"mcp-server"
|
|
73
|
+
"mcp-server",
|
|
74
|
+
"cursor",
|
|
75
|
+
"copilot",
|
|
76
|
+
"devtools"
|
|
69
77
|
]
|
|
70
78
|
}
|
package/dist/cli.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/utils/file-walker.ts","../src/utils/patterns.ts","../src/utils/version.ts","../src/scorer.ts","../src/rules/secrets.ts","../src/rules/hallucinated-imports.ts","../src/rules/auth-checks.ts","../src/rules/env-exposure.ts","../src/rules/error-handling.ts","../src/rules/input-validation.ts","../src/rules/rate-limiting.ts","../src/rules/cors-config.ts","../src/rules/ai-smells.ts","../src/rules/unsafe-html.ts","../src/rules/sql-injection.ts","../src/rules/index.ts","../src/scanner.ts","../src/reporter.ts"],"sourcesContent":["import { parseArgs } from 'node:util'\r\nimport { scan } from './scanner.js'\r\nimport { reportPretty, reportJson } from './reporter.js'\r\nimport { getVersion } from './utils/version.js'\r\n\r\nasync function main() {\r\n const { values, positionals } = parseArgs({\r\n allowPositionals: true,\r\n options: {\r\n json: { type: 'boolean', default: false },\r\n ignore: { type: 'string', multiple: true, default: [] },\r\n help: { type: 'boolean', short: 'h', default: false },\r\n version: { type: 'boolean', short: 'v', default: false },\r\n },\r\n })\r\n\r\n if (values.help) {\r\n printHelp()\r\n process.exit(0)\r\n }\r\n\r\n if (values.version) {\r\n console.log(getVersion())\r\n process.exit(0)\r\n }\r\n\r\n const targetPath = positionals[0] ?? '.'\r\n\r\n const result = await scan({\r\n path: targetPath,\r\n ignore: values.ignore as string[],\r\n })\r\n\r\n if (values.json) {\r\n console.log(reportJson(result))\r\n } else {\r\n console.log(reportPretty(result))\r\n }\r\n\r\n if (result.summary.critical > 0) {\r\n process.exit(1)\r\n }\r\n}\r\n\r\nfunction printHelp() {\r\n console.log(`\r\n prodlint - Scan AI-generated projects for production readiness issues\r\n\r\n Usage:\r\n npx prodlint [path] [options]\r\n\r\n Options:\r\n --json Output results as JSON\r\n --ignore <pattern> Glob patterns to ignore (can be repeated)\r\n -h, --help Show this help message\r\n -v, --version Show version\r\n\r\n Examples:\r\n npx prodlint Scan current directory\r\n npx prodlint ./my-app Scan specific path\r\n npx prodlint --json JSON output\r\n npx prodlint --ignore \"*.test\" Ignore test files\r\n`)\r\n}\r\n\r\nmain().catch((err) => {\r\n console.error('prodlint error:', err instanceof Error ? err.message : 'Unknown error')\r\n process.exit(2)\r\n})\r\n","import fg from 'fast-glob'\r\nimport { readFile, stat, realpath } from 'node:fs/promises'\r\nimport { resolve, extname, sep } from 'node:path'\r\nimport { buildCommentMap } from './patterns.js'\r\nimport type { FileContext, ProjectContext } from '../types.js'\r\n\r\nconst DEFAULT_IGNORES = [\r\n '**/node_modules/**',\r\n '**/dist/**',\r\n '**/build/**',\r\n '**/.next/**',\r\n '**/.git/**',\r\n '**/coverage/**',\r\n '**/*.min.js',\r\n '**/*.min.css',\r\n '**/package-lock.json',\r\n '**/yarn.lock',\r\n '**/pnpm-lock.yaml',\r\n '**/bun.lockb',\r\n '**/*.map',\r\n '**/*.d.ts',\r\n]\r\n\r\nconst SCAN_EXTENSIONS = [\r\n 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs',\r\n 'json',\r\n]\r\n\r\nconst MAX_FILE_SIZE = 1024 * 1024 // 1MB\r\n\r\nexport async function walkFiles(\r\n root: string,\r\n extraIgnores: string[] = [],\r\n): Promise<string[]> {\r\n const patterns = SCAN_EXTENSIONS.map(ext => `**/*.${ext}`)\r\n // Also grab .env files (dotfiles)\r\n patterns.push('**/.env', '**/.env.*')\r\n // Also grab .gitignore\r\n patterns.push('**/.gitignore')\r\n\r\n const files = await fg(patterns, {\r\n cwd: root,\r\n ignore: [...DEFAULT_IGNORES, ...extraIgnores],\r\n absolute: false,\r\n dot: true,\r\n followSymbolicLinks: false,\r\n })\r\n\r\n return files.sort()\r\n}\r\n\r\nexport async function readFileContext(\r\n root: string,\r\n relativePath: string,\r\n): Promise<FileContext | null> {\r\n try {\r\n const absolutePath = resolve(root, relativePath)\r\n\r\n // Verify the resolved path stays within the project root\r\n const realRoot = await realpath(root)\r\n const realFile = await realpath(absolutePath)\r\n if (!realFile.startsWith(realRoot + sep) && realFile !== realRoot) return null\r\n\r\n // Skip files over 1MB\r\n const fileStats = await stat(absolutePath)\r\n if (fileStats.size > MAX_FILE_SIZE) return null\r\n\r\n const content = await readFile(absolutePath, 'utf-8')\r\n const lines = content.split(/\\r?\\n|\\r/)\r\n return {\r\n absolutePath,\r\n relativePath,\r\n content,\r\n lines,\r\n ext: extname(relativePath).slice(1), // remove leading dot\r\n commentMap: buildCommentMap(lines),\r\n }\r\n } catch {\r\n return null\r\n }\r\n}\r\n\r\nexport async function buildProjectContext(\r\n root: string,\r\n files: string[],\r\n): Promise<ProjectContext> {\r\n let packageJson: Record<string, unknown> | null = null\r\n let declaredDependencies = new Set<string>()\r\n let tsconfigPaths = new Set<string>()\r\n let hasAuthMiddleware = false\r\n let gitignoreContent: string | null = null\r\n let envInGitignore = false\r\n\r\n // Read package.json\r\n try {\r\n const raw = await readFile(resolve(root, 'package.json'), 'utf-8')\r\n packageJson = JSON.parse(raw)\r\n const deps = {\r\n ...(packageJson?.dependencies as Record<string, string> ?? {}),\r\n ...(packageJson?.devDependencies as Record<string, string> ?? {}),\r\n ...(packageJson?.peerDependencies as Record<string, string> ?? {}),\r\n }\r\n declaredDependencies = new Set(Object.keys(deps))\r\n } catch {\r\n // No package.json or invalid JSON\r\n }\r\n\r\n // Read tsconfig.json to extract path aliases\r\n try {\r\n const raw = await readFile(resolve(root, 'tsconfig.json'), 'utf-8')\r\n // Strip single-line comments (tsconfig allows them)\r\n const stripped = raw.replace(/\\/\\/.*$/gm, '')\r\n const tsconfig = JSON.parse(stripped)\r\n const paths = tsconfig?.compilerOptions?.paths as Record<string, string[]> | undefined\r\n if (paths) {\r\n for (const alias of Object.keys(paths)) {\r\n // @components/* → @components\r\n // @/* → @/\r\n const prefix = alias.replace(/\\/?\\*$/, '')\r\n if (prefix) tsconfigPaths.add(prefix)\r\n }\r\n }\r\n } catch {\r\n // No tsconfig.json\r\n }\r\n\r\n // Check for auth middleware (middleware.ts or middleware.js in root)\r\n try {\r\n for (const name of ['middleware.ts', 'middleware.js', 'src/middleware.ts', 'src/middleware.js']) {\r\n try {\r\n const content = await readFile(resolve(root, name), 'utf-8')\r\n const authPatterns = [\r\n /getSession/i, /getUser/i, /auth\\(\\)/, /withAuth/i,\r\n /clerkMiddleware/i, /authMiddleware/i, /NextAuth/i,\r\n /supabase.*auth/i, /createMiddlewareClient/i,\r\n /getToken/i, /verifyToken/i, /jwt/i, /updateSession/i,\r\n ]\r\n if (authPatterns.some(p => p.test(content))) {\r\n hasAuthMiddleware = true\r\n break\r\n }\r\n } catch {\r\n // File doesn't exist\r\n }\r\n }\r\n } catch {\r\n // No middleware\r\n }\r\n\r\n // Read .gitignore\r\n try {\r\n gitignoreContent = await readFile(resolve(root, '.gitignore'), 'utf-8')\r\n envInGitignore = /^\\.env$/m.test(gitignoreContent) ||\r\n /^\\.env\\*$/m.test(gitignoreContent) ||\r\n /^\\.env\\.\\*$/m.test(gitignoreContent) ||\r\n /^\\.env\\.local$/m.test(gitignoreContent)\r\n } catch {\r\n // No .gitignore\r\n }\r\n\r\n return {\r\n root,\r\n packageJson,\r\n declaredDependencies,\r\n tsconfigPaths,\r\n hasAuthMiddleware,\r\n gitignoreContent,\r\n envInGitignore,\r\n allFiles: files,\r\n }\r\n}\r\n","/**\r\n * Check if a file path looks like a Next.js API route\r\n */\r\nexport function isApiRoute(relativePath: string): boolean {\r\n // Next.js App Router: app/**/route.ts\r\n if (/app\\/.*route\\.(ts|js|tsx|jsx)$/.test(relativePath)) return true\r\n // Next.js Pages Router: pages/api/**\r\n if (/pages\\/api\\//.test(relativePath)) return true\r\n return false\r\n}\r\n\r\n/**\r\n * Check if a file is a React client component (has \"use client\" directive)\r\n */\r\nexport function isClientComponent(content: string): boolean {\r\n // Must be at the very top (possibly after comments/whitespace)\r\n const firstLines = content.slice(0, 500)\r\n return /^(['\"])use client\\1/m.test(firstLines)\r\n}\r\n\r\n/**\r\n * Check if a file is a React server component (default in App Router)\r\n */\r\nexport function isServerComponent(content: string): boolean {\r\n return !isClientComponent(content)\r\n}\r\n\r\n/**\r\n * Pre-compute which lines are inside block comments.\r\n * Returns a boolean array where true = line is inside a block comment.\r\n */\r\nexport function buildCommentMap(lines: string[]): boolean[] {\r\n const map = new Array<boolean>(lines.length).fill(false)\r\n let inBlock = false\r\n\r\n for (let i = 0; i < lines.length; i++) {\r\n const line = lines[i]\r\n\r\n if (inBlock) {\r\n map[i] = true\r\n if (line.includes('*/')) {\r\n inBlock = false\r\n }\r\n continue\r\n }\r\n\r\n // Check for block comment start (not preceded by code on the same line that matters)\r\n const trimmed = line.trim()\r\n if (trimmed.startsWith('/*')) {\r\n map[i] = true\r\n if (!trimmed.includes('*/')) {\r\n inBlock = true\r\n }\r\n continue\r\n }\r\n\r\n // Inline /* ... */ on a line with code — don't mark the whole line\r\n // But a line that's ONLY a mid-block comment continuation (starts with *)\r\n if (trimmed.startsWith('*')) {\r\n // Could be JSDoc or block comment continuation\r\n map[i] = true\r\n }\r\n }\r\n\r\n return map\r\n}\r\n\r\n/**\r\n * Check if a line is a comment (single-line or inside block comment)\r\n */\r\nexport function isCommentLine(lines: string[], lineIndex: number, commentMap: boolean[]): boolean {\r\n if (commentMap[lineIndex]) return true\r\n const trimmed = lines[lineIndex]?.trim() ?? ''\r\n return trimmed.startsWith('//')\r\n}\r\n\r\n/**\r\n * Check if line is suppressed by a prodlint-disable comment\r\n */\r\nexport function isLineSuppressed(\r\n lines: string[],\r\n lineIndex: number,\r\n ruleId: string,\r\n): boolean {\r\n // Check file-level disable\r\n for (const line of lines) {\r\n const trimmed = line.trim()\r\n if (trimmed === '' || trimmed.startsWith('//') || trimmed.startsWith('/*')) {\r\n const match = trimmed.match(/prodlint-disable\\s+(.+)/)\r\n if (match) {\r\n const ids = match[1].split(/[\\s,]+/).filter(Boolean)\r\n if (ids.includes(ruleId)) return true\r\n }\r\n continue\r\n }\r\n break // Stop at first non-comment, non-empty line\r\n }\r\n\r\n // Check line-level disable (previous line)\r\n if (lineIndex > 0) {\r\n const prevLine = lines[lineIndex - 1].trim()\r\n const match = prevLine.match(/prodlint-disable-next-line\\s+(.+)/)\r\n if (match) {\r\n const ids = match[1].split(/[\\s,]+/).filter(Boolean)\r\n if (ids.includes(ruleId)) return true\r\n }\r\n }\r\n\r\n return false\r\n}\r\n\r\n/**\r\n * Known Node.js built-in modules\r\n */\r\nexport const NODE_BUILTINS = new Set([\r\n 'assert', 'async_hooks', 'buffer', 'child_process', 'cluster',\r\n 'console', 'constants', 'crypto', 'dgram', 'diagnostics_channel',\r\n 'dns', 'domain', 'events', 'fs', 'http', 'http2', 'https',\r\n 'inspector', 'module', 'net', 'os', 'path', 'perf_hooks',\r\n 'process', 'punycode', 'querystring', 'readline', 'repl',\r\n 'stream', 'string_decoder', 'sys', 'timers', 'tls', 'trace_events',\r\n 'tty', 'url', 'util', 'v8', 'vm', 'wasi', 'worker_threads', 'zlib',\r\n // node: prefixed are handled separately\r\n])\r\n","import { readFileSync } from 'node:fs'\r\nimport { fileURLToPath } from 'node:url'\r\nimport { dirname, resolve } from 'node:path'\r\n\r\nexport function getVersion(): string {\r\n try {\r\n // Try relative to the built file location\r\n const dir = dirname(fileURLToPath(import.meta.url))\r\n const pkg = JSON.parse(\r\n readFileSync(resolve(dir, '..', 'package.json'), 'utf-8'),\r\n )\r\n return pkg.version ?? '0.0.0'\r\n } catch {\r\n return '0.0.0'\r\n }\r\n}\r\n","import type { Category, CategoryScore, Finding } from './types.js'\r\n\r\nconst CATEGORIES: Category[] = ['security', 'reliability', 'performance', 'ai-quality']\r\n\r\nconst DEDUCTIONS: Record<string, number> = {\r\n critical: 10,\r\n warning: 3,\r\n info: 1,\r\n}\r\n\r\nexport function calculateScores(findings: Finding[]): {\r\n overallScore: number\r\n categoryScores: CategoryScore[]\r\n} {\r\n const categoryScores: CategoryScore[] = CATEGORIES.map(category => {\r\n const categoryFindings = findings.filter(f => f.category === category)\r\n let score = 100\r\n for (const f of categoryFindings) {\r\n score -= DEDUCTIONS[f.severity] ?? 0\r\n }\r\n return {\r\n category,\r\n score: Math.max(0, score),\r\n findingCount: categoryFindings.length,\r\n }\r\n })\r\n\r\n const overallScore = Math.round(\r\n categoryScores.reduce((sum, c) => sum + c.score, 0) / CATEGORIES.length,\r\n )\r\n\r\n return { overallScore, categoryScores }\r\n}\r\n\r\nexport function summarizeFindings(findings: Finding[]) {\r\n return {\r\n critical: findings.filter(f => f.severity === 'critical').length,\r\n warning: findings.filter(f => f.severity === 'warning').length,\r\n info: findings.filter(f => f.severity === 'info').length,\r\n }\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine } from '../utils/patterns.js'\r\n\r\nconst SECRET_PATTERNS: { name: string; pattern: RegExp }[] = [\r\n { name: 'Stripe secret key', pattern: /sk_live_[a-zA-Z0-9]{20,}/ },\r\n { name: 'Stripe test key', pattern: /sk_test_[a-zA-Z0-9]{20,}/ },\r\n { name: 'AWS access key', pattern: /AKIA[0-9A-Z]{16}/ },\r\n { name: 'AWS secret key', pattern: /(?:aws_secret_access_key|AWS_SECRET)\\s*[=:]\\s*['\"]?[A-Za-z0-9/+=]{40}['\"]?/ },\r\n { name: 'Supabase service role key', pattern: /eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.[A-Za-z0-9_-]{50,}\\.[A-Za-z0-9_-]{20,}/ },\r\n { name: 'OpenAI API key', pattern: /sk-[a-zA-Z0-9]{20,}T3BlbkFJ[a-zA-Z0-9]{20,}/ },\r\n { name: 'GitHub token', pattern: /gh[ps]_[A-Za-z0-9_]{36,}/ },\r\n { name: 'GitHub fine-grained token', pattern: /github_pat_[A-Za-z0-9_]{22,}/ },\r\n { name: 'Generic API key assignment', pattern: /(?:api_key|apikey|api_secret|secret_key|private_key)\\s*[=:]\\s*['\"][a-zA-Z0-9_\\-]{20,}['\"]/ },\r\n { name: 'SendGrid API key', pattern: /SG\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9_-]{43}/ },\r\n]\r\n\r\nexport const secretsRule: Rule = {\r\n id: 'secrets',\r\n name: 'Hardcoded Secrets',\r\n description: 'Detects hardcoded API keys, tokens, and credentials in source code',\r\n category: 'security',\r\n severity: 'critical',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'json'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n const line = file.lines[i]\r\n\r\n // Skip comments (single-line and block)\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n for (const { name, pattern } of SECRET_PATTERNS) {\r\n const match = pattern.exec(line)\r\n if (match) {\r\n findings.push({\r\n ruleId: 'secrets',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: match.index + 1,\r\n message: `Hardcoded ${name} detected`,\r\n severity: 'critical',\r\n category: 'security',\r\n })\r\n }\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine, NODE_BUILTINS } from '../utils/patterns.js'\r\n\r\n// Packages that are commonly available without being in package.json\r\nconst IMPLICIT_PACKAGES = new Set([\r\n 'react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime',\r\n 'next', 'next/server', 'next/image', 'next/link', 'next/font',\r\n 'next/navigation', 'next/headers', 'next/dynamic', 'next/script',\r\n 'next/router', 'next/head', 'next/app', 'next/document',\r\n])\r\n\r\n// Line-level patterns to capture import sources\r\nconst LINE_IMPORT_PATTERNS = [\r\n /from\\s+['\"]([^'\"./][^'\"]*)['\"]/,\r\n /require\\s*\\(\\s*['\"]([^'\"./][^'\"]*)['\"]\\s*\\)/,\r\n /import\\s*\\(\\s*['\"]([^'\"./][^'\"]*)['\"]\\s*\\)/,\r\n]\r\n\r\nfunction getPackageName(importPath: string): string {\r\n if (importPath.startsWith('@')) {\r\n const parts = importPath.split('/')\r\n return parts.slice(0, 2).join('/')\r\n }\r\n return importPath.split('/')[0]\r\n}\r\n\r\nfunction isNodeBuiltin(name: string): boolean {\r\n if (name.startsWith('node:')) return true\r\n return NODE_BUILTINS.has(name)\r\n}\r\n\r\nfunction isPathAlias(importPath: string, tsconfigPaths: Set<string>): boolean {\r\n // Common convention aliases\r\n if (importPath.startsWith('@/') || importPath === '@') return true\r\n if (importPath.startsWith('~/') || importPath.startsWith('#/')) return true\r\n\r\n // Check against tsconfig.json paths\r\n for (const prefix of tsconfigPaths) {\r\n if (importPath === prefix || importPath.startsWith(prefix + '/')) return true\r\n }\r\n\r\n return false\r\n}\r\n\r\nexport const hallucinatedImportsRule: Rule = {\r\n id: 'hallucinated-imports',\r\n name: 'Hallucinated Imports',\r\n description: 'Detects imports of packages not declared in package.json and not Node.js built-ins',\r\n category: 'reliability',\r\n severity: 'critical',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs'],\r\n\r\n check(file: FileContext, project: ProjectContext): Finding[] {\r\n if (!project.packageJson) return []\r\n\r\n const findings: Finding[] = []\r\n const seen = new Set<string>()\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n // Skip comments\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n\r\n for (const pattern of LINE_IMPORT_PATTERNS) {\r\n const match = pattern.exec(line)\r\n if (!match) continue\r\n\r\n const importPath = match[1]\r\n const pkgName = getPackageName(importPath)\r\n\r\n if (seen.has(pkgName)) continue\r\n seen.add(pkgName)\r\n\r\n if (isPathAlias(importPath, project.tsconfigPaths)) continue\r\n if (isNodeBuiltin(pkgName)) continue\r\n if (IMPLICIT_PACKAGES.has(importPath) || IMPLICIT_PACKAGES.has(pkgName)) continue\r\n if (project.declaredDependencies.has(pkgName)) continue\r\n\r\n findings.push({\r\n ruleId: 'hallucinated-imports',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: match.index + 1,\r\n message: `Package \"${pkgName}\" is imported but not in package.json`,\r\n severity: 'critical',\r\n category: 'reliability',\r\n })\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isApiRoute } from '../utils/patterns.js'\r\n\r\n// Routes that typically don't need auth\r\nconst AUTH_EXEMPT_PATTERNS = [\r\n /auth/i,\r\n /login/i,\r\n /signup/i,\r\n /register/i,\r\n /callback/i,\r\n /webhook/i,\r\n /health/i,\r\n /ping/i,\r\n /cron/i,\r\n /inngest/i,\r\n /stripe/i,\r\n /public/i,\r\n]\r\n\r\n// Patterns that indicate auth is being checked\r\nconst AUTH_PATTERNS = [\r\n /getServerSession\\s*\\(/,\r\n /getSession\\s*\\(/,\r\n /\\.auth\\.getUser\\s*\\(/,\r\n /auth\\(\\)/,\r\n /authenticate\\s*\\(/,\r\n /isAuthenticated/,\r\n /requireAuth/,\r\n /withAuth/,\r\n /NextAuth/,\r\n /getToken\\s*\\(/,\r\n /verifyToken\\s*\\(/,\r\n /jwt\\.verify\\s*\\(/,\r\n /createRouteHandlerClient/,\r\n /createServerComponentClient/,\r\n /authorization/i,\r\n /bearer/i,\r\n]\r\n\r\nexport const authChecksRule: Rule = {\r\n id: 'auth-checks',\r\n name: 'Missing Auth Checks',\r\n description: 'Detects API routes that lack authentication checks',\r\n category: 'security',\r\n severity: 'critical',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, project: ProjectContext): Finding[] {\r\n if (!isApiRoute(file.relativePath)) return []\r\n\r\n // Check if route is exempt\r\n for (const pattern of AUTH_EXEMPT_PATTERNS) {\r\n if (pattern.test(file.relativePath)) return []\r\n }\r\n\r\n // If project uses middleware-based auth, downgrade to info\r\n const severity = project.hasAuthMiddleware ? 'info' as const : 'critical' as const\r\n\r\n // Check if any auth pattern exists in the file\r\n for (const pattern of AUTH_PATTERNS) {\r\n if (pattern.test(file.content)) return []\r\n }\r\n\r\n // Find the line where the handler is exported\r\n let handlerLine = 1\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (/export\\s+(async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE|handler)/i.test(file.lines[i])) {\r\n handlerLine = i + 1\r\n break\r\n }\r\n }\r\n\r\n const message = project.hasAuthMiddleware\r\n ? 'API route has no inline auth check (middleware auth detected — verify coverage)'\r\n : 'API route has no authentication check'\r\n\r\n return [{\r\n ruleId: 'auth-checks',\r\n file: file.relativePath,\r\n line: handlerLine,\r\n column: 1,\r\n message,\r\n severity,\r\n category: 'security',\r\n }]\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isClientComponent, isCommentLine } from '../utils/patterns.js'\r\n\r\n// Server-only env vars that should never appear in client code\r\nconst SERVER_ENV_PATTERN = /process\\.env\\.(?!NEXT_PUBLIC_)([A-Z][A-Z0-9_]*)/g\r\n\r\n// Env var names that are definitely server-only sensitive\r\nconst SENSITIVE_ENV_NAMES = new Set([\r\n 'DATABASE_URL',\r\n 'SUPABASE_SERVICE_ROLE_KEY',\r\n 'STRIPE_SECRET_KEY',\r\n 'STRIPE_WEBHOOK_SECRET',\r\n 'OPENAI_API_KEY',\r\n 'ANTHROPIC_API_KEY',\r\n 'AWS_SECRET_ACCESS_KEY',\r\n 'JWT_SECRET',\r\n 'SESSION_SECRET',\r\n 'REDIS_URL',\r\n 'SMTP_PASSWORD',\r\n 'SENDGRID_API_KEY',\r\n])\r\n\r\nexport const envExposureRule: Rule = {\r\n id: 'env-exposure',\r\n name: 'Environment Variable Exposure',\r\n description: 'Detects server environment variables used in client components and .env files not in .gitignore',\r\n category: 'security',\r\n severity: 'critical',\r\n fileExtensions: [],\r\n\r\n check(file: FileContext, project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n // Check 1: .env not in .gitignore\r\n if (file.relativePath === '.gitignore') {\r\n if (!project.envInGitignore) {\r\n findings.push({\r\n ruleId: 'env-exposure',\r\n file: file.relativePath,\r\n line: 1,\r\n column: 1,\r\n message: '.env is not listed in .gitignore — secrets may be committed',\r\n severity: 'critical',\r\n category: 'security',\r\n })\r\n }\r\n return findings\r\n }\r\n\r\n // Check 2: Server env vars in client components\r\n if (!['ts', 'tsx', 'js', 'jsx'].includes(file.ext)) return findings\r\n if (!isClientComponent(file.content)) return findings\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n const regex = new RegExp(SERVER_ENV_PATTERN.source, SERVER_ENV_PATTERN.flags)\r\n let match: RegExpExecArray | null\r\n while ((match = regex.exec(line)) !== null) {\r\n const envName = match[1]\r\n const isSensitive = SENSITIVE_ENV_NAMES.has(envName)\r\n findings.push({\r\n ruleId: 'env-exposure',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: match.index + 1,\r\n message: isSensitive\r\n ? `Sensitive server env var \"${envName}\" used in client component`\r\n : `Server env var \"${envName}\" used in client component (will be undefined at runtime)`,\r\n severity: isSensitive ? 'critical' : 'warning',\r\n category: 'security',\r\n })\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isApiRoute, isCommentLine } from '../utils/patterns.js'\r\n\r\nexport const errorHandlingRule: Rule = {\r\n id: 'error-handling',\r\n name: 'Missing Error Handling',\r\n description: 'Detects API routes without try/catch and empty catch blocks',\r\n category: 'reliability',\r\n severity: 'warning',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n // Check 1: API routes without try/catch\r\n if (isApiRoute(file.relativePath)) {\r\n const hasTryCatch = /try\\s*\\{/.test(file.content)\r\n if (!hasTryCatch) {\r\n let handlerLine = 1\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (/export\\s+(async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE|handler)/i.test(file.lines[i])) {\r\n handlerLine = i + 1\r\n break\r\n }\r\n }\r\n findings.push({\r\n ruleId: 'error-handling',\r\n file: file.relativePath,\r\n line: handlerLine,\r\n column: 1,\r\n message: 'API route handler has no try/catch block',\r\n severity: 'warning',\r\n category: 'reliability',\r\n })\r\n }\r\n }\r\n\r\n // Check 2: Empty catch blocks (all files)\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n\r\n // Single-line empty catch: catch (...) {} or catch {}\r\n if (/catch\\s*(\\([^)]*\\))?\\s*\\{\\s*\\}/.test(line)) {\r\n findings.push({\r\n ruleId: 'error-handling',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('catch') + 1,\r\n message: 'Empty catch block silently swallows errors',\r\n severity: 'warning',\r\n category: 'reliability',\r\n })\r\n continue\r\n }\r\n\r\n // Multi-line empty catch: catch (...) {\\n}\r\n if (/catch\\s*(\\([^)]*\\))?\\s*\\{\\s*$/.test(line)) {\r\n const nextLine = file.lines[i + 1]?.trim()\r\n if (nextLine === '}') {\r\n findings.push({\r\n ruleId: 'error-handling',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('catch') + 1,\r\n message: 'Empty catch block silently swallows errors',\r\n severity: 'warning',\r\n category: 'reliability',\r\n })\r\n }\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isApiRoute } from '../utils/patterns.js'\r\n\r\nconst VALIDATION_PATTERNS = [\r\n /\\.parse\\s*\\(/,\r\n /\\.safeParse\\s*\\(/,\r\n /\\.validate\\s*\\(/,\r\n /\\.validateSync\\s*\\(/,\r\n /Joi\\.object/,\r\n /z\\.object/,\r\n /z\\.string/,\r\n /z\\.number/,\r\n /z\\.array/,\r\n /yup\\.object/,\r\n /ajv/i,\r\n /typebox/i,\r\n /valibot/i,\r\n /typeof\\s+.*body/,\r\n]\r\n\r\nconst BODY_ACCESS_PATTERNS = [\r\n /req\\.body/,\r\n /request\\.json\\s*\\(\\)/,\r\n /req\\.json\\s*\\(\\)/,\r\n]\r\n\r\nexport const inputValidationRule: Rule = {\r\n id: 'input-validation',\r\n name: 'Missing Input Validation',\r\n description: 'Detects API routes that access request body without validation',\r\n category: 'security',\r\n severity: 'warning',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n if (!isApiRoute(file.relativePath)) return []\r\n\r\n const accessesBody = BODY_ACCESS_PATTERNS.some(p => p.test(file.content))\r\n if (!accessesBody) return []\r\n\r\n const hasValidation = VALIDATION_PATTERNS.some(p => p.test(file.content))\r\n if (hasValidation) return []\r\n\r\n let bodyLine = 1\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (BODY_ACCESS_PATTERNS.some(p => p.test(file.lines[i]))) {\r\n bodyLine = i + 1\r\n break\r\n }\r\n }\r\n\r\n return [{\r\n ruleId: 'input-validation',\r\n file: file.relativePath,\r\n line: bodyLine,\r\n column: 1,\r\n message: 'Request body accessed without validation (consider using Zod, Yup, or similar)',\r\n severity: 'warning',\r\n category: 'security',\r\n }]\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isApiRoute } from '../utils/patterns.js'\r\n\r\n// Patterns indicating rate limiting is present\r\nconst RATE_LIMIT_PATTERNS = [\r\n /rateLimit/i,\r\n /rateLimiter/i,\r\n /rate-limit/i,\r\n /upstash.*ratelimit/i,\r\n /Ratelimit/,\r\n /@upstash\\/ratelimit/,\r\n /express-rate-limit/,\r\n /limiter/i,\r\n /throttle/i,\r\n /slidingWindow/,\r\n /fixedWindow/,\r\n /tokenBucket/,\r\n]\r\n\r\n// Routes that typically don't need rate limiting\r\nconst EXEMPT_PATTERNS = [\r\n /health/i,\r\n /ping/i,\r\n /webhook/i,\r\n /cron/i,\r\n /inngest/i,\r\n]\r\n\r\nexport const rateLimitingRule: Rule = {\r\n id: 'rate-limiting',\r\n name: 'Missing Rate Limiting',\r\n description: 'Detects API routes without rate limiting',\r\n category: 'security',\r\n severity: 'warning',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n if (!isApiRoute(file.relativePath)) return []\r\n\r\n // Check if route is exempt\r\n for (const pattern of EXEMPT_PATTERNS) {\r\n if (pattern.test(file.relativePath)) return []\r\n }\r\n\r\n // Check if rate limiting exists in the file\r\n for (const pattern of RATE_LIMIT_PATTERNS) {\r\n if (pattern.test(file.content)) return []\r\n }\r\n\r\n // Find the handler line\r\n let handlerLine = 1\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (/export\\s+(async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE|handler)/i.test(file.lines[i])) {\r\n handlerLine = i + 1\r\n break\r\n }\r\n }\r\n\r\n return [{\r\n ruleId: 'rate-limiting',\r\n file: file.relativePath,\r\n line: handlerLine,\r\n column: 1,\r\n message: 'API route has no rate limiting',\r\n severity: 'warning',\r\n category: 'security',\r\n }]\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine } from '../utils/patterns.js'\r\n\r\nexport const corsConfigRule: Rule = {\r\n id: 'cors-config',\r\n name: 'Permissive CORS',\r\n description: 'Detects overly permissive CORS configuration',\r\n category: 'security',\r\n severity: 'warning',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n\r\n if (/['\"]Access-Control-Allow-Origin['\"]\\s*[,:]\\s*['\"]\\*['\"]/.test(line)) {\r\n findings.push({\r\n ruleId: 'cors-config',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('Access-Control') + 1,\r\n message: 'Access-Control-Allow-Origin set to \"*\" allows any domain',\r\n severity: 'warning',\r\n category: 'security',\r\n })\r\n }\r\n\r\n if (/cors\\(\\s*\\)/.test(line)) {\r\n findings.push({\r\n ruleId: 'cors-config',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('cors(') + 1,\r\n message: 'cors() called without config allows all origins',\r\n severity: 'warning',\r\n category: 'security',\r\n })\r\n }\r\n\r\n if (/origin\\s*:\\s*['\"]\\*['\"]/.test(line)) {\r\n findings.push({\r\n ruleId: 'cors-config',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('origin') + 1,\r\n message: 'CORS origin set to \"*\" allows any domain',\r\n severity: 'warning',\r\n category: 'security',\r\n })\r\n }\r\n\r\n if (/origin\\s*:\\s*true/.test(line)) {\r\n findings.push({\r\n ruleId: 'cors-config',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('origin') + 1,\r\n message: 'CORS origin set to true mirrors any requesting origin',\r\n severity: 'warning',\r\n category: 'security',\r\n })\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine } from '../utils/patterns.js'\r\n\r\nconst CONSOLE_LOG_THRESHOLD = 5\r\nconst ANY_TYPE_THRESHOLD = 5\r\nconst COMMENTED_CODE_THRESHOLD = 3\r\n\r\nexport const aiSmellsRule: Rule = {\r\n id: 'ai-smells',\r\n name: 'AI Code Smells',\r\n description: 'Detects TODOs, placeholder functions, excessive console.log, any types, and commented-out code',\r\n category: 'ai-quality',\r\n severity: 'info',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n let consoleLogCount = 0\r\n let anyTypeCount = 0\r\n let commentedCodeRun = 0\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n const line = file.lines[i]\r\n const trimmed = line.trim()\r\n\r\n // Inside block comments — only check for commented code detection\r\n if (file.commentMap[i]) {\r\n commentedCodeRun = 0\r\n continue\r\n }\r\n\r\n // TODO / FIXME / HACK / XXX comments (single-line only)\r\n if (trimmed.startsWith('//')) {\r\n const todoMatch = trimmed.match(/\\/\\/\\s*(TODO|FIXME|HACK|XXX)\\b(.*)/)\r\n if (todoMatch) {\r\n findings.push({\r\n ruleId: 'ai-smells',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf(todoMatch[1]) + 1,\r\n message: `${todoMatch[1]} comment: ${todoMatch[2].trim() || '(no description)'}`,\r\n severity: 'info',\r\n category: 'ai-quality',\r\n })\r\n }\r\n\r\n // Commented-out code detection\r\n const commentContent = trimmed.slice(2).trim()\r\n const looksLikeCode = /^(import |export |const |let |var |function |if |for |while |return |await |async |class |switch )/.test(commentContent) ||\r\n /[{};=]$/.test(commentContent) ||\r\n /^\\w+\\(/.test(commentContent)\r\n\r\n if (looksLikeCode) {\r\n commentedCodeRun++\r\n if (commentedCodeRun === COMMENTED_CODE_THRESHOLD) {\r\n findings.push({\r\n ruleId: 'ai-smells',\r\n file: file.relativePath,\r\n line: i + 1 - COMMENTED_CODE_THRESHOLD + 1,\r\n column: 1,\r\n message: `${COMMENTED_CODE_THRESHOLD}+ consecutive lines of commented-out code`,\r\n severity: 'info',\r\n category: 'ai-quality',\r\n })\r\n }\r\n } else {\r\n commentedCodeRun = 0\r\n }\r\n continue\r\n }\r\n\r\n commentedCodeRun = 0\r\n\r\n // Placeholder functions\r\n if (/(?:throw new Error|throw Error)\\s*\\(\\s*['\"]not implemented['\"]/i.test(line)) {\r\n findings.push({\r\n ruleId: 'ai-smells',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: 1,\r\n message: 'Placeholder \"not implemented\" function',\r\n severity: 'warning',\r\n category: 'ai-quality',\r\n })\r\n }\r\n\r\n // console.log\r\n if (/console\\.log\\s*\\(/.test(line)) {\r\n consoleLogCount++\r\n }\r\n\r\n // Excessive `any` types (only in actual type positions)\r\n if (/:\\s*any\\b/.test(line) || /\\bas\\s+any\\b/.test(line) || /<any>/.test(line)) {\r\n anyTypeCount++\r\n }\r\n }\r\n\r\n if (consoleLogCount > CONSOLE_LOG_THRESHOLD) {\r\n findings.push({\r\n ruleId: 'ai-smells',\r\n file: file.relativePath,\r\n line: 1,\r\n column: 1,\r\n message: `${consoleLogCount} console.log statements (consider a proper logger)`,\r\n severity: 'warning',\r\n category: 'ai-quality',\r\n })\r\n }\r\n\r\n if (anyTypeCount > ANY_TYPE_THRESHOLD) {\r\n findings.push({\r\n ruleId: 'ai-smells',\r\n file: file.relativePath,\r\n line: 1,\r\n column: 1,\r\n message: `${anyTypeCount} uses of \"any\" type (consider proper typing)`,\r\n severity: 'warning',\r\n category: 'ai-quality',\r\n })\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine } from '../utils/patterns.js'\r\n\r\nexport const unsafeHtmlRule: Rule = {\r\n id: 'unsafe-html',\r\n name: 'Unsafe HTML Rendering',\r\n description: 'Detects dangerouslySetInnerHTML and other XSS vectors in JSX/DOM code',\r\n category: 'security',\r\n severity: 'critical',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n\r\n // Only match dangerouslySetInnerHTML in JSX attribute position (after = or with {)\r\n // Avoids matching inside string literals, variable names, or regex patterns\r\n if (/dangerouslySetInnerHTML\\s*=/.test(line) || /dangerouslySetInnerHTML\\s*:/.test(line)) {\r\n findings.push({\r\n ruleId: 'unsafe-html',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('dangerouslySetInnerHTML') + 1,\r\n message: 'dangerouslySetInnerHTML is an XSS risk — sanitize with DOMPurify or similar',\r\n severity: 'critical',\r\n category: 'security',\r\n })\r\n }\r\n\r\n // innerHTML assignment (actual DOM mutation, not inside a string)\r\n if (/\\w\\.innerHTML\\s*=/.test(line)) {\r\n findings.push({\r\n ruleId: 'unsafe-html',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('.innerHTML') + 1,\r\n message: 'Direct innerHTML assignment is an XSS risk',\r\n severity: 'critical',\r\n category: 'security',\r\n })\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine } from '../utils/patterns.js'\r\n\r\n// Patterns for unsafe SQL construction\r\nconst SQL_INJECTION_PATTERNS = [\r\n // Template literals with SQL keywords and interpolation\r\n { pattern: /`\\s*(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE)\\b[^`]*\\$\\{/, message: 'SQL query built with template literal interpolation — use parameterized queries' },\r\n // String concatenation with SQL\r\n { pattern: /(?:SELECT|INSERT|UPDATE|DELETE|DROP)\\b.*['\"]?\\s*\\+\\s*\\w/, message: 'SQL query built with string concatenation — use parameterized queries' },\r\n // .query() or .execute() with template literal\r\n { pattern: /\\.(?:query|execute|raw)\\s*\\(\\s*`[^`]*\\$\\{/, message: 'Database query with template literal interpolation — use parameterized queries' },\r\n]\r\n\r\nexport const sqlInjectionRule: Rule = {\r\n id: 'sql-injection',\r\n name: 'SQL Injection Risk',\r\n description: 'Detects SQL queries built with string interpolation or concatenation',\r\n category: 'security',\r\n severity: 'critical',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n\r\n for (const { pattern, message } of SQL_INJECTION_PATTERNS) {\r\n if (pattern.test(line)) {\r\n findings.push({\r\n ruleId: 'sql-injection',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: 1,\r\n message,\r\n severity: 'critical',\r\n category: 'security',\r\n })\r\n break // One finding per line is enough\r\n }\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule } from '../types.js'\r\nimport { secretsRule } from './secrets.js'\r\nimport { hallucinatedImportsRule } from './hallucinated-imports.js'\r\nimport { authChecksRule } from './auth-checks.js'\r\nimport { envExposureRule } from './env-exposure.js'\r\nimport { errorHandlingRule } from './error-handling.js'\r\nimport { inputValidationRule } from './input-validation.js'\r\nimport { rateLimitingRule } from './rate-limiting.js'\r\nimport { corsConfigRule } from './cors-config.js'\r\nimport { aiSmellsRule } from './ai-smells.js'\r\nimport { unsafeHtmlRule } from './unsafe-html.js'\r\nimport { sqlInjectionRule } from './sql-injection.js'\r\n\r\nexport const rules: Rule[] = [\r\n secretsRule,\r\n hallucinatedImportsRule,\r\n authChecksRule,\r\n envExposureRule,\r\n errorHandlingRule,\r\n inputValidationRule,\r\n rateLimitingRule,\r\n corsConfigRule,\r\n aiSmellsRule,\r\n unsafeHtmlRule,\r\n sqlInjectionRule,\r\n]\r\n","import { walkFiles, readFileContext, buildProjectContext } from './utils/file-walker.js'\r\nimport { isLineSuppressed } from './utils/patterns.js'\r\nimport { getVersion } from './utils/version.js'\r\nimport { calculateScores, summarizeFindings } from './scorer.js'\r\nimport { rules } from './rules/index.js'\r\nimport type { Finding, ScanOptions, ScanResult } from './types.js'\r\nimport { resolve } from 'node:path'\r\n\r\nexport async function scan(options: ScanOptions): Promise<ScanResult> {\r\n const start = performance.now()\r\n const root = resolve(options.path)\r\n\r\n const filePaths = await walkFiles(root, options.ignore)\r\n const project = await buildProjectContext(root, filePaths)\r\n\r\n const findings: Finding[] = []\r\n\r\n for (const relativePath of filePaths) {\r\n const file = await readFileContext(root, relativePath)\r\n if (!file) continue\r\n\r\n for (const rule of rules) {\r\n if (\r\n rule.fileExtensions.length > 0 &&\r\n !rule.fileExtensions.includes(file.ext)\r\n ) {\r\n continue\r\n }\r\n\r\n const ruleFindings = rule.check(file, project)\r\n\r\n for (const finding of ruleFindings) {\r\n if (!isLineSuppressed(file.lines, finding.line - 1, finding.ruleId)) {\r\n findings.push(finding)\r\n }\r\n }\r\n }\r\n }\r\n\r\n const { overallScore, categoryScores } = calculateScores(findings)\r\n const summary = summarizeFindings(findings)\r\n\r\n return {\r\n version: getVersion(),\r\n scannedPath: options.path,\r\n filesScanned: filePaths.length,\r\n scanDurationMs: Math.round(performance.now() - start),\r\n findings,\r\n overallScore,\r\n categoryScores,\r\n summary,\r\n }\r\n}\r\n","import pc from 'picocolors'\r\nimport type { ScanResult, Finding } from './types.js'\r\n\r\nconst SEVERITY_COLORS = {\r\n critical: pc.red,\r\n warning: pc.yellow,\r\n info: pc.blue,\r\n} as const\r\n\r\nconst SEVERITY_LABELS = {\r\n critical: 'CRIT',\r\n warning: 'WARN',\r\n info: 'INFO',\r\n} as const\r\n\r\nfunction scoreColor(score: number): (s: string) => string {\r\n if (score >= 80) return pc.green\r\n if (score >= 50) return pc.yellow\r\n return pc.red\r\n}\r\n\r\nfunction groupByFile(findings: Finding[]): Map<string, Finding[]> {\r\n const map = new Map<string, Finding[]>()\r\n for (const f of findings) {\r\n const group = map.get(f.file) ?? []\r\n group.push(f)\r\n map.set(f.file, group)\r\n }\r\n return map\r\n}\r\n\r\nexport function reportPretty(result: ScanResult): string {\r\n const lines: string[] = []\r\n\r\n lines.push('')\r\n lines.push(pc.bold(' prodlint') + pc.dim(` v${result.version}`))\r\n lines.push(pc.dim(` Scanned ${result.filesScanned} files in ${result.scanDurationMs}ms`))\r\n lines.push('')\r\n\r\n // Findings grouped by file\r\n if (result.findings.length > 0) {\r\n const grouped = groupByFile(result.findings)\r\n for (const [file, findings] of grouped) {\r\n lines.push(pc.underline(file))\r\n for (const f of findings) {\r\n const color = SEVERITY_COLORS[f.severity]\r\n const label = SEVERITY_LABELS[f.severity]\r\n lines.push(\r\n ` ${pc.dim(`${f.line}:${f.column}`)} ${color(label)} ${f.message} ${pc.dim(f.ruleId)}`,\r\n )\r\n }\r\n lines.push('')\r\n }\r\n }\r\n\r\n // Category scores\r\n lines.push(pc.bold(' Scores'))\r\n for (const cs of result.categoryScores) {\r\n const color = scoreColor(cs.score)\r\n const bar = renderBar(cs.score)\r\n lines.push(` ${cs.category.padEnd(14)} ${color(String(cs.score).padStart(3))} ${bar} ${pc.dim(`(${cs.findingCount} issues)`)}`)\r\n }\r\n lines.push('')\r\n\r\n // Overall\r\n const overallColor = scoreColor(result.overallScore)\r\n lines.push(pc.bold(` Overall: ${overallColor(String(result.overallScore))}/100`))\r\n lines.push('')\r\n\r\n // Summary line\r\n const { critical, warning, info } = result.summary\r\n const parts: string[] = []\r\n if (critical > 0) parts.push(pc.red(`${critical} critical`))\r\n if (warning > 0) parts.push(pc.yellow(`${warning} warnings`))\r\n if (info > 0) parts.push(pc.blue(`${info} info`))\r\n if (parts.length === 0) {\r\n lines.push(pc.green(' No issues found!'))\r\n } else {\r\n lines.push(` ${parts.join(pc.dim(' · '))}`)\r\n }\r\n lines.push('')\r\n\r\n // Badge\r\n const badgeColor = result.overallScore >= 80 ? 'brightgreen' : result.overallScore >= 60 ? 'yellow' : 'red'\r\n const badgeUrl = `https://img.shields.io/badge/prodlint-${result.overallScore}%2F100-${badgeColor}`\r\n lines.push(pc.dim(' Add to your README:'))\r\n lines.push(pc.dim(` [](https://prodlint.com)`))\r\n lines.push('')\r\n\r\n return lines.join('\\n')\r\n}\r\n\r\nexport function reportJson(result: ScanResult): string {\r\n return JSON.stringify(result, null, 2)\r\n}\r\n\r\nfunction renderBar(score: number): string {\r\n const width = 20\r\n const filled = Math.round((score / 100) * width)\r\n const empty = width - filled\r\n const color = scoreColor(score)\r\n return color('█'.repeat(filled)) + pc.dim('░'.repeat(empty))\r\n}\r\n"],"mappings":"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","names":["resolve","resolve"]}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/file-walker.ts","../src/utils/patterns.ts","../src/utils/version.ts","../src/scorer.ts","../src/rules/secrets.ts","../src/rules/hallucinated-imports.ts","../src/rules/auth-checks.ts","../src/rules/env-exposure.ts","../src/rules/error-handling.ts","../src/rules/input-validation.ts","../src/rules/rate-limiting.ts","../src/rules/cors-config.ts","../src/rules/ai-smells.ts","../src/rules/unsafe-html.ts","../src/rules/sql-injection.ts","../src/rules/index.ts","../src/scanner.ts"],"sourcesContent":["import fg from 'fast-glob'\r\nimport { readFile, stat, realpath } from 'node:fs/promises'\r\nimport { resolve, extname, sep } from 'node:path'\r\nimport { buildCommentMap } from './patterns.js'\r\nimport type { FileContext, ProjectContext } from '../types.js'\r\n\r\nconst DEFAULT_IGNORES = [\r\n '**/node_modules/**',\r\n '**/dist/**',\r\n '**/build/**',\r\n '**/.next/**',\r\n '**/.git/**',\r\n '**/coverage/**',\r\n '**/*.min.js',\r\n '**/*.min.css',\r\n '**/package-lock.json',\r\n '**/yarn.lock',\r\n '**/pnpm-lock.yaml',\r\n '**/bun.lockb',\r\n '**/*.map',\r\n '**/*.d.ts',\r\n]\r\n\r\nconst SCAN_EXTENSIONS = [\r\n 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs',\r\n 'json',\r\n]\r\n\r\nconst MAX_FILE_SIZE = 1024 * 1024 // 1MB\r\n\r\nexport async function walkFiles(\r\n root: string,\r\n extraIgnores: string[] = [],\r\n): Promise<string[]> {\r\n const patterns = SCAN_EXTENSIONS.map(ext => `**/*.${ext}`)\r\n // Also grab .env files (dotfiles)\r\n patterns.push('**/.env', '**/.env.*')\r\n // Also grab .gitignore\r\n patterns.push('**/.gitignore')\r\n\r\n const files = await fg(patterns, {\r\n cwd: root,\r\n ignore: [...DEFAULT_IGNORES, ...extraIgnores],\r\n absolute: false,\r\n dot: true,\r\n followSymbolicLinks: false,\r\n })\r\n\r\n return files.sort()\r\n}\r\n\r\nexport async function readFileContext(\r\n root: string,\r\n relativePath: string,\r\n): Promise<FileContext | null> {\r\n try {\r\n const absolutePath = resolve(root, relativePath)\r\n\r\n // Verify the resolved path stays within the project root\r\n const realRoot = await realpath(root)\r\n const realFile = await realpath(absolutePath)\r\n if (!realFile.startsWith(realRoot + sep) && realFile !== realRoot) return null\r\n\r\n // Skip files over 1MB\r\n const fileStats = await stat(absolutePath)\r\n if (fileStats.size > MAX_FILE_SIZE) return null\r\n\r\n const content = await readFile(absolutePath, 'utf-8')\r\n const lines = content.split(/\\r?\\n|\\r/)\r\n return {\r\n absolutePath,\r\n relativePath,\r\n content,\r\n lines,\r\n ext: extname(relativePath).slice(1), // remove leading dot\r\n commentMap: buildCommentMap(lines),\r\n }\r\n } catch {\r\n return null\r\n }\r\n}\r\n\r\nexport async function buildProjectContext(\r\n root: string,\r\n files: string[],\r\n): Promise<ProjectContext> {\r\n let packageJson: Record<string, unknown> | null = null\r\n let declaredDependencies = new Set<string>()\r\n let tsconfigPaths = new Set<string>()\r\n let hasAuthMiddleware = false\r\n let gitignoreContent: string | null = null\r\n let envInGitignore = false\r\n\r\n // Read package.json\r\n try {\r\n const raw = await readFile(resolve(root, 'package.json'), 'utf-8')\r\n packageJson = JSON.parse(raw)\r\n const deps = {\r\n ...(packageJson?.dependencies as Record<string, string> ?? {}),\r\n ...(packageJson?.devDependencies as Record<string, string> ?? {}),\r\n ...(packageJson?.peerDependencies as Record<string, string> ?? {}),\r\n }\r\n declaredDependencies = new Set(Object.keys(deps))\r\n } catch {\r\n // No package.json or invalid JSON\r\n }\r\n\r\n // Read tsconfig.json to extract path aliases\r\n try {\r\n const raw = await readFile(resolve(root, 'tsconfig.json'), 'utf-8')\r\n // Strip single-line comments (tsconfig allows them)\r\n const stripped = raw.replace(/\\/\\/.*$/gm, '')\r\n const tsconfig = JSON.parse(stripped)\r\n const paths = tsconfig?.compilerOptions?.paths as Record<string, string[]> | undefined\r\n if (paths) {\r\n for (const alias of Object.keys(paths)) {\r\n // @components/* → @components\r\n // @/* → @/\r\n const prefix = alias.replace(/\\/?\\*$/, '')\r\n if (prefix) tsconfigPaths.add(prefix)\r\n }\r\n }\r\n } catch {\r\n // No tsconfig.json\r\n }\r\n\r\n // Check for auth middleware (middleware.ts or middleware.js in root)\r\n try {\r\n for (const name of ['middleware.ts', 'middleware.js', 'src/middleware.ts', 'src/middleware.js']) {\r\n try {\r\n const content = await readFile(resolve(root, name), 'utf-8')\r\n const authPatterns = [\r\n /getSession/i, /getUser/i, /auth\\(\\)/, /withAuth/i,\r\n /clerkMiddleware/i, /authMiddleware/i, /NextAuth/i,\r\n /supabase.*auth/i, /createMiddlewareClient/i,\r\n /getToken/i, /verifyToken/i, /jwt/i, /updateSession/i,\r\n ]\r\n if (authPatterns.some(p => p.test(content))) {\r\n hasAuthMiddleware = true\r\n break\r\n }\r\n } catch {\r\n // File doesn't exist\r\n }\r\n }\r\n } catch {\r\n // No middleware\r\n }\r\n\r\n // Read .gitignore\r\n try {\r\n gitignoreContent = await readFile(resolve(root, '.gitignore'), 'utf-8')\r\n envInGitignore = /^\\.env$/m.test(gitignoreContent) ||\r\n /^\\.env\\*$/m.test(gitignoreContent) ||\r\n /^\\.env\\.\\*$/m.test(gitignoreContent) ||\r\n /^\\.env\\.local$/m.test(gitignoreContent)\r\n } catch {\r\n // No .gitignore\r\n }\r\n\r\n return {\r\n root,\r\n packageJson,\r\n declaredDependencies,\r\n tsconfigPaths,\r\n hasAuthMiddleware,\r\n gitignoreContent,\r\n envInGitignore,\r\n allFiles: files,\r\n }\r\n}\r\n","/**\r\n * Check if a file path looks like a Next.js API route\r\n */\r\nexport function isApiRoute(relativePath: string): boolean {\r\n // Next.js App Router: app/**/route.ts\r\n if (/app\\/.*route\\.(ts|js|tsx|jsx)$/.test(relativePath)) return true\r\n // Next.js Pages Router: pages/api/**\r\n if (/pages\\/api\\//.test(relativePath)) return true\r\n return false\r\n}\r\n\r\n/**\r\n * Check if a file is a React client component (has \"use client\" directive)\r\n */\r\nexport function isClientComponent(content: string): boolean {\r\n // Must be at the very top (possibly after comments/whitespace)\r\n const firstLines = content.slice(0, 500)\r\n return /^(['\"])use client\\1/m.test(firstLines)\r\n}\r\n\r\n/**\r\n * Check if a file is a React server component (default in App Router)\r\n */\r\nexport function isServerComponent(content: string): boolean {\r\n return !isClientComponent(content)\r\n}\r\n\r\n/**\r\n * Pre-compute which lines are inside block comments.\r\n * Returns a boolean array where true = line is inside a block comment.\r\n */\r\nexport function buildCommentMap(lines: string[]): boolean[] {\r\n const map = new Array<boolean>(lines.length).fill(false)\r\n let inBlock = false\r\n\r\n for (let i = 0; i < lines.length; i++) {\r\n const line = lines[i]\r\n\r\n if (inBlock) {\r\n map[i] = true\r\n if (line.includes('*/')) {\r\n inBlock = false\r\n }\r\n continue\r\n }\r\n\r\n // Check for block comment start (not preceded by code on the same line that matters)\r\n const trimmed = line.trim()\r\n if (trimmed.startsWith('/*')) {\r\n map[i] = true\r\n if (!trimmed.includes('*/')) {\r\n inBlock = true\r\n }\r\n continue\r\n }\r\n\r\n // Inline /* ... */ on a line with code — don't mark the whole line\r\n // But a line that's ONLY a mid-block comment continuation (starts with *)\r\n if (trimmed.startsWith('*')) {\r\n // Could be JSDoc or block comment continuation\r\n map[i] = true\r\n }\r\n }\r\n\r\n return map\r\n}\r\n\r\n/**\r\n * Check if a line is a comment (single-line or inside block comment)\r\n */\r\nexport function isCommentLine(lines: string[], lineIndex: number, commentMap: boolean[]): boolean {\r\n if (commentMap[lineIndex]) return true\r\n const trimmed = lines[lineIndex]?.trim() ?? ''\r\n return trimmed.startsWith('//')\r\n}\r\n\r\n/**\r\n * Check if line is suppressed by a prodlint-disable comment\r\n */\r\nexport function isLineSuppressed(\r\n lines: string[],\r\n lineIndex: number,\r\n ruleId: string,\r\n): boolean {\r\n // Check file-level disable\r\n for (const line of lines) {\r\n const trimmed = line.trim()\r\n if (trimmed === '' || trimmed.startsWith('//') || trimmed.startsWith('/*')) {\r\n const match = trimmed.match(/prodlint-disable\\s+(.+)/)\r\n if (match) {\r\n const ids = match[1].split(/[\\s,]+/).filter(Boolean)\r\n if (ids.includes(ruleId)) return true\r\n }\r\n continue\r\n }\r\n break // Stop at first non-comment, non-empty line\r\n }\r\n\r\n // Check line-level disable (previous line)\r\n if (lineIndex > 0) {\r\n const prevLine = lines[lineIndex - 1].trim()\r\n const match = prevLine.match(/prodlint-disable-next-line\\s+(.+)/)\r\n if (match) {\r\n const ids = match[1].split(/[\\s,]+/).filter(Boolean)\r\n if (ids.includes(ruleId)) return true\r\n }\r\n }\r\n\r\n return false\r\n}\r\n\r\n/**\r\n * Known Node.js built-in modules\r\n */\r\nexport const NODE_BUILTINS = new Set([\r\n 'assert', 'async_hooks', 'buffer', 'child_process', 'cluster',\r\n 'console', 'constants', 'crypto', 'dgram', 'diagnostics_channel',\r\n 'dns', 'domain', 'events', 'fs', 'http', 'http2', 'https',\r\n 'inspector', 'module', 'net', 'os', 'path', 'perf_hooks',\r\n 'process', 'punycode', 'querystring', 'readline', 'repl',\r\n 'stream', 'string_decoder', 'sys', 'timers', 'tls', 'trace_events',\r\n 'tty', 'url', 'util', 'v8', 'vm', 'wasi', 'worker_threads', 'zlib',\r\n // node: prefixed are handled separately\r\n])\r\n","import { readFileSync } from 'node:fs'\r\nimport { fileURLToPath } from 'node:url'\r\nimport { dirname, resolve } from 'node:path'\r\n\r\nexport function getVersion(): string {\r\n try {\r\n // Try relative to the built file location\r\n const dir = dirname(fileURLToPath(import.meta.url))\r\n const pkg = JSON.parse(\r\n readFileSync(resolve(dir, '..', 'package.json'), 'utf-8'),\r\n )\r\n return pkg.version ?? '0.0.0'\r\n } catch {\r\n return '0.0.0'\r\n }\r\n}\r\n","import type { Category, CategoryScore, Finding } from './types.js'\r\n\r\nconst CATEGORIES: Category[] = ['security', 'reliability', 'performance', 'ai-quality']\r\n\r\nconst DEDUCTIONS: Record<string, number> = {\r\n critical: 10,\r\n warning: 3,\r\n info: 1,\r\n}\r\n\r\nexport function calculateScores(findings: Finding[]): {\r\n overallScore: number\r\n categoryScores: CategoryScore[]\r\n} {\r\n const categoryScores: CategoryScore[] = CATEGORIES.map(category => {\r\n const categoryFindings = findings.filter(f => f.category === category)\r\n let score = 100\r\n for (const f of categoryFindings) {\r\n score -= DEDUCTIONS[f.severity] ?? 0\r\n }\r\n return {\r\n category,\r\n score: Math.max(0, score),\r\n findingCount: categoryFindings.length,\r\n }\r\n })\r\n\r\n const overallScore = Math.round(\r\n categoryScores.reduce((sum, c) => sum + c.score, 0) / CATEGORIES.length,\r\n )\r\n\r\n return { overallScore, categoryScores }\r\n}\r\n\r\nexport function summarizeFindings(findings: Finding[]) {\r\n return {\r\n critical: findings.filter(f => f.severity === 'critical').length,\r\n warning: findings.filter(f => f.severity === 'warning').length,\r\n info: findings.filter(f => f.severity === 'info').length,\r\n }\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine } from '../utils/patterns.js'\r\n\r\nconst SECRET_PATTERNS: { name: string; pattern: RegExp }[] = [\r\n { name: 'Stripe secret key', pattern: /sk_live_[a-zA-Z0-9]{20,}/ },\r\n { name: 'Stripe test key', pattern: /sk_test_[a-zA-Z0-9]{20,}/ },\r\n { name: 'AWS access key', pattern: /AKIA[0-9A-Z]{16}/ },\r\n { name: 'AWS secret key', pattern: /(?:aws_secret_access_key|AWS_SECRET)\\s*[=:]\\s*['\"]?[A-Za-z0-9/+=]{40}['\"]?/ },\r\n { name: 'Supabase service role key', pattern: /eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.[A-Za-z0-9_-]{50,}\\.[A-Za-z0-9_-]{20,}/ },\r\n { name: 'OpenAI API key', pattern: /sk-[a-zA-Z0-9]{20,}T3BlbkFJ[a-zA-Z0-9]{20,}/ },\r\n { name: 'GitHub token', pattern: /gh[ps]_[A-Za-z0-9_]{36,}/ },\r\n { name: 'GitHub fine-grained token', pattern: /github_pat_[A-Za-z0-9_]{22,}/ },\r\n { name: 'Generic API key assignment', pattern: /(?:api_key|apikey|api_secret|secret_key|private_key)\\s*[=:]\\s*['\"][a-zA-Z0-9_\\-]{20,}['\"]/ },\r\n { name: 'SendGrid API key', pattern: /SG\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9_-]{43}/ },\r\n]\r\n\r\nexport const secretsRule: Rule = {\r\n id: 'secrets',\r\n name: 'Hardcoded Secrets',\r\n description: 'Detects hardcoded API keys, tokens, and credentials in source code',\r\n category: 'security',\r\n severity: 'critical',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'json'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n const line = file.lines[i]\r\n\r\n // Skip comments (single-line and block)\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n for (const { name, pattern } of SECRET_PATTERNS) {\r\n const match = pattern.exec(line)\r\n if (match) {\r\n findings.push({\r\n ruleId: 'secrets',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: match.index + 1,\r\n message: `Hardcoded ${name} detected`,\r\n severity: 'critical',\r\n category: 'security',\r\n })\r\n }\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine, NODE_BUILTINS } from '../utils/patterns.js'\r\n\r\n// Packages that are commonly available without being in package.json\r\nconst IMPLICIT_PACKAGES = new Set([\r\n 'react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime',\r\n 'next', 'next/server', 'next/image', 'next/link', 'next/font',\r\n 'next/navigation', 'next/headers', 'next/dynamic', 'next/script',\r\n 'next/router', 'next/head', 'next/app', 'next/document',\r\n])\r\n\r\n// Line-level patterns to capture import sources\r\nconst LINE_IMPORT_PATTERNS = [\r\n /from\\s+['\"]([^'\"./][^'\"]*)['\"]/,\r\n /require\\s*\\(\\s*['\"]([^'\"./][^'\"]*)['\"]\\s*\\)/,\r\n /import\\s*\\(\\s*['\"]([^'\"./][^'\"]*)['\"]\\s*\\)/,\r\n]\r\n\r\nfunction getPackageName(importPath: string): string {\r\n if (importPath.startsWith('@')) {\r\n const parts = importPath.split('/')\r\n return parts.slice(0, 2).join('/')\r\n }\r\n return importPath.split('/')[0]\r\n}\r\n\r\nfunction isNodeBuiltin(name: string): boolean {\r\n if (name.startsWith('node:')) return true\r\n return NODE_BUILTINS.has(name)\r\n}\r\n\r\nfunction isPathAlias(importPath: string, tsconfigPaths: Set<string>): boolean {\r\n // Common convention aliases\r\n if (importPath.startsWith('@/') || importPath === '@') return true\r\n if (importPath.startsWith('~/') || importPath.startsWith('#/')) return true\r\n\r\n // Check against tsconfig.json paths\r\n for (const prefix of tsconfigPaths) {\r\n if (importPath === prefix || importPath.startsWith(prefix + '/')) return true\r\n }\r\n\r\n return false\r\n}\r\n\r\nexport const hallucinatedImportsRule: Rule = {\r\n id: 'hallucinated-imports',\r\n name: 'Hallucinated Imports',\r\n description: 'Detects imports of packages not declared in package.json and not Node.js built-ins',\r\n category: 'reliability',\r\n severity: 'critical',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs'],\r\n\r\n check(file: FileContext, project: ProjectContext): Finding[] {\r\n if (!project.packageJson) return []\r\n\r\n const findings: Finding[] = []\r\n const seen = new Set<string>()\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n // Skip comments\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n\r\n for (const pattern of LINE_IMPORT_PATTERNS) {\r\n const match = pattern.exec(line)\r\n if (!match) continue\r\n\r\n const importPath = match[1]\r\n const pkgName = getPackageName(importPath)\r\n\r\n if (seen.has(pkgName)) continue\r\n seen.add(pkgName)\r\n\r\n if (isPathAlias(importPath, project.tsconfigPaths)) continue\r\n if (isNodeBuiltin(pkgName)) continue\r\n if (IMPLICIT_PACKAGES.has(importPath) || IMPLICIT_PACKAGES.has(pkgName)) continue\r\n if (project.declaredDependencies.has(pkgName)) continue\r\n\r\n findings.push({\r\n ruleId: 'hallucinated-imports',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: match.index + 1,\r\n message: `Package \"${pkgName}\" is imported but not in package.json`,\r\n severity: 'critical',\r\n category: 'reliability',\r\n })\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isApiRoute } from '../utils/patterns.js'\r\n\r\n// Routes that typically don't need auth\r\nconst AUTH_EXEMPT_PATTERNS = [\r\n /auth/i,\r\n /login/i,\r\n /signup/i,\r\n /register/i,\r\n /callback/i,\r\n /webhook/i,\r\n /health/i,\r\n /ping/i,\r\n /cron/i,\r\n /inngest/i,\r\n /stripe/i,\r\n /public/i,\r\n]\r\n\r\n// Patterns that indicate auth is being checked\r\nconst AUTH_PATTERNS = [\r\n /getServerSession\\s*\\(/,\r\n /getSession\\s*\\(/,\r\n /\\.auth\\.getUser\\s*\\(/,\r\n /auth\\(\\)/,\r\n /authenticate\\s*\\(/,\r\n /isAuthenticated/,\r\n /requireAuth/,\r\n /withAuth/,\r\n /NextAuth/,\r\n /getToken\\s*\\(/,\r\n /verifyToken\\s*\\(/,\r\n /jwt\\.verify\\s*\\(/,\r\n /createRouteHandlerClient/,\r\n /createServerComponentClient/,\r\n /authorization/i,\r\n /bearer/i,\r\n]\r\n\r\nexport const authChecksRule: Rule = {\r\n id: 'auth-checks',\r\n name: 'Missing Auth Checks',\r\n description: 'Detects API routes that lack authentication checks',\r\n category: 'security',\r\n severity: 'critical',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, project: ProjectContext): Finding[] {\r\n if (!isApiRoute(file.relativePath)) return []\r\n\r\n // Check if route is exempt\r\n for (const pattern of AUTH_EXEMPT_PATTERNS) {\r\n if (pattern.test(file.relativePath)) return []\r\n }\r\n\r\n // If project uses middleware-based auth, downgrade to info\r\n const severity = project.hasAuthMiddleware ? 'info' as const : 'critical' as const\r\n\r\n // Check if any auth pattern exists in the file\r\n for (const pattern of AUTH_PATTERNS) {\r\n if (pattern.test(file.content)) return []\r\n }\r\n\r\n // Find the line where the handler is exported\r\n let handlerLine = 1\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (/export\\s+(async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE|handler)/i.test(file.lines[i])) {\r\n handlerLine = i + 1\r\n break\r\n }\r\n }\r\n\r\n const message = project.hasAuthMiddleware\r\n ? 'API route has no inline auth check (middleware auth detected — verify coverage)'\r\n : 'API route has no authentication check'\r\n\r\n return [{\r\n ruleId: 'auth-checks',\r\n file: file.relativePath,\r\n line: handlerLine,\r\n column: 1,\r\n message,\r\n severity,\r\n category: 'security',\r\n }]\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isClientComponent, isCommentLine } from '../utils/patterns.js'\r\n\r\n// Server-only env vars that should never appear in client code\r\nconst SERVER_ENV_PATTERN = /process\\.env\\.(?!NEXT_PUBLIC_)([A-Z][A-Z0-9_]*)/g\r\n\r\n// Env var names that are definitely server-only sensitive\r\nconst SENSITIVE_ENV_NAMES = new Set([\r\n 'DATABASE_URL',\r\n 'SUPABASE_SERVICE_ROLE_KEY',\r\n 'STRIPE_SECRET_KEY',\r\n 'STRIPE_WEBHOOK_SECRET',\r\n 'OPENAI_API_KEY',\r\n 'ANTHROPIC_API_KEY',\r\n 'AWS_SECRET_ACCESS_KEY',\r\n 'JWT_SECRET',\r\n 'SESSION_SECRET',\r\n 'REDIS_URL',\r\n 'SMTP_PASSWORD',\r\n 'SENDGRID_API_KEY',\r\n])\r\n\r\nexport const envExposureRule: Rule = {\r\n id: 'env-exposure',\r\n name: 'Environment Variable Exposure',\r\n description: 'Detects server environment variables used in client components and .env files not in .gitignore',\r\n category: 'security',\r\n severity: 'critical',\r\n fileExtensions: [],\r\n\r\n check(file: FileContext, project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n // Check 1: .env not in .gitignore\r\n if (file.relativePath === '.gitignore') {\r\n if (!project.envInGitignore) {\r\n findings.push({\r\n ruleId: 'env-exposure',\r\n file: file.relativePath,\r\n line: 1,\r\n column: 1,\r\n message: '.env is not listed in .gitignore — secrets may be committed',\r\n severity: 'critical',\r\n category: 'security',\r\n })\r\n }\r\n return findings\r\n }\r\n\r\n // Check 2: Server env vars in client components\r\n if (!['ts', 'tsx', 'js', 'jsx'].includes(file.ext)) return findings\r\n if (!isClientComponent(file.content)) return findings\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n const regex = new RegExp(SERVER_ENV_PATTERN.source, SERVER_ENV_PATTERN.flags)\r\n let match: RegExpExecArray | null\r\n while ((match = regex.exec(line)) !== null) {\r\n const envName = match[1]\r\n const isSensitive = SENSITIVE_ENV_NAMES.has(envName)\r\n findings.push({\r\n ruleId: 'env-exposure',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: match.index + 1,\r\n message: isSensitive\r\n ? `Sensitive server env var \"${envName}\" used in client component`\r\n : `Server env var \"${envName}\" used in client component (will be undefined at runtime)`,\r\n severity: isSensitive ? 'critical' : 'warning',\r\n category: 'security',\r\n })\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isApiRoute, isCommentLine } from '../utils/patterns.js'\r\n\r\nexport const errorHandlingRule: Rule = {\r\n id: 'error-handling',\r\n name: 'Missing Error Handling',\r\n description: 'Detects API routes without try/catch and empty catch blocks',\r\n category: 'reliability',\r\n severity: 'warning',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n // Check 1: API routes without try/catch\r\n if (isApiRoute(file.relativePath)) {\r\n const hasTryCatch = /try\\s*\\{/.test(file.content)\r\n if (!hasTryCatch) {\r\n let handlerLine = 1\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (/export\\s+(async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE|handler)/i.test(file.lines[i])) {\r\n handlerLine = i + 1\r\n break\r\n }\r\n }\r\n findings.push({\r\n ruleId: 'error-handling',\r\n file: file.relativePath,\r\n line: handlerLine,\r\n column: 1,\r\n message: 'API route handler has no try/catch block',\r\n severity: 'warning',\r\n category: 'reliability',\r\n })\r\n }\r\n }\r\n\r\n // Check 2: Empty catch blocks (all files)\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n\r\n // Single-line empty catch: catch (...) {} or catch {}\r\n if (/catch\\s*(\\([^)]*\\))?\\s*\\{\\s*\\}/.test(line)) {\r\n findings.push({\r\n ruleId: 'error-handling',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('catch') + 1,\r\n message: 'Empty catch block silently swallows errors',\r\n severity: 'warning',\r\n category: 'reliability',\r\n })\r\n continue\r\n }\r\n\r\n // Multi-line empty catch: catch (...) {\\n}\r\n if (/catch\\s*(\\([^)]*\\))?\\s*\\{\\s*$/.test(line)) {\r\n const nextLine = file.lines[i + 1]?.trim()\r\n if (nextLine === '}') {\r\n findings.push({\r\n ruleId: 'error-handling',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('catch') + 1,\r\n message: 'Empty catch block silently swallows errors',\r\n severity: 'warning',\r\n category: 'reliability',\r\n })\r\n }\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isApiRoute } from '../utils/patterns.js'\r\n\r\nconst VALIDATION_PATTERNS = [\r\n /\\.parse\\s*\\(/,\r\n /\\.safeParse\\s*\\(/,\r\n /\\.validate\\s*\\(/,\r\n /\\.validateSync\\s*\\(/,\r\n /Joi\\.object/,\r\n /z\\.object/,\r\n /z\\.string/,\r\n /z\\.number/,\r\n /z\\.array/,\r\n /yup\\.object/,\r\n /ajv/i,\r\n /typebox/i,\r\n /valibot/i,\r\n /typeof\\s+.*body/,\r\n]\r\n\r\nconst BODY_ACCESS_PATTERNS = [\r\n /req\\.body/,\r\n /request\\.json\\s*\\(\\)/,\r\n /req\\.json\\s*\\(\\)/,\r\n]\r\n\r\nexport const inputValidationRule: Rule = {\r\n id: 'input-validation',\r\n name: 'Missing Input Validation',\r\n description: 'Detects API routes that access request body without validation',\r\n category: 'security',\r\n severity: 'warning',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n if (!isApiRoute(file.relativePath)) return []\r\n\r\n const accessesBody = BODY_ACCESS_PATTERNS.some(p => p.test(file.content))\r\n if (!accessesBody) return []\r\n\r\n const hasValidation = VALIDATION_PATTERNS.some(p => p.test(file.content))\r\n if (hasValidation) return []\r\n\r\n let bodyLine = 1\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (BODY_ACCESS_PATTERNS.some(p => p.test(file.lines[i]))) {\r\n bodyLine = i + 1\r\n break\r\n }\r\n }\r\n\r\n return [{\r\n ruleId: 'input-validation',\r\n file: file.relativePath,\r\n line: bodyLine,\r\n column: 1,\r\n message: 'Request body accessed without validation (consider using Zod, Yup, or similar)',\r\n severity: 'warning',\r\n category: 'security',\r\n }]\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isApiRoute } from '../utils/patterns.js'\r\n\r\n// Patterns indicating rate limiting is present\r\nconst RATE_LIMIT_PATTERNS = [\r\n /rateLimit/i,\r\n /rateLimiter/i,\r\n /rate-limit/i,\r\n /upstash.*ratelimit/i,\r\n /Ratelimit/,\r\n /@upstash\\/ratelimit/,\r\n /express-rate-limit/,\r\n /limiter/i,\r\n /throttle/i,\r\n /slidingWindow/,\r\n /fixedWindow/,\r\n /tokenBucket/,\r\n]\r\n\r\n// Routes that typically don't need rate limiting\r\nconst EXEMPT_PATTERNS = [\r\n /health/i,\r\n /ping/i,\r\n /webhook/i,\r\n /cron/i,\r\n /inngest/i,\r\n]\r\n\r\nexport const rateLimitingRule: Rule = {\r\n id: 'rate-limiting',\r\n name: 'Missing Rate Limiting',\r\n description: 'Detects API routes without rate limiting',\r\n category: 'security',\r\n severity: 'warning',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n if (!isApiRoute(file.relativePath)) return []\r\n\r\n // Check if route is exempt\r\n for (const pattern of EXEMPT_PATTERNS) {\r\n if (pattern.test(file.relativePath)) return []\r\n }\r\n\r\n // Check if rate limiting exists in the file\r\n for (const pattern of RATE_LIMIT_PATTERNS) {\r\n if (pattern.test(file.content)) return []\r\n }\r\n\r\n // Find the handler line\r\n let handlerLine = 1\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (/export\\s+(async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE|handler)/i.test(file.lines[i])) {\r\n handlerLine = i + 1\r\n break\r\n }\r\n }\r\n\r\n return [{\r\n ruleId: 'rate-limiting',\r\n file: file.relativePath,\r\n line: handlerLine,\r\n column: 1,\r\n message: 'API route has no rate limiting',\r\n severity: 'warning',\r\n category: 'security',\r\n }]\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine } from '../utils/patterns.js'\r\n\r\nexport const corsConfigRule: Rule = {\r\n id: 'cors-config',\r\n name: 'Permissive CORS',\r\n description: 'Detects overly permissive CORS configuration',\r\n category: 'security',\r\n severity: 'warning',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n\r\n if (/['\"]Access-Control-Allow-Origin['\"]\\s*[,:]\\s*['\"]\\*['\"]/.test(line)) {\r\n findings.push({\r\n ruleId: 'cors-config',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('Access-Control') + 1,\r\n message: 'Access-Control-Allow-Origin set to \"*\" allows any domain',\r\n severity: 'warning',\r\n category: 'security',\r\n })\r\n }\r\n\r\n if (/cors\\(\\s*\\)/.test(line)) {\r\n findings.push({\r\n ruleId: 'cors-config',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('cors(') + 1,\r\n message: 'cors() called without config allows all origins',\r\n severity: 'warning',\r\n category: 'security',\r\n })\r\n }\r\n\r\n if (/origin\\s*:\\s*['\"]\\*['\"]/.test(line)) {\r\n findings.push({\r\n ruleId: 'cors-config',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('origin') + 1,\r\n message: 'CORS origin set to \"*\" allows any domain',\r\n severity: 'warning',\r\n category: 'security',\r\n })\r\n }\r\n\r\n if (/origin\\s*:\\s*true/.test(line)) {\r\n findings.push({\r\n ruleId: 'cors-config',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('origin') + 1,\r\n message: 'CORS origin set to true mirrors any requesting origin',\r\n severity: 'warning',\r\n category: 'security',\r\n })\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine } from '../utils/patterns.js'\r\n\r\nconst CONSOLE_LOG_THRESHOLD = 5\r\nconst ANY_TYPE_THRESHOLD = 5\r\nconst COMMENTED_CODE_THRESHOLD = 3\r\n\r\nexport const aiSmellsRule: Rule = {\r\n id: 'ai-smells',\r\n name: 'AI Code Smells',\r\n description: 'Detects TODOs, placeholder functions, excessive console.log, any types, and commented-out code',\r\n category: 'ai-quality',\r\n severity: 'info',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n let consoleLogCount = 0\r\n let anyTypeCount = 0\r\n let commentedCodeRun = 0\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n const line = file.lines[i]\r\n const trimmed = line.trim()\r\n\r\n // Inside block comments — only check for commented code detection\r\n if (file.commentMap[i]) {\r\n commentedCodeRun = 0\r\n continue\r\n }\r\n\r\n // TODO / FIXME / HACK / XXX comments (single-line only)\r\n if (trimmed.startsWith('//')) {\r\n const todoMatch = trimmed.match(/\\/\\/\\s*(TODO|FIXME|HACK|XXX)\\b(.*)/)\r\n if (todoMatch) {\r\n findings.push({\r\n ruleId: 'ai-smells',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf(todoMatch[1]) + 1,\r\n message: `${todoMatch[1]} comment: ${todoMatch[2].trim() || '(no description)'}`,\r\n severity: 'info',\r\n category: 'ai-quality',\r\n })\r\n }\r\n\r\n // Commented-out code detection\r\n const commentContent = trimmed.slice(2).trim()\r\n const looksLikeCode = /^(import |export |const |let |var |function |if |for |while |return |await |async |class |switch )/.test(commentContent) ||\r\n /[{};=]$/.test(commentContent) ||\r\n /^\\w+\\(/.test(commentContent)\r\n\r\n if (looksLikeCode) {\r\n commentedCodeRun++\r\n if (commentedCodeRun === COMMENTED_CODE_THRESHOLD) {\r\n findings.push({\r\n ruleId: 'ai-smells',\r\n file: file.relativePath,\r\n line: i + 1 - COMMENTED_CODE_THRESHOLD + 1,\r\n column: 1,\r\n message: `${COMMENTED_CODE_THRESHOLD}+ consecutive lines of commented-out code`,\r\n severity: 'info',\r\n category: 'ai-quality',\r\n })\r\n }\r\n } else {\r\n commentedCodeRun = 0\r\n }\r\n continue\r\n }\r\n\r\n commentedCodeRun = 0\r\n\r\n // Placeholder functions\r\n if (/(?:throw new Error|throw Error)\\s*\\(\\s*['\"]not implemented['\"]/i.test(line)) {\r\n findings.push({\r\n ruleId: 'ai-smells',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: 1,\r\n message: 'Placeholder \"not implemented\" function',\r\n severity: 'warning',\r\n category: 'ai-quality',\r\n })\r\n }\r\n\r\n // console.log\r\n if (/console\\.log\\s*\\(/.test(line)) {\r\n consoleLogCount++\r\n }\r\n\r\n // Excessive `any` types (only in actual type positions)\r\n if (/:\\s*any\\b/.test(line) || /\\bas\\s+any\\b/.test(line) || /<any>/.test(line)) {\r\n anyTypeCount++\r\n }\r\n }\r\n\r\n if (consoleLogCount > CONSOLE_LOG_THRESHOLD) {\r\n findings.push({\r\n ruleId: 'ai-smells',\r\n file: file.relativePath,\r\n line: 1,\r\n column: 1,\r\n message: `${consoleLogCount} console.log statements (consider a proper logger)`,\r\n severity: 'warning',\r\n category: 'ai-quality',\r\n })\r\n }\r\n\r\n if (anyTypeCount > ANY_TYPE_THRESHOLD) {\r\n findings.push({\r\n ruleId: 'ai-smells',\r\n file: file.relativePath,\r\n line: 1,\r\n column: 1,\r\n message: `${anyTypeCount} uses of \"any\" type (consider proper typing)`,\r\n severity: 'warning',\r\n category: 'ai-quality',\r\n })\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine } from '../utils/patterns.js'\r\n\r\nexport const unsafeHtmlRule: Rule = {\r\n id: 'unsafe-html',\r\n name: 'Unsafe HTML Rendering',\r\n description: 'Detects dangerouslySetInnerHTML and other XSS vectors in JSX/DOM code',\r\n category: 'security',\r\n severity: 'critical',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n\r\n // Only match dangerouslySetInnerHTML in JSX attribute position (after = or with {)\r\n // Avoids matching inside string literals, variable names, or regex patterns\r\n if (/dangerouslySetInnerHTML\\s*=/.test(line) || /dangerouslySetInnerHTML\\s*:/.test(line)) {\r\n findings.push({\r\n ruleId: 'unsafe-html',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('dangerouslySetInnerHTML') + 1,\r\n message: 'dangerouslySetInnerHTML is an XSS risk — sanitize with DOMPurify or similar',\r\n severity: 'critical',\r\n category: 'security',\r\n })\r\n }\r\n\r\n // innerHTML assignment (actual DOM mutation, not inside a string)\r\n if (/\\w\\.innerHTML\\s*=/.test(line)) {\r\n findings.push({\r\n ruleId: 'unsafe-html',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('.innerHTML') + 1,\r\n message: 'Direct innerHTML assignment is an XSS risk',\r\n severity: 'critical',\r\n category: 'security',\r\n })\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine } from '../utils/patterns.js'\r\n\r\n// Patterns for unsafe SQL construction\r\nconst SQL_INJECTION_PATTERNS = [\r\n // Template literals with SQL keywords and interpolation\r\n { pattern: /`\\s*(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE)\\b[^`]*\\$\\{/, message: 'SQL query built with template literal interpolation — use parameterized queries' },\r\n // String concatenation with SQL\r\n { pattern: /(?:SELECT|INSERT|UPDATE|DELETE|DROP)\\b.*['\"]?\\s*\\+\\s*\\w/, message: 'SQL query built with string concatenation — use parameterized queries' },\r\n // .query() or .execute() with template literal\r\n { pattern: /\\.(?:query|execute|raw)\\s*\\(\\s*`[^`]*\\$\\{/, message: 'Database query with template literal interpolation — use parameterized queries' },\r\n]\r\n\r\nexport const sqlInjectionRule: Rule = {\r\n id: 'sql-injection',\r\n name: 'SQL Injection Risk',\r\n description: 'Detects SQL queries built with string interpolation or concatenation',\r\n category: 'security',\r\n severity: 'critical',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n\r\n for (const { pattern, message } of SQL_INJECTION_PATTERNS) {\r\n if (pattern.test(line)) {\r\n findings.push({\r\n ruleId: 'sql-injection',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: 1,\r\n message,\r\n severity: 'critical',\r\n category: 'security',\r\n })\r\n break // One finding per line is enough\r\n }\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule } from '../types.js'\r\nimport { secretsRule } from './secrets.js'\r\nimport { hallucinatedImportsRule } from './hallucinated-imports.js'\r\nimport { authChecksRule } from './auth-checks.js'\r\nimport { envExposureRule } from './env-exposure.js'\r\nimport { errorHandlingRule } from './error-handling.js'\r\nimport { inputValidationRule } from './input-validation.js'\r\nimport { rateLimitingRule } from './rate-limiting.js'\r\nimport { corsConfigRule } from './cors-config.js'\r\nimport { aiSmellsRule } from './ai-smells.js'\r\nimport { unsafeHtmlRule } from './unsafe-html.js'\r\nimport { sqlInjectionRule } from './sql-injection.js'\r\n\r\nexport const rules: Rule[] = [\r\n secretsRule,\r\n hallucinatedImportsRule,\r\n authChecksRule,\r\n envExposureRule,\r\n errorHandlingRule,\r\n inputValidationRule,\r\n rateLimitingRule,\r\n corsConfigRule,\r\n aiSmellsRule,\r\n unsafeHtmlRule,\r\n sqlInjectionRule,\r\n]\r\n","import { walkFiles, readFileContext, buildProjectContext } from './utils/file-walker.js'\r\nimport { isLineSuppressed } from './utils/patterns.js'\r\nimport { getVersion } from './utils/version.js'\r\nimport { calculateScores, summarizeFindings } from './scorer.js'\r\nimport { rules } from './rules/index.js'\r\nimport type { Finding, ScanOptions, ScanResult } from './types.js'\r\nimport { resolve } from 'node:path'\r\n\r\nexport async function scan(options: ScanOptions): Promise<ScanResult> {\r\n const start = performance.now()\r\n const root = resolve(options.path)\r\n\r\n const filePaths = await walkFiles(root, options.ignore)\r\n const project = await buildProjectContext(root, filePaths)\r\n\r\n const findings: Finding[] = []\r\n\r\n for (const relativePath of filePaths) {\r\n const file = await readFileContext(root, relativePath)\r\n if (!file) continue\r\n\r\n for (const rule of rules) {\r\n if (\r\n rule.fileExtensions.length > 0 &&\r\n !rule.fileExtensions.includes(file.ext)\r\n ) {\r\n continue\r\n }\r\n\r\n const ruleFindings = rule.check(file, project)\r\n\r\n for (const finding of ruleFindings) {\r\n if (!isLineSuppressed(file.lines, finding.line - 1, finding.ruleId)) {\r\n findings.push(finding)\r\n }\r\n }\r\n }\r\n }\r\n\r\n const { overallScore, categoryScores } = calculateScores(findings)\r\n const summary = summarizeFindings(findings)\r\n\r\n return {\r\n version: getVersion(),\r\n scannedPath: options.path,\r\n filesScanned: filePaths.length,\r\n scanDurationMs: Math.round(performance.now() - start),\r\n findings,\r\n overallScore,\r\n categoryScores,\r\n summary,\r\n }\r\n}\r\n"],"mappings":"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","names":["resolve","resolve"]}
|
package/dist/mcp.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mcp.ts","../src/utils/file-walker.ts","../src/utils/patterns.ts","../src/utils/version.ts","../src/scorer.ts","../src/rules/secrets.ts","../src/rules/hallucinated-imports.ts","../src/rules/auth-checks.ts","../src/rules/env-exposure.ts","../src/rules/error-handling.ts","../src/rules/input-validation.ts","../src/rules/rate-limiting.ts","../src/rules/cors-config.ts","../src/rules/ai-smells.ts","../src/rules/unsafe-html.ts","../src/rules/sql-injection.ts","../src/rules/index.ts","../src/scanner.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\r\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\r\nimport { z } from 'zod'\r\nimport { stat } from 'node:fs/promises'\r\nimport { resolve } from 'node:path'\r\nimport { scan } from './scanner.js'\r\nimport { getVersion } from './utils/version.js'\r\n\r\nconst server = new McpServer({\r\n name: 'prodlint',\r\n version: getVersion(),\r\n})\r\n\r\nserver.tool(\r\n 'scan',\r\n 'Scan a project for production readiness issues. Returns a 0-100 score with findings across security, reliability, performance, and AI quality categories.',\r\n {\r\n path: z.string().describe('Absolute path to the project directory to scan'),\r\n ignore: z.array(z.string()).optional().describe('Glob patterns to ignore'),\r\n },\r\n async ({ path, ignore }) => {\r\n // Validate the path exists and is a directory\r\n const resolved = resolve(path)\r\n try {\r\n const stats = await stat(resolved)\r\n if (!stats.isDirectory()) {\r\n return {\r\n content: [{ type: 'text' as const, text: `Error: ${path} is not a directory` }],\r\n isError: true,\r\n }\r\n }\r\n } catch {\r\n return {\r\n content: [{ type: 'text' as const, text: `Error: ${path} does not exist or is not accessible` }],\r\n isError: true,\r\n }\r\n }\r\n\r\n const result = await scan({ path: resolved, ignore })\r\n\r\n const summary = [\r\n `## Production Readiness Score: ${result.overallScore}/100`,\r\n '',\r\n `Scanned ${result.filesScanned} files in ${result.scanDurationMs}ms`,\r\n '',\r\n '### Category Scores',\r\n ...result.categoryScores.map(\r\n c => `- **${c.category}**: ${c.score}/100 (${c.findingCount} issues)`,\r\n ),\r\n '',\r\n `### Summary: ${result.summary.critical} critical, ${result.summary.warning} warnings, ${result.summary.info} info`,\r\n ]\r\n\r\n if (result.findings.length > 0) {\r\n summary.push('', '### Findings')\r\n for (const f of result.findings.slice(0, 30)) {\r\n summary.push(\r\n `- **[${f.severity}]** \\`${f.ruleId}\\` ${f.file}:${f.line} — ${f.message}`,\r\n )\r\n }\r\n if (result.findings.length > 30) {\r\n summary.push(`- ...and ${result.findings.length - 30} more findings`)\r\n }\r\n }\r\n\r\n return {\r\n content: [{ type: 'text' as const, text: summary.join('\\n') }],\r\n }\r\n },\r\n)\r\n\r\nasync function main() {\r\n const transport = new StdioServerTransport()\r\n await server.connect(transport)\r\n}\r\n\r\nmain().catch((err) => {\r\n console.error('Prodlint MCP server error:', err instanceof Error ? err.message : 'Unknown error')\r\n process.exit(1)\r\n})\r\n","import fg from 'fast-glob'\r\nimport { readFile, stat, realpath } from 'node:fs/promises'\r\nimport { resolve, extname, sep } from 'node:path'\r\nimport { buildCommentMap } from './patterns.js'\r\nimport type { FileContext, ProjectContext } from '../types.js'\r\n\r\nconst DEFAULT_IGNORES = [\r\n '**/node_modules/**',\r\n '**/dist/**',\r\n '**/build/**',\r\n '**/.next/**',\r\n '**/.git/**',\r\n '**/coverage/**',\r\n '**/*.min.js',\r\n '**/*.min.css',\r\n '**/package-lock.json',\r\n '**/yarn.lock',\r\n '**/pnpm-lock.yaml',\r\n '**/bun.lockb',\r\n '**/*.map',\r\n '**/*.d.ts',\r\n]\r\n\r\nconst SCAN_EXTENSIONS = [\r\n 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs',\r\n 'json',\r\n]\r\n\r\nconst MAX_FILE_SIZE = 1024 * 1024 // 1MB\r\n\r\nexport async function walkFiles(\r\n root: string,\r\n extraIgnores: string[] = [],\r\n): Promise<string[]> {\r\n const patterns = SCAN_EXTENSIONS.map(ext => `**/*.${ext}`)\r\n // Also grab .env files (dotfiles)\r\n patterns.push('**/.env', '**/.env.*')\r\n // Also grab .gitignore\r\n patterns.push('**/.gitignore')\r\n\r\n const files = await fg(patterns, {\r\n cwd: root,\r\n ignore: [...DEFAULT_IGNORES, ...extraIgnores],\r\n absolute: false,\r\n dot: true,\r\n followSymbolicLinks: false,\r\n })\r\n\r\n return files.sort()\r\n}\r\n\r\nexport async function readFileContext(\r\n root: string,\r\n relativePath: string,\r\n): Promise<FileContext | null> {\r\n try {\r\n const absolutePath = resolve(root, relativePath)\r\n\r\n // Verify the resolved path stays within the project root\r\n const realRoot = await realpath(root)\r\n const realFile = await realpath(absolutePath)\r\n if (!realFile.startsWith(realRoot + sep) && realFile !== realRoot) return null\r\n\r\n // Skip files over 1MB\r\n const fileStats = await stat(absolutePath)\r\n if (fileStats.size > MAX_FILE_SIZE) return null\r\n\r\n const content = await readFile(absolutePath, 'utf-8')\r\n const lines = content.split(/\\r?\\n|\\r/)\r\n return {\r\n absolutePath,\r\n relativePath,\r\n content,\r\n lines,\r\n ext: extname(relativePath).slice(1), // remove leading dot\r\n commentMap: buildCommentMap(lines),\r\n }\r\n } catch {\r\n return null\r\n }\r\n}\r\n\r\nexport async function buildProjectContext(\r\n root: string,\r\n files: string[],\r\n): Promise<ProjectContext> {\r\n let packageJson: Record<string, unknown> | null = null\r\n let declaredDependencies = new Set<string>()\r\n let tsconfigPaths = new Set<string>()\r\n let hasAuthMiddleware = false\r\n let gitignoreContent: string | null = null\r\n let envInGitignore = false\r\n\r\n // Read package.json\r\n try {\r\n const raw = await readFile(resolve(root, 'package.json'), 'utf-8')\r\n packageJson = JSON.parse(raw)\r\n const deps = {\r\n ...(packageJson?.dependencies as Record<string, string> ?? {}),\r\n ...(packageJson?.devDependencies as Record<string, string> ?? {}),\r\n ...(packageJson?.peerDependencies as Record<string, string> ?? {}),\r\n }\r\n declaredDependencies = new Set(Object.keys(deps))\r\n } catch {\r\n // No package.json or invalid JSON\r\n }\r\n\r\n // Read tsconfig.json to extract path aliases\r\n try {\r\n const raw = await readFile(resolve(root, 'tsconfig.json'), 'utf-8')\r\n // Strip single-line comments (tsconfig allows them)\r\n const stripped = raw.replace(/\\/\\/.*$/gm, '')\r\n const tsconfig = JSON.parse(stripped)\r\n const paths = tsconfig?.compilerOptions?.paths as Record<string, string[]> | undefined\r\n if (paths) {\r\n for (const alias of Object.keys(paths)) {\r\n // @components/* → @components\r\n // @/* → @/\r\n const prefix = alias.replace(/\\/?\\*$/, '')\r\n if (prefix) tsconfigPaths.add(prefix)\r\n }\r\n }\r\n } catch {\r\n // No tsconfig.json\r\n }\r\n\r\n // Check for auth middleware (middleware.ts or middleware.js in root)\r\n try {\r\n for (const name of ['middleware.ts', 'middleware.js', 'src/middleware.ts', 'src/middleware.js']) {\r\n try {\r\n const content = await readFile(resolve(root, name), 'utf-8')\r\n const authPatterns = [\r\n /getSession/i, /getUser/i, /auth\\(\\)/, /withAuth/i,\r\n /clerkMiddleware/i, /authMiddleware/i, /NextAuth/i,\r\n /supabase.*auth/i, /createMiddlewareClient/i,\r\n /getToken/i, /verifyToken/i, /jwt/i, /updateSession/i,\r\n ]\r\n if (authPatterns.some(p => p.test(content))) {\r\n hasAuthMiddleware = true\r\n break\r\n }\r\n } catch {\r\n // File doesn't exist\r\n }\r\n }\r\n } catch {\r\n // No middleware\r\n }\r\n\r\n // Read .gitignore\r\n try {\r\n gitignoreContent = await readFile(resolve(root, '.gitignore'), 'utf-8')\r\n envInGitignore = /^\\.env$/m.test(gitignoreContent) ||\r\n /^\\.env\\*$/m.test(gitignoreContent) ||\r\n /^\\.env\\.\\*$/m.test(gitignoreContent) ||\r\n /^\\.env\\.local$/m.test(gitignoreContent)\r\n } catch {\r\n // No .gitignore\r\n }\r\n\r\n return {\r\n root,\r\n packageJson,\r\n declaredDependencies,\r\n tsconfigPaths,\r\n hasAuthMiddleware,\r\n gitignoreContent,\r\n envInGitignore,\r\n allFiles: files,\r\n }\r\n}\r\n","/**\r\n * Check if a file path looks like a Next.js API route\r\n */\r\nexport function isApiRoute(relativePath: string): boolean {\r\n // Next.js App Router: app/**/route.ts\r\n if (/app\\/.*route\\.(ts|js|tsx|jsx)$/.test(relativePath)) return true\r\n // Next.js Pages Router: pages/api/**\r\n if (/pages\\/api\\//.test(relativePath)) return true\r\n return false\r\n}\r\n\r\n/**\r\n * Check if a file is a React client component (has \"use client\" directive)\r\n */\r\nexport function isClientComponent(content: string): boolean {\r\n // Must be at the very top (possibly after comments/whitespace)\r\n const firstLines = content.slice(0, 500)\r\n return /^(['\"])use client\\1/m.test(firstLines)\r\n}\r\n\r\n/**\r\n * Check if a file is a React server component (default in App Router)\r\n */\r\nexport function isServerComponent(content: string): boolean {\r\n return !isClientComponent(content)\r\n}\r\n\r\n/**\r\n * Pre-compute which lines are inside block comments.\r\n * Returns a boolean array where true = line is inside a block comment.\r\n */\r\nexport function buildCommentMap(lines: string[]): boolean[] {\r\n const map = new Array<boolean>(lines.length).fill(false)\r\n let inBlock = false\r\n\r\n for (let i = 0; i < lines.length; i++) {\r\n const line = lines[i]\r\n\r\n if (inBlock) {\r\n map[i] = true\r\n if (line.includes('*/')) {\r\n inBlock = false\r\n }\r\n continue\r\n }\r\n\r\n // Check for block comment start (not preceded by code on the same line that matters)\r\n const trimmed = line.trim()\r\n if (trimmed.startsWith('/*')) {\r\n map[i] = true\r\n if (!trimmed.includes('*/')) {\r\n inBlock = true\r\n }\r\n continue\r\n }\r\n\r\n // Inline /* ... */ on a line with code — don't mark the whole line\r\n // But a line that's ONLY a mid-block comment continuation (starts with *)\r\n if (trimmed.startsWith('*')) {\r\n // Could be JSDoc or block comment continuation\r\n map[i] = true\r\n }\r\n }\r\n\r\n return map\r\n}\r\n\r\n/**\r\n * Check if a line is a comment (single-line or inside block comment)\r\n */\r\nexport function isCommentLine(lines: string[], lineIndex: number, commentMap: boolean[]): boolean {\r\n if (commentMap[lineIndex]) return true\r\n const trimmed = lines[lineIndex]?.trim() ?? ''\r\n return trimmed.startsWith('//')\r\n}\r\n\r\n/**\r\n * Check if line is suppressed by a prodlint-disable comment\r\n */\r\nexport function isLineSuppressed(\r\n lines: string[],\r\n lineIndex: number,\r\n ruleId: string,\r\n): boolean {\r\n // Check file-level disable\r\n for (const line of lines) {\r\n const trimmed = line.trim()\r\n if (trimmed === '' || trimmed.startsWith('//') || trimmed.startsWith('/*')) {\r\n const match = trimmed.match(/prodlint-disable\\s+(.+)/)\r\n if (match) {\r\n const ids = match[1].split(/[\\s,]+/).filter(Boolean)\r\n if (ids.includes(ruleId)) return true\r\n }\r\n continue\r\n }\r\n break // Stop at first non-comment, non-empty line\r\n }\r\n\r\n // Check line-level disable (previous line)\r\n if (lineIndex > 0) {\r\n const prevLine = lines[lineIndex - 1].trim()\r\n const match = prevLine.match(/prodlint-disable-next-line\\s+(.+)/)\r\n if (match) {\r\n const ids = match[1].split(/[\\s,]+/).filter(Boolean)\r\n if (ids.includes(ruleId)) return true\r\n }\r\n }\r\n\r\n return false\r\n}\r\n\r\n/**\r\n * Known Node.js built-in modules\r\n */\r\nexport const NODE_BUILTINS = new Set([\r\n 'assert', 'async_hooks', 'buffer', 'child_process', 'cluster',\r\n 'console', 'constants', 'crypto', 'dgram', 'diagnostics_channel',\r\n 'dns', 'domain', 'events', 'fs', 'http', 'http2', 'https',\r\n 'inspector', 'module', 'net', 'os', 'path', 'perf_hooks',\r\n 'process', 'punycode', 'querystring', 'readline', 'repl',\r\n 'stream', 'string_decoder', 'sys', 'timers', 'tls', 'trace_events',\r\n 'tty', 'url', 'util', 'v8', 'vm', 'wasi', 'worker_threads', 'zlib',\r\n // node: prefixed are handled separately\r\n])\r\n","import { readFileSync } from 'node:fs'\r\nimport { fileURLToPath } from 'node:url'\r\nimport { dirname, resolve } from 'node:path'\r\n\r\nexport function getVersion(): string {\r\n try {\r\n // Try relative to the built file location\r\n const dir = dirname(fileURLToPath(import.meta.url))\r\n const pkg = JSON.parse(\r\n readFileSync(resolve(dir, '..', 'package.json'), 'utf-8'),\r\n )\r\n return pkg.version ?? '0.0.0'\r\n } catch {\r\n return '0.0.0'\r\n }\r\n}\r\n","import type { Category, CategoryScore, Finding } from './types.js'\r\n\r\nconst CATEGORIES: Category[] = ['security', 'reliability', 'performance', 'ai-quality']\r\n\r\nconst DEDUCTIONS: Record<string, number> = {\r\n critical: 10,\r\n warning: 3,\r\n info: 1,\r\n}\r\n\r\nexport function calculateScores(findings: Finding[]): {\r\n overallScore: number\r\n categoryScores: CategoryScore[]\r\n} {\r\n const categoryScores: CategoryScore[] = CATEGORIES.map(category => {\r\n const categoryFindings = findings.filter(f => f.category === category)\r\n let score = 100\r\n for (const f of categoryFindings) {\r\n score -= DEDUCTIONS[f.severity] ?? 0\r\n }\r\n return {\r\n category,\r\n score: Math.max(0, score),\r\n findingCount: categoryFindings.length,\r\n }\r\n })\r\n\r\n const overallScore = Math.round(\r\n categoryScores.reduce((sum, c) => sum + c.score, 0) / CATEGORIES.length,\r\n )\r\n\r\n return { overallScore, categoryScores }\r\n}\r\n\r\nexport function summarizeFindings(findings: Finding[]) {\r\n return {\r\n critical: findings.filter(f => f.severity === 'critical').length,\r\n warning: findings.filter(f => f.severity === 'warning').length,\r\n info: findings.filter(f => f.severity === 'info').length,\r\n }\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine } from '../utils/patterns.js'\r\n\r\nconst SECRET_PATTERNS: { name: string; pattern: RegExp }[] = [\r\n { name: 'Stripe secret key', pattern: /sk_live_[a-zA-Z0-9]{20,}/ },\r\n { name: 'Stripe test key', pattern: /sk_test_[a-zA-Z0-9]{20,}/ },\r\n { name: 'AWS access key', pattern: /AKIA[0-9A-Z]{16}/ },\r\n { name: 'AWS secret key', pattern: /(?:aws_secret_access_key|AWS_SECRET)\\s*[=:]\\s*['\"]?[A-Za-z0-9/+=]{40}['\"]?/ },\r\n { name: 'Supabase service role key', pattern: /eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.[A-Za-z0-9_-]{50,}\\.[A-Za-z0-9_-]{20,}/ },\r\n { name: 'OpenAI API key', pattern: /sk-[a-zA-Z0-9]{20,}T3BlbkFJ[a-zA-Z0-9]{20,}/ },\r\n { name: 'GitHub token', pattern: /gh[ps]_[A-Za-z0-9_]{36,}/ },\r\n { name: 'GitHub fine-grained token', pattern: /github_pat_[A-Za-z0-9_]{22,}/ },\r\n { name: 'Generic API key assignment', pattern: /(?:api_key|apikey|api_secret|secret_key|private_key)\\s*[=:]\\s*['\"][a-zA-Z0-9_\\-]{20,}['\"]/ },\r\n { name: 'SendGrid API key', pattern: /SG\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9_-]{43}/ },\r\n]\r\n\r\nexport const secretsRule: Rule = {\r\n id: 'secrets',\r\n name: 'Hardcoded Secrets',\r\n description: 'Detects hardcoded API keys, tokens, and credentials in source code',\r\n category: 'security',\r\n severity: 'critical',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'json'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n const line = file.lines[i]\r\n\r\n // Skip comments (single-line and block)\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n for (const { name, pattern } of SECRET_PATTERNS) {\r\n const match = pattern.exec(line)\r\n if (match) {\r\n findings.push({\r\n ruleId: 'secrets',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: match.index + 1,\r\n message: `Hardcoded ${name} detected`,\r\n severity: 'critical',\r\n category: 'security',\r\n })\r\n }\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine, NODE_BUILTINS } from '../utils/patterns.js'\r\n\r\n// Packages that are commonly available without being in package.json\r\nconst IMPLICIT_PACKAGES = new Set([\r\n 'react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime',\r\n 'next', 'next/server', 'next/image', 'next/link', 'next/font',\r\n 'next/navigation', 'next/headers', 'next/dynamic', 'next/script',\r\n 'next/router', 'next/head', 'next/app', 'next/document',\r\n])\r\n\r\n// Line-level patterns to capture import sources\r\nconst LINE_IMPORT_PATTERNS = [\r\n /from\\s+['\"]([^'\"./][^'\"]*)['\"]/,\r\n /require\\s*\\(\\s*['\"]([^'\"./][^'\"]*)['\"]\\s*\\)/,\r\n /import\\s*\\(\\s*['\"]([^'\"./][^'\"]*)['\"]\\s*\\)/,\r\n]\r\n\r\nfunction getPackageName(importPath: string): string {\r\n if (importPath.startsWith('@')) {\r\n const parts = importPath.split('/')\r\n return parts.slice(0, 2).join('/')\r\n }\r\n return importPath.split('/')[0]\r\n}\r\n\r\nfunction isNodeBuiltin(name: string): boolean {\r\n if (name.startsWith('node:')) return true\r\n return NODE_BUILTINS.has(name)\r\n}\r\n\r\nfunction isPathAlias(importPath: string, tsconfigPaths: Set<string>): boolean {\r\n // Common convention aliases\r\n if (importPath.startsWith('@/') || importPath === '@') return true\r\n if (importPath.startsWith('~/') || importPath.startsWith('#/')) return true\r\n\r\n // Check against tsconfig.json paths\r\n for (const prefix of tsconfigPaths) {\r\n if (importPath === prefix || importPath.startsWith(prefix + '/')) return true\r\n }\r\n\r\n return false\r\n}\r\n\r\nexport const hallucinatedImportsRule: Rule = {\r\n id: 'hallucinated-imports',\r\n name: 'Hallucinated Imports',\r\n description: 'Detects imports of packages not declared in package.json and not Node.js built-ins',\r\n category: 'reliability',\r\n severity: 'critical',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs'],\r\n\r\n check(file: FileContext, project: ProjectContext): Finding[] {\r\n if (!project.packageJson) return []\r\n\r\n const findings: Finding[] = []\r\n const seen = new Set<string>()\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n // Skip comments\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n\r\n for (const pattern of LINE_IMPORT_PATTERNS) {\r\n const match = pattern.exec(line)\r\n if (!match) continue\r\n\r\n const importPath = match[1]\r\n const pkgName = getPackageName(importPath)\r\n\r\n if (seen.has(pkgName)) continue\r\n seen.add(pkgName)\r\n\r\n if (isPathAlias(importPath, project.tsconfigPaths)) continue\r\n if (isNodeBuiltin(pkgName)) continue\r\n if (IMPLICIT_PACKAGES.has(importPath) || IMPLICIT_PACKAGES.has(pkgName)) continue\r\n if (project.declaredDependencies.has(pkgName)) continue\r\n\r\n findings.push({\r\n ruleId: 'hallucinated-imports',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: match.index + 1,\r\n message: `Package \"${pkgName}\" is imported but not in package.json`,\r\n severity: 'critical',\r\n category: 'reliability',\r\n })\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isApiRoute } from '../utils/patterns.js'\r\n\r\n// Routes that typically don't need auth\r\nconst AUTH_EXEMPT_PATTERNS = [\r\n /auth/i,\r\n /login/i,\r\n /signup/i,\r\n /register/i,\r\n /callback/i,\r\n /webhook/i,\r\n /health/i,\r\n /ping/i,\r\n /cron/i,\r\n /inngest/i,\r\n /stripe/i,\r\n /public/i,\r\n]\r\n\r\n// Patterns that indicate auth is being checked\r\nconst AUTH_PATTERNS = [\r\n /getServerSession\\s*\\(/,\r\n /getSession\\s*\\(/,\r\n /\\.auth\\.getUser\\s*\\(/,\r\n /auth\\(\\)/,\r\n /authenticate\\s*\\(/,\r\n /isAuthenticated/,\r\n /requireAuth/,\r\n /withAuth/,\r\n /NextAuth/,\r\n /getToken\\s*\\(/,\r\n /verifyToken\\s*\\(/,\r\n /jwt\\.verify\\s*\\(/,\r\n /createRouteHandlerClient/,\r\n /createServerComponentClient/,\r\n /authorization/i,\r\n /bearer/i,\r\n]\r\n\r\nexport const authChecksRule: Rule = {\r\n id: 'auth-checks',\r\n name: 'Missing Auth Checks',\r\n description: 'Detects API routes that lack authentication checks',\r\n category: 'security',\r\n severity: 'critical',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, project: ProjectContext): Finding[] {\r\n if (!isApiRoute(file.relativePath)) return []\r\n\r\n // Check if route is exempt\r\n for (const pattern of AUTH_EXEMPT_PATTERNS) {\r\n if (pattern.test(file.relativePath)) return []\r\n }\r\n\r\n // If project uses middleware-based auth, downgrade to info\r\n const severity = project.hasAuthMiddleware ? 'info' as const : 'critical' as const\r\n\r\n // Check if any auth pattern exists in the file\r\n for (const pattern of AUTH_PATTERNS) {\r\n if (pattern.test(file.content)) return []\r\n }\r\n\r\n // Find the line where the handler is exported\r\n let handlerLine = 1\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (/export\\s+(async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE|handler)/i.test(file.lines[i])) {\r\n handlerLine = i + 1\r\n break\r\n }\r\n }\r\n\r\n const message = project.hasAuthMiddleware\r\n ? 'API route has no inline auth check (middleware auth detected — verify coverage)'\r\n : 'API route has no authentication check'\r\n\r\n return [{\r\n ruleId: 'auth-checks',\r\n file: file.relativePath,\r\n line: handlerLine,\r\n column: 1,\r\n message,\r\n severity,\r\n category: 'security',\r\n }]\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isClientComponent, isCommentLine } from '../utils/patterns.js'\r\n\r\n// Server-only env vars that should never appear in client code\r\nconst SERVER_ENV_PATTERN = /process\\.env\\.(?!NEXT_PUBLIC_)([A-Z][A-Z0-9_]*)/g\r\n\r\n// Env var names that are definitely server-only sensitive\r\nconst SENSITIVE_ENV_NAMES = new Set([\r\n 'DATABASE_URL',\r\n 'SUPABASE_SERVICE_ROLE_KEY',\r\n 'STRIPE_SECRET_KEY',\r\n 'STRIPE_WEBHOOK_SECRET',\r\n 'OPENAI_API_KEY',\r\n 'ANTHROPIC_API_KEY',\r\n 'AWS_SECRET_ACCESS_KEY',\r\n 'JWT_SECRET',\r\n 'SESSION_SECRET',\r\n 'REDIS_URL',\r\n 'SMTP_PASSWORD',\r\n 'SENDGRID_API_KEY',\r\n])\r\n\r\nexport const envExposureRule: Rule = {\r\n id: 'env-exposure',\r\n name: 'Environment Variable Exposure',\r\n description: 'Detects server environment variables used in client components and .env files not in .gitignore',\r\n category: 'security',\r\n severity: 'critical',\r\n fileExtensions: [],\r\n\r\n check(file: FileContext, project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n // Check 1: .env not in .gitignore\r\n if (file.relativePath === '.gitignore') {\r\n if (!project.envInGitignore) {\r\n findings.push({\r\n ruleId: 'env-exposure',\r\n file: file.relativePath,\r\n line: 1,\r\n column: 1,\r\n message: '.env is not listed in .gitignore — secrets may be committed',\r\n severity: 'critical',\r\n category: 'security',\r\n })\r\n }\r\n return findings\r\n }\r\n\r\n // Check 2: Server env vars in client components\r\n if (!['ts', 'tsx', 'js', 'jsx'].includes(file.ext)) return findings\r\n if (!isClientComponent(file.content)) return findings\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n const regex = new RegExp(SERVER_ENV_PATTERN.source, SERVER_ENV_PATTERN.flags)\r\n let match: RegExpExecArray | null\r\n while ((match = regex.exec(line)) !== null) {\r\n const envName = match[1]\r\n const isSensitive = SENSITIVE_ENV_NAMES.has(envName)\r\n findings.push({\r\n ruleId: 'env-exposure',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: match.index + 1,\r\n message: isSensitive\r\n ? `Sensitive server env var \"${envName}\" used in client component`\r\n : `Server env var \"${envName}\" used in client component (will be undefined at runtime)`,\r\n severity: isSensitive ? 'critical' : 'warning',\r\n category: 'security',\r\n })\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isApiRoute, isCommentLine } from '../utils/patterns.js'\r\n\r\nexport const errorHandlingRule: Rule = {\r\n id: 'error-handling',\r\n name: 'Missing Error Handling',\r\n description: 'Detects API routes without try/catch and empty catch blocks',\r\n category: 'reliability',\r\n severity: 'warning',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n // Check 1: API routes without try/catch\r\n if (isApiRoute(file.relativePath)) {\r\n const hasTryCatch = /try\\s*\\{/.test(file.content)\r\n if (!hasTryCatch) {\r\n let handlerLine = 1\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (/export\\s+(async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE|handler)/i.test(file.lines[i])) {\r\n handlerLine = i + 1\r\n break\r\n }\r\n }\r\n findings.push({\r\n ruleId: 'error-handling',\r\n file: file.relativePath,\r\n line: handlerLine,\r\n column: 1,\r\n message: 'API route handler has no try/catch block',\r\n severity: 'warning',\r\n category: 'reliability',\r\n })\r\n }\r\n }\r\n\r\n // Check 2: Empty catch blocks (all files)\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n\r\n // Single-line empty catch: catch (...) {} or catch {}\r\n if (/catch\\s*(\\([^)]*\\))?\\s*\\{\\s*\\}/.test(line)) {\r\n findings.push({\r\n ruleId: 'error-handling',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('catch') + 1,\r\n message: 'Empty catch block silently swallows errors',\r\n severity: 'warning',\r\n category: 'reliability',\r\n })\r\n continue\r\n }\r\n\r\n // Multi-line empty catch: catch (...) {\\n}\r\n if (/catch\\s*(\\([^)]*\\))?\\s*\\{\\s*$/.test(line)) {\r\n const nextLine = file.lines[i + 1]?.trim()\r\n if (nextLine === '}') {\r\n findings.push({\r\n ruleId: 'error-handling',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('catch') + 1,\r\n message: 'Empty catch block silently swallows errors',\r\n severity: 'warning',\r\n category: 'reliability',\r\n })\r\n }\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isApiRoute } from '../utils/patterns.js'\r\n\r\nconst VALIDATION_PATTERNS = [\r\n /\\.parse\\s*\\(/,\r\n /\\.safeParse\\s*\\(/,\r\n /\\.validate\\s*\\(/,\r\n /\\.validateSync\\s*\\(/,\r\n /Joi\\.object/,\r\n /z\\.object/,\r\n /z\\.string/,\r\n /z\\.number/,\r\n /z\\.array/,\r\n /yup\\.object/,\r\n /ajv/i,\r\n /typebox/i,\r\n /valibot/i,\r\n /typeof\\s+.*body/,\r\n]\r\n\r\nconst BODY_ACCESS_PATTERNS = [\r\n /req\\.body/,\r\n /request\\.json\\s*\\(\\)/,\r\n /req\\.json\\s*\\(\\)/,\r\n]\r\n\r\nexport const inputValidationRule: Rule = {\r\n id: 'input-validation',\r\n name: 'Missing Input Validation',\r\n description: 'Detects API routes that access request body without validation',\r\n category: 'security',\r\n severity: 'warning',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n if (!isApiRoute(file.relativePath)) return []\r\n\r\n const accessesBody = BODY_ACCESS_PATTERNS.some(p => p.test(file.content))\r\n if (!accessesBody) return []\r\n\r\n const hasValidation = VALIDATION_PATTERNS.some(p => p.test(file.content))\r\n if (hasValidation) return []\r\n\r\n let bodyLine = 1\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (BODY_ACCESS_PATTERNS.some(p => p.test(file.lines[i]))) {\r\n bodyLine = i + 1\r\n break\r\n }\r\n }\r\n\r\n return [{\r\n ruleId: 'input-validation',\r\n file: file.relativePath,\r\n line: bodyLine,\r\n column: 1,\r\n message: 'Request body accessed without validation (consider using Zod, Yup, or similar)',\r\n severity: 'warning',\r\n category: 'security',\r\n }]\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isApiRoute } from '../utils/patterns.js'\r\n\r\n// Patterns indicating rate limiting is present\r\nconst RATE_LIMIT_PATTERNS = [\r\n /rateLimit/i,\r\n /rateLimiter/i,\r\n /rate-limit/i,\r\n /upstash.*ratelimit/i,\r\n /Ratelimit/,\r\n /@upstash\\/ratelimit/,\r\n /express-rate-limit/,\r\n /limiter/i,\r\n /throttle/i,\r\n /slidingWindow/,\r\n /fixedWindow/,\r\n /tokenBucket/,\r\n]\r\n\r\n// Routes that typically don't need rate limiting\r\nconst EXEMPT_PATTERNS = [\r\n /health/i,\r\n /ping/i,\r\n /webhook/i,\r\n /cron/i,\r\n /inngest/i,\r\n]\r\n\r\nexport const rateLimitingRule: Rule = {\r\n id: 'rate-limiting',\r\n name: 'Missing Rate Limiting',\r\n description: 'Detects API routes without rate limiting',\r\n category: 'security',\r\n severity: 'warning',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n if (!isApiRoute(file.relativePath)) return []\r\n\r\n // Check if route is exempt\r\n for (const pattern of EXEMPT_PATTERNS) {\r\n if (pattern.test(file.relativePath)) return []\r\n }\r\n\r\n // Check if rate limiting exists in the file\r\n for (const pattern of RATE_LIMIT_PATTERNS) {\r\n if (pattern.test(file.content)) return []\r\n }\r\n\r\n // Find the handler line\r\n let handlerLine = 1\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (/export\\s+(async\\s+)?function\\s+(GET|POST|PUT|PATCH|DELETE|handler)/i.test(file.lines[i])) {\r\n handlerLine = i + 1\r\n break\r\n }\r\n }\r\n\r\n return [{\r\n ruleId: 'rate-limiting',\r\n file: file.relativePath,\r\n line: handlerLine,\r\n column: 1,\r\n message: 'API route has no rate limiting',\r\n severity: 'warning',\r\n category: 'security',\r\n }]\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine } from '../utils/patterns.js'\r\n\r\nexport const corsConfigRule: Rule = {\r\n id: 'cors-config',\r\n name: 'Permissive CORS',\r\n description: 'Detects overly permissive CORS configuration',\r\n category: 'security',\r\n severity: 'warning',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n\r\n if (/['\"]Access-Control-Allow-Origin['\"]\\s*[,:]\\s*['\"]\\*['\"]/.test(line)) {\r\n findings.push({\r\n ruleId: 'cors-config',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('Access-Control') + 1,\r\n message: 'Access-Control-Allow-Origin set to \"*\" allows any domain',\r\n severity: 'warning',\r\n category: 'security',\r\n })\r\n }\r\n\r\n if (/cors\\(\\s*\\)/.test(line)) {\r\n findings.push({\r\n ruleId: 'cors-config',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('cors(') + 1,\r\n message: 'cors() called without config allows all origins',\r\n severity: 'warning',\r\n category: 'security',\r\n })\r\n }\r\n\r\n if (/origin\\s*:\\s*['\"]\\*['\"]/.test(line)) {\r\n findings.push({\r\n ruleId: 'cors-config',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('origin') + 1,\r\n message: 'CORS origin set to \"*\" allows any domain',\r\n severity: 'warning',\r\n category: 'security',\r\n })\r\n }\r\n\r\n if (/origin\\s*:\\s*true/.test(line)) {\r\n findings.push({\r\n ruleId: 'cors-config',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('origin') + 1,\r\n message: 'CORS origin set to true mirrors any requesting origin',\r\n severity: 'warning',\r\n category: 'security',\r\n })\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine } from '../utils/patterns.js'\r\n\r\nconst CONSOLE_LOG_THRESHOLD = 5\r\nconst ANY_TYPE_THRESHOLD = 5\r\nconst COMMENTED_CODE_THRESHOLD = 3\r\n\r\nexport const aiSmellsRule: Rule = {\r\n id: 'ai-smells',\r\n name: 'AI Code Smells',\r\n description: 'Detects TODOs, placeholder functions, excessive console.log, any types, and commented-out code',\r\n category: 'ai-quality',\r\n severity: 'info',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n let consoleLogCount = 0\r\n let anyTypeCount = 0\r\n let commentedCodeRun = 0\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n const line = file.lines[i]\r\n const trimmed = line.trim()\r\n\r\n // Inside block comments — only check for commented code detection\r\n if (file.commentMap[i]) {\r\n commentedCodeRun = 0\r\n continue\r\n }\r\n\r\n // TODO / FIXME / HACK / XXX comments (single-line only)\r\n if (trimmed.startsWith('//')) {\r\n const todoMatch = trimmed.match(/\\/\\/\\s*(TODO|FIXME|HACK|XXX)\\b(.*)/)\r\n if (todoMatch) {\r\n findings.push({\r\n ruleId: 'ai-smells',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf(todoMatch[1]) + 1,\r\n message: `${todoMatch[1]} comment: ${todoMatch[2].trim() || '(no description)'}`,\r\n severity: 'info',\r\n category: 'ai-quality',\r\n })\r\n }\r\n\r\n // Commented-out code detection\r\n const commentContent = trimmed.slice(2).trim()\r\n const looksLikeCode = /^(import |export |const |let |var |function |if |for |while |return |await |async |class |switch )/.test(commentContent) ||\r\n /[{};=]$/.test(commentContent) ||\r\n /^\\w+\\(/.test(commentContent)\r\n\r\n if (looksLikeCode) {\r\n commentedCodeRun++\r\n if (commentedCodeRun === COMMENTED_CODE_THRESHOLD) {\r\n findings.push({\r\n ruleId: 'ai-smells',\r\n file: file.relativePath,\r\n line: i + 1 - COMMENTED_CODE_THRESHOLD + 1,\r\n column: 1,\r\n message: `${COMMENTED_CODE_THRESHOLD}+ consecutive lines of commented-out code`,\r\n severity: 'info',\r\n category: 'ai-quality',\r\n })\r\n }\r\n } else {\r\n commentedCodeRun = 0\r\n }\r\n continue\r\n }\r\n\r\n commentedCodeRun = 0\r\n\r\n // Placeholder functions\r\n if (/(?:throw new Error|throw Error)\\s*\\(\\s*['\"]not implemented['\"]/i.test(line)) {\r\n findings.push({\r\n ruleId: 'ai-smells',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: 1,\r\n message: 'Placeholder \"not implemented\" function',\r\n severity: 'warning',\r\n category: 'ai-quality',\r\n })\r\n }\r\n\r\n // console.log\r\n if (/console\\.log\\s*\\(/.test(line)) {\r\n consoleLogCount++\r\n }\r\n\r\n // Excessive `any` types (only in actual type positions)\r\n if (/:\\s*any\\b/.test(line) || /\\bas\\s+any\\b/.test(line) || /<any>/.test(line)) {\r\n anyTypeCount++\r\n }\r\n }\r\n\r\n if (consoleLogCount > CONSOLE_LOG_THRESHOLD) {\r\n findings.push({\r\n ruleId: 'ai-smells',\r\n file: file.relativePath,\r\n line: 1,\r\n column: 1,\r\n message: `${consoleLogCount} console.log statements (consider a proper logger)`,\r\n severity: 'warning',\r\n category: 'ai-quality',\r\n })\r\n }\r\n\r\n if (anyTypeCount > ANY_TYPE_THRESHOLD) {\r\n findings.push({\r\n ruleId: 'ai-smells',\r\n file: file.relativePath,\r\n line: 1,\r\n column: 1,\r\n message: `${anyTypeCount} uses of \"any\" type (consider proper typing)`,\r\n severity: 'warning',\r\n category: 'ai-quality',\r\n })\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine } from '../utils/patterns.js'\r\n\r\nexport const unsafeHtmlRule: Rule = {\r\n id: 'unsafe-html',\r\n name: 'Unsafe HTML Rendering',\r\n description: 'Detects dangerouslySetInnerHTML and other XSS vectors in JSX/DOM code',\r\n category: 'security',\r\n severity: 'critical',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n\r\n // Only match dangerouslySetInnerHTML in JSX attribute position (after = or with {)\r\n // Avoids matching inside string literals, variable names, or regex patterns\r\n if (/dangerouslySetInnerHTML\\s*=/.test(line) || /dangerouslySetInnerHTML\\s*:/.test(line)) {\r\n findings.push({\r\n ruleId: 'unsafe-html',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('dangerouslySetInnerHTML') + 1,\r\n message: 'dangerouslySetInnerHTML is an XSS risk — sanitize with DOMPurify or similar',\r\n severity: 'critical',\r\n category: 'security',\r\n })\r\n }\r\n\r\n // innerHTML assignment (actual DOM mutation, not inside a string)\r\n if (/\\w\\.innerHTML\\s*=/.test(line)) {\r\n findings.push({\r\n ruleId: 'unsafe-html',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: line.indexOf('.innerHTML') + 1,\r\n message: 'Direct innerHTML assignment is an XSS risk',\r\n severity: 'critical',\r\n category: 'security',\r\n })\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule, Finding, FileContext, ProjectContext } from '../types.js'\r\nimport { isCommentLine } from '../utils/patterns.js'\r\n\r\n// Patterns for unsafe SQL construction\r\nconst SQL_INJECTION_PATTERNS = [\r\n // Template literals with SQL keywords and interpolation\r\n { pattern: /`\\s*(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE)\\b[^`]*\\$\\{/, message: 'SQL query built with template literal interpolation — use parameterized queries' },\r\n // String concatenation with SQL\r\n { pattern: /(?:SELECT|INSERT|UPDATE|DELETE|DROP)\\b.*['\"]?\\s*\\+\\s*\\w/, message: 'SQL query built with string concatenation — use parameterized queries' },\r\n // .query() or .execute() with template literal\r\n { pattern: /\\.(?:query|execute|raw)\\s*\\(\\s*`[^`]*\\$\\{/, message: 'Database query with template literal interpolation — use parameterized queries' },\r\n]\r\n\r\nexport const sqlInjectionRule: Rule = {\r\n id: 'sql-injection',\r\n name: 'SQL Injection Risk',\r\n description: 'Detects SQL queries built with string interpolation or concatenation',\r\n category: 'security',\r\n severity: 'critical',\r\n fileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs'],\r\n\r\n check(file: FileContext, _project: ProjectContext): Finding[] {\r\n const findings: Finding[] = []\r\n\r\n for (let i = 0; i < file.lines.length; i++) {\r\n if (isCommentLine(file.lines, i, file.commentMap)) continue\r\n\r\n const line = file.lines[i]\r\n\r\n for (const { pattern, message } of SQL_INJECTION_PATTERNS) {\r\n if (pattern.test(line)) {\r\n findings.push({\r\n ruleId: 'sql-injection',\r\n file: file.relativePath,\r\n line: i + 1,\r\n column: 1,\r\n message,\r\n severity: 'critical',\r\n category: 'security',\r\n })\r\n break // One finding per line is enough\r\n }\r\n }\r\n }\r\n\r\n return findings\r\n },\r\n}\r\n","import type { Rule } from '../types.js'\r\nimport { secretsRule } from './secrets.js'\r\nimport { hallucinatedImportsRule } from './hallucinated-imports.js'\r\nimport { authChecksRule } from './auth-checks.js'\r\nimport { envExposureRule } from './env-exposure.js'\r\nimport { errorHandlingRule } from './error-handling.js'\r\nimport { inputValidationRule } from './input-validation.js'\r\nimport { rateLimitingRule } from './rate-limiting.js'\r\nimport { corsConfigRule } from './cors-config.js'\r\nimport { aiSmellsRule } from './ai-smells.js'\r\nimport { unsafeHtmlRule } from './unsafe-html.js'\r\nimport { sqlInjectionRule } from './sql-injection.js'\r\n\r\nexport const rules: Rule[] = [\r\n secretsRule,\r\n hallucinatedImportsRule,\r\n authChecksRule,\r\n envExposureRule,\r\n errorHandlingRule,\r\n inputValidationRule,\r\n rateLimitingRule,\r\n corsConfigRule,\r\n aiSmellsRule,\r\n unsafeHtmlRule,\r\n sqlInjectionRule,\r\n]\r\n","import { walkFiles, readFileContext, buildProjectContext } from './utils/file-walker.js'\r\nimport { isLineSuppressed } from './utils/patterns.js'\r\nimport { getVersion } from './utils/version.js'\r\nimport { calculateScores, summarizeFindings } from './scorer.js'\r\nimport { rules } from './rules/index.js'\r\nimport type { Finding, ScanOptions, ScanResult } from './types.js'\r\nimport { resolve } from 'node:path'\r\n\r\nexport async function scan(options: ScanOptions): Promise<ScanResult> {\r\n const start = performance.now()\r\n const root = resolve(options.path)\r\n\r\n const filePaths = await walkFiles(root, options.ignore)\r\n const project = await buildProjectContext(root, filePaths)\r\n\r\n const findings: Finding[] = []\r\n\r\n for (const relativePath of filePaths) {\r\n const file = await readFileContext(root, relativePath)\r\n if (!file) continue\r\n\r\n for (const rule of rules) {\r\n if (\r\n rule.fileExtensions.length > 0 &&\r\n !rule.fileExtensions.includes(file.ext)\r\n ) {\r\n continue\r\n }\r\n\r\n const ruleFindings = rule.check(file, project)\r\n\r\n for (const finding of ruleFindings) {\r\n if (!isLineSuppressed(file.lines, finding.line - 1, finding.ruleId)) {\r\n findings.push(finding)\r\n }\r\n }\r\n }\r\n }\r\n\r\n const { overallScore, categoryScores } = calculateScores(findings)\r\n const summary = summarizeFindings(findings)\r\n\r\n return {\r\n version: getVersion(),\r\n scannedPath: options.path,\r\n filesScanned: filePaths.length,\r\n scanDurationMs: Math.round(performance.now() - start),\r\n findings,\r\n overallScore,\r\n categoryScores,\r\n summary,\r\n }\r\n}\r\n"],"mappings":"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","names":["stat","resolve","resolve","resolve","resolve","stat"]}
|