vbguard 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vibeguard contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # ⚡ vibeguard
2
+
3
+ **Security scanner built for AI-generated code. Catches what traditional scanners miss.**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/vibeguard.svg)](https://www.npmjs.com/package/vibeguard)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ---
9
+
10
+ Vibe coding is fast. But 45% of AI-generated code ships with known vulnerabilities. The Moltbook breach, the pickle exploits, the hardcoded Supabase keys — all caused by patterns that traditional scanners weren't designed to catch.
11
+
12
+ **vibeguard** scans your codebase for the security mistakes that AI coding tools (Cursor, Claude Code, Copilot, Lovable, Bolt, Replit) introduce most often.
13
+
14
+ ## Quick Start
15
+
16
+ ```bash
17
+ npx vibeguard .
18
+ ```
19
+
20
+ That's it. No config, no account, no API key.
21
+
22
+ ## What It Catches
23
+
24
+ | Category | Examples | Severity |
25
+ |----------|----------|----------|
26
+ | **Hardcoded Secrets** | API keys, DB connection strings, JWTs, private keys inline in code | Critical |
27
+ | **Frontend-Exposed Secrets** | Stripe secret keys, service role tokens, DB URLs in client-side code | Critical |
28
+ | **Dangerous Functions** | `pickle.loads()`, `eval()` with user input, SQL injection via f-strings/template literals | Critical |
29
+ | **Missing Auth** | Express/Flask/FastAPI servers with no authentication middleware | High |
30
+ | **Permissive Configs** | `cors(*)`, `debug=True`, Firebase rules `allow: if true`, Supabase without RLS | High |
31
+ | **No Rate Limiting** | HTTP servers without rate limiting middleware | High |
32
+ | **Dangerous Dependencies** | Compromised packages (event-stream, faker), deprecated libs AI still suggests | Medium |
33
+ | **Missing .gitignore** | `.env` files not gitignored, secrets about to be committed | Critical |
34
+ | **Docker Misconfigs** | Running as root, copying `.env` into images, exposed DB ports | Medium-High |
35
+
36
+ ## Supported Languages
37
+
38
+ - **JavaScript / TypeScript** — Express, Fastify, Next.js, React, Vue, Svelte
39
+ - **Python** — Flask, FastAPI, Django
40
+
41
+ ## Usage
42
+
43
+ ```bash
44
+ # Scan current directory
45
+ vibeguard .
46
+
47
+ # Scan a specific project
48
+ vibeguard ./my-app
49
+
50
+ # Only show high and critical issues
51
+ vibeguard . --severity=high
52
+
53
+ # Output as JSON (for CI/CD)
54
+ vibeguard . --json
55
+
56
+ # Hide fix suggestions
57
+ vibeguard . --no-fix
58
+
59
+ # Ignore specific directories
60
+ vibeguard . --ignore=tests,scripts
61
+ ```
62
+
63
+ ## CI/CD Integration
64
+
65
+ ### GitHub Actions
66
+
67
+ ```yaml
68
+ name: Security Scan
69
+ on: [push, pull_request]
70
+
71
+ jobs:
72
+ vibeguard:
73
+ runs-on: ubuntu-latest
74
+ steps:
75
+ - uses: actions/checkout@v4
76
+ - uses: actions/setup-node@v4
77
+ with:
78
+ node-version: '20'
79
+ - run: npx vibeguard . --severity=high
80
+ ```
81
+
82
+ vibeguard exits with code 1 if critical or high severity issues are found, making it easy to block deploys.
83
+
84
+ ### Pre-commit Hook
85
+
86
+ ```bash
87
+ # .husky/pre-commit
88
+ npx vibeguard . --severity=high
89
+ ```
90
+
91
+ ## Example Output
92
+
93
+ ```
94
+ ⚡ vibeguard v0.1.0
95
+ Security scanner for AI-generated code
96
+
97
+ Scanning: /Users/dev/my-vibe-app
98
+
99
+ 🚨 CRITICAL (3)
100
+
101
+ ▸ secret/openai-api-key
102
+ src/api/chat.ts:5
103
+ Hardcoded OpenAI API Key detected. AI tools commonly inline
104
+ credentials — this is a top cause of breaches in vibe-coded apps.
105
+ 💡 Fix: Move to environment variable OPENAI_API_KEY.
106
+
107
+ ▸ frontend/stripe-secret-key-in-client
108
+ src/components/Checkout.tsx:12
109
+ Stripe Secret Key in Client found in client-side code. This will
110
+ be visible to anyone who opens browser DevTools.
111
+ 💡 Fix: Stripe secret keys must NEVER be in frontend code.
112
+
113
+ ▸ dangerous/pickle-deserialization
114
+ api/data.py:23
115
+ pickle.load() allows arbitrary code execution when deserializing
116
+ untrusted data.
117
+ 💡 Fix: Use json.loads() for data exchange.
118
+
119
+ 🔴 HIGH (2)
120
+
121
+ ▸ defaults/no-rate-limiting
122
+ src/api/server.ts
123
+ No rate limiting detected on HTTP server.
124
+ 💡 Fix: Add express-rate-limit.
125
+
126
+ ▸ defaults/permissive-cors
127
+ src/api/server.ts:8
128
+ CORS is set to allow all origins (*).
129
+ 💡 Fix: Set specific origin: cors({ origin: 'https://yourdomain.com' })
130
+
131
+ ─────────────────────────────────────────
132
+ 5 issues found: 3 critical, 2 high
133
+ Scanned 24 files in 12ms
134
+
135
+ ⚠ Fix critical and high severity issues before deploying!
136
+ ```
137
+
138
+ ## Why Not Just Use Snyk / Semgrep / SonarQube?
139
+
140
+ Those tools are great for traditional code. But they weren't designed for AI-generated code patterns:
141
+
142
+ - **Snyk** focuses on dependency vulnerabilities, not hardcoded secrets or missing middleware
143
+ - **Semgrep** requires writing custom rules — vibeguard ships with AI-specific patterns out of the box
144
+ - **SonarQube** is enterprise-heavy and takes hours to configure
145
+
146
+ vibeguard is opinionated, zero-config, and runs in milliseconds. It's built specifically for the patterns that Cursor, Claude Code, Copilot, Lovable, and Bolt introduce.
147
+
148
+ ## How It Works
149
+
150
+ vibeguard uses pattern matching (regex + structural analysis) against a curated ruleset of AI-specific vulnerability patterns. No AI, no API calls, no data leaves your machine. It runs entirely locally.
151
+
152
+ The ruleset is based on real-world breaches and academic research:
153
+ - The Moltbook breach (Supabase misconfiguration)
154
+ - Tenzai's 2025 study (69 vulnerabilities across 5 AI coding tools)
155
+ - Escape.tech's scan of 5,600 vibe-coded apps
156
+ - Georgia Tech's Vibe Security Radar (tracking AI-generated CVEs)
157
+
158
+ ## Contributing
159
+
160
+ Contributions welcome. If you've found a vulnerability pattern that AI tools commonly introduce, open a PR to add it to the scanner.
161
+
162
+ ```
163
+ src/scanners/
164
+ secrets.js # Hardcoded API keys, tokens, connection strings
165
+ dangerous-defaults.js # Missing auth, rate limiting, CORS, headers
166
+ dangerous-functions.js # eval, pickle, SQL injection, XSS
167
+ exposed-frontend.js # Server secrets in client-side code
168
+ permissive-configs.js # Supabase, Firebase, Docker misconfigs
169
+ dependencies.js # Compromised/deprecated packages
170
+ gitignore.js # Missing .gitignore entries
171
+ ```
172
+
173
+ ## License
174
+
175
+ MIT
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "vbguard",
3
+ "version": "0.1.0",
4
+ "description": "Security scanner for AI-generated code. Catches what traditional scanners miss — hardcoded secrets, dangerous defaults, exposed keys, and more.",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "vbguard": "./src/cli.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node src/cli.js",
11
+ "test": "node test/test.js"
12
+ },
13
+ "keywords": [
14
+ "security",
15
+ "vibe-coding",
16
+ "ai-code",
17
+ "scanner",
18
+ "vulnerability",
19
+ "cli",
20
+ "vibeguard",
21
+ "sast",
22
+ "secrets",
23
+ "ai-security"
24
+ ],
25
+ "author": "",
26
+ "license": "MIT",
27
+ "engines": {
28
+ "node": ">=16.0.0"
29
+ },
30
+ "files": [
31
+ "src/**/*",
32
+ "README.md",
33
+ "LICENSE"
34
+ ]
35
+ }
package/src/cli.js ADDED
@@ -0,0 +1,62 @@
1
+ const path = require('path');
2
+ const { scanDirectory } = require('./index');
3
+ const { formatReport } = require('./reporter');
4
+
5
+ const args = process.argv.slice(2);
6
+
7
+ const HELP = `
8
+ vbguard - Security scanner for AI-generated code
9
+
10
+ Usage:
11
+ vbguard [directory] [options]
12
+
13
+ Options:
14
+ --json Output results as JSON
15
+ --severity=X Minimum severity (low, medium, high, critical)
16
+ --no-fix Hide fix suggestions
17
+ --ignore=X Comma-separated patterns to ignore
18
+ -h, --help Show this help
19
+ -v, --version Show version
20
+ `;
21
+
22
+ function parseArgs(args) {
23
+ const opts = { dir: '.', json: false, severity: 'low', showFix: true, ignore: [] };
24
+ for (const arg of args) {
25
+ if (arg === '-h' || arg === '--help') { console.log(HELP); process.exit(0); }
26
+ if (arg === '-v' || arg === '--version') { console.log('0.1.0'); process.exit(0); }
27
+ if (arg === '--json') opts.json = true;
28
+ else if (arg === '--no-fix') opts.showFix = false;
29
+ else if (arg.startsWith('--severity=')) opts.severity = arg.split('=')[1];
30
+ else if (arg.startsWith('--ignore=')) opts.ignore = arg.split('=')[1].split(',');
31
+ else if (!arg.startsWith('-')) opts.dir = arg;
32
+ }
33
+ return opts;
34
+ }
35
+
36
+ async function main() {
37
+ const opts = parseArgs(args);
38
+ const targetDir = path.resolve(opts.dir);
39
+
40
+ console.log('');
41
+ console.log(' \x1b[1m\x1b[35m\u26a1 vbguard\x1b[0m \x1b[2mv0.1.0\x1b[0m');
42
+ console.log(' \x1b[2mSecurity scanner for AI-generated code\x1b[0m');
43
+ console.log('');
44
+ console.log(` \x1b[2mScanning:\x1b[0m ${targetDir}`);
45
+ console.log('');
46
+
47
+ try {
48
+ const results = await scanDirectory(targetDir, opts);
49
+ if (opts.json) {
50
+ console.log(JSON.stringify(results, null, 2));
51
+ } else {
52
+ formatReport(results, opts);
53
+ }
54
+ const hasCritical = results.findings.some(f => f.severity === 'critical' || f.severity === 'high');
55
+ process.exit(hasCritical ? 1 : 0);
56
+ } catch (err) {
57
+ console.error(`\x1b[31m Error: ${err.message}\x1b[0m`);
58
+ process.exit(2);
59
+ }
60
+ }
61
+
62
+ main();
package/src/index.js ADDED
@@ -0,0 +1,135 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const { scanSecrets } = require('./scanners/secrets');
5
+ const { scanDangerousDefaults } = require('./scanners/dangerous-defaults');
6
+ const { scanExposedFrontend } = require('./scanners/exposed-frontend');
7
+ const { scanMissingGitignore } = require('./scanners/gitignore');
8
+ const { scanDangerousFunctions } = require('./scanners/dangerous-functions');
9
+ const { scanPermissiveConfigs } = require('./scanners/permissive-configs');
10
+ const { scanDependencies } = require('./scanners/dependencies');
11
+
12
+ const IGNORE_DIRS = new Set([
13
+ 'node_modules', '.git', '.next', '__pycache__', '.venv', 'venv',
14
+ 'env', '.env', 'dist', 'build', '.cache', 'coverage', '.nyc_output',
15
+ '.pytest_cache', 'egg-info', '.tox', '.mypy_cache',
16
+ ]);
17
+
18
+ const SCAN_EXTENSIONS = new Set([
19
+ '.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
20
+ '.py', '.pyw',
21
+ '.json', '.yaml', '.yml', '.toml',
22
+ '.env', '.env.local', '.env.production', '.env.development',
23
+ '.html', '.htm', '.vue', '.svelte',
24
+ ]);
25
+
26
+ function shouldScanFile(filePath) {
27
+ const ext = path.extname(filePath).toLowerCase();
28
+ const basename = path.basename(filePath);
29
+
30
+ // Always scan dotenv files
31
+ if (basename.startsWith('.env')) return true;
32
+ // Always scan config files
33
+ if (['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml',
34
+ 'firestore.rules', 'database.rules.json', 'storage.rules',
35
+ 'firebase.json'].includes(basename)) return true;
36
+
37
+ return SCAN_EXTENSIONS.has(ext);
38
+ }
39
+
40
+ function walkDir(dir, ignore = []) {
41
+ const files = [];
42
+
43
+ function walk(currentDir) {
44
+ let entries;
45
+ try {
46
+ entries = fs.readdirSync(currentDir, { withFileTypes: true });
47
+ } catch {
48
+ return;
49
+ }
50
+
51
+ for (const entry of entries) {
52
+ const fullPath = path.join(currentDir, entry.name);
53
+ const relativePath = path.relative(dir, fullPath);
54
+
55
+ if (entry.isDirectory()) {
56
+ if (IGNORE_DIRS.has(entry.name)) continue;
57
+ if (ignore.some((pattern) => relativePath.includes(pattern))) continue;
58
+ walk(fullPath);
59
+ } else if (entry.isFile()) {
60
+ if (shouldScanFile(fullPath)) {
61
+ files.push(fullPath);
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ walk(dir);
68
+ return files;
69
+ }
70
+
71
+ async function scanDirectory(dir, opts = {}) {
72
+ const startTime = Date.now();
73
+
74
+ if (!fs.existsSync(dir)) {
75
+ throw new Error(`Directory not found: ${dir}`);
76
+ }
77
+
78
+ const files = walkDir(dir, opts.ignore || []);
79
+ const findings = [];
80
+ let filesScanned = 0;
81
+
82
+ // Per-file scanners
83
+ for (const filePath of files) {
84
+ let content;
85
+ try {
86
+ const stat = fs.statSync(filePath);
87
+ // Skip files > 1MB
88
+ if (stat.size > 1_000_000) continue;
89
+ content = fs.readFileSync(filePath, 'utf-8');
90
+ } catch {
91
+ continue;
92
+ }
93
+
94
+ filesScanned++;
95
+ const relativePath = path.relative(dir, filePath);
96
+ const ext = path.extname(filePath).toLowerCase();
97
+ const basename = path.basename(filePath);
98
+
99
+ const ctx = { filePath, relativePath, content, ext, basename };
100
+
101
+ findings.push(...scanSecrets(ctx));
102
+ findings.push(...scanDangerousDefaults(ctx));
103
+ findings.push(...scanExposedFrontend(ctx));
104
+ findings.push(...scanDangerousFunctions(ctx));
105
+ findings.push(...scanPermissiveConfigs(ctx));
106
+ }
107
+
108
+ // Project-level scanners
109
+ findings.push(...scanMissingGitignore(dir));
110
+ findings.push(...(await scanDependencies(dir)));
111
+
112
+ // Filter by severity
113
+ const severityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
114
+ const minSeverity = severityOrder[opts.severity || 'low'] || 1;
115
+ const filtered = findings.filter(
116
+ (f) => (severityOrder[f.severity] || 0) >= minSeverity
117
+ );
118
+
119
+ const elapsed = Date.now() - startTime;
120
+
121
+ return {
122
+ findings: filtered,
123
+ summary: {
124
+ filesScanned,
125
+ totalFindings: filtered.length,
126
+ critical: filtered.filter((f) => f.severity === 'critical').length,
127
+ high: filtered.filter((f) => f.severity === 'high').length,
128
+ medium: filtered.filter((f) => f.severity === 'medium').length,
129
+ low: filtered.filter((f) => f.severity === 'low').length,
130
+ elapsed,
131
+ },
132
+ };
133
+ }
134
+
135
+ module.exports = { scanDirectory };
@@ -0,0 +1,78 @@
1
+ const SEVERITY_COLORS = {
2
+ critical: '\x1b[41m\x1b[37m', // white on red bg
3
+ high: '\x1b[31m', // red
4
+ medium: '\x1b[33m', // yellow
5
+ low: '\x1b[36m', // cyan
6
+ };
7
+
8
+ const SEVERITY_ICONS = {
9
+ critical: '🚨',
10
+ high: '🔴',
11
+ medium: '🟡',
12
+ low: '🔵',
13
+ };
14
+
15
+ const RESET = '\x1b[0m';
16
+ const DIM = '\x1b[2m';
17
+ const BOLD = '\x1b[1m';
18
+
19
+ function formatReport(results, opts = {}) {
20
+ const { findings, summary } = results;
21
+
22
+ if (findings.length === 0) {
23
+ console.log(' \x1b[32m✓ No security issues found!\x1b[0m');
24
+ console.log(` ${DIM}Scanned ${summary.filesScanned} files in ${summary.elapsed}ms${RESET}`);
25
+ console.log('');
26
+ return;
27
+ }
28
+
29
+ // Group by severity
30
+ const grouped = { critical: [], high: [], medium: [], low: [] };
31
+ for (const f of findings) {
32
+ if (grouped[f.severity]) {
33
+ grouped[f.severity].push(f);
34
+ }
35
+ }
36
+
37
+ for (const severity of ['critical', 'high', 'medium', 'low']) {
38
+ const items = grouped[severity];
39
+ if (items.length === 0) continue;
40
+
41
+ const color = SEVERITY_COLORS[severity];
42
+ const icon = SEVERITY_ICONS[severity];
43
+
44
+ console.log(` ${color}${BOLD}${icon} ${severity.toUpperCase()} (${items.length})${RESET}`);
45
+ console.log('');
46
+
47
+ for (const finding of items) {
48
+ console.log(` ${color} ▸ ${finding.rule}${RESET}`);
49
+ console.log(` ${DIM}${finding.file}${finding.line ? `:${finding.line}` : ''}${RESET}`);
50
+ console.log(` ${finding.message}`);
51
+
52
+ if (opts.showFix !== false && finding.fix) {
53
+ console.log(` ${DIM}💡 Fix: ${finding.fix}${RESET}`);
54
+ }
55
+
56
+ console.log('');
57
+ }
58
+ }
59
+
60
+ // Summary bar
61
+ console.log(' ─────────────────────────────────────────');
62
+ const parts = [];
63
+ if (summary.critical > 0) parts.push(`\x1b[31m${summary.critical} critical${RESET}`);
64
+ if (summary.high > 0) parts.push(`\x1b[31m${summary.high} high${RESET}`);
65
+ if (summary.medium > 0) parts.push(`\x1b[33m${summary.medium} medium${RESET}`);
66
+ if (summary.low > 0) parts.push(`\x1b[36m${summary.low} low${RESET}`);
67
+
68
+ console.log(` ${BOLD}${summary.totalFindings} issues${RESET} found: ${parts.join(', ')}`);
69
+ console.log(` ${DIM}Scanned ${summary.filesScanned} files in ${summary.elapsed}ms${RESET}`);
70
+ console.log('');
71
+
72
+ if (summary.critical > 0 || summary.high > 0) {
73
+ console.log(` \x1b[31m${BOLD}⚠ Fix critical and high severity issues before deploying!${RESET}`);
74
+ console.log('');
75
+ }
76
+ }
77
+
78
+ module.exports = { formatReport };
@@ -0,0 +1,166 @@
1
+ // AI-generated code frequently ships without basic security middleware
2
+ // These checks catch the most common omissions
3
+
4
+ function scanDangerousDefaults(ctx) {
5
+ const { content, relativePath, ext, basename } = ctx;
6
+ const findings = [];
7
+
8
+ const isJS = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'].includes(ext);
9
+ const isPy = ['.py', '.pyw'].includes(ext);
10
+ const isServerFile = /(?:server|app|index|main|api)\.(js|ts|py)$/i.test(basename) ||
11
+ /(?:server|api|backend|routes)/i.test(relativePath);
12
+
13
+ if (!isServerFile) return findings;
14
+
15
+ // === Express / Node.js checks ===
16
+ if (isJS) {
17
+ const hasExpress = /require\s*\(\s*['"]express['"]\s*\)|from\s+['"]express['"]/i.test(content);
18
+ const hasFastify = /require\s*\(\s*['"]fastify['"]\s*\)|from\s+['"]fastify['"]/i.test(content);
19
+ const hasKoa = /require\s*\(\s*['"]koa['"]\s*\)|from\s+['"]koa['"]/i.test(content);
20
+ const isHttpServer = hasExpress || hasFastify || hasKoa;
21
+
22
+ if (isHttpServer) {
23
+ // No rate limiting
24
+ if (!/rate.?limit|express-rate-limit|@fastify\/rate-limit|bottleneck|express-slow-down/i.test(content)) {
25
+ findings.push({
26
+ rule: 'defaults/no-rate-limiting',
27
+ severity: 'high',
28
+ file: relativePath,
29
+ line: null,
30
+ message: 'No rate limiting detected on HTTP server. AI-generated servers almost never include rate limiting, making them vulnerable to brute force and DDoS.',
31
+ fix: 'Add express-rate-limit: app.use(rateLimit({ windowMs: 15*60*1000, max: 100 }))',
32
+ });
33
+ }
34
+
35
+ // No helmet / security headers
36
+ if (!/helmet|security.?headers|x-content-type|x-frame-options|strict-transport/i.test(content)) {
37
+ findings.push({
38
+ rule: 'defaults/no-security-headers',
39
+ severity: 'medium',
40
+ file: relativePath,
41
+ line: null,
42
+ message: 'No security headers middleware (helmet) detected. Missing headers like X-Frame-Options, CSP, HSTS.',
43
+ fix: 'Add helmet: app.use(helmet()). Install with npm install helmet.',
44
+ });
45
+ }
46
+
47
+ // Permissive CORS
48
+ const corsMatch = content.match(/cors\(\s*\{?\s*origin\s*:\s*['"`]\*['"`]|cors\(\s*\)/);
49
+ if (corsMatch) {
50
+ const lineNum = content.substring(0, corsMatch.index).split('\n').length;
51
+ findings.push({
52
+ rule: 'defaults/permissive-cors',
53
+ severity: 'high',
54
+ file: relativePath,
55
+ line: lineNum,
56
+ message: 'CORS is set to allow all origins (*). AI tools default to permissive CORS. Restrict to your actual frontend domain.',
57
+ fix: "Set specific origin: cors({ origin: 'https://yourdomain.com' })",
58
+ });
59
+ }
60
+
61
+ // No input validation
62
+ if (!/zod|joi|yup|express-validator|class-validator|ajv|superstruct/i.test(content)) {
63
+ // Check if there are POST/PUT route handlers
64
+ if (/\.(post|put|patch)\s*\(/i.test(content)) {
65
+ findings.push({
66
+ rule: 'defaults/no-input-validation',
67
+ severity: 'medium',
68
+ file: relativePath,
69
+ line: null,
70
+ message: 'No input validation library detected but POST/PUT handlers exist. AI-generated APIs rarely validate input, enabling injection attacks.',
71
+ fix: 'Add zod or joi for request body validation on all mutation endpoints.',
72
+ });
73
+ }
74
+ }
75
+
76
+ // No auth middleware
77
+ if (/\.(get|post|put|patch|delete)\s*\(/i.test(content)) {
78
+ if (!/auth|jwt|passport|clerk|supabase.*auth|next-auth|lucia|session|bearer/i.test(content)) {
79
+ findings.push({
80
+ rule: 'defaults/no-auth-middleware',
81
+ severity: 'high',
82
+ file: relativePath,
83
+ line: null,
84
+ message: 'Route handlers detected but no authentication middleware found. AI tools frequently skip auth, leaving all endpoints publicly accessible.',
85
+ fix: 'Add authentication middleware to protected routes. Use passport, clerk, or JWT verification.',
86
+ });
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ // === Python / Flask / FastAPI / Django checks ===
93
+ if (isPy) {
94
+ const hasFlask = /from\s+flask\s+import|import\s+flask/i.test(content);
95
+ const hasFastAPI = /from\s+fastapi\s+import|import\s+fastapi/i.test(content);
96
+ const hasDjango = /from\s+django/i.test(content);
97
+ const isPyServer = hasFlask || hasFastAPI || hasDjango;
98
+
99
+ if (isPyServer) {
100
+ // Debug mode in production
101
+ const debugMatch = content.match(/debug\s*=\s*True|DEBUG\s*=\s*True|app\.run\([^)]*debug\s*=\s*True/i);
102
+ if (debugMatch) {
103
+ const lineNum = content.substring(0, debugMatch.index).split('\n').length;
104
+ findings.push({
105
+ rule: 'defaults/debug-mode-enabled',
106
+ severity: 'critical',
107
+ file: relativePath,
108
+ line: lineNum,
109
+ message: 'Debug mode enabled. AI tools always set debug=True. This exposes stack traces, allows code execution (Flask debugger), and leaks secrets in production.',
110
+ fix: 'Set debug=False or use environment variable: debug=os.environ.get("DEBUG", "False") == "True"',
111
+ });
112
+ }
113
+
114
+ // Flask secret key hardcoded
115
+ const secretKeyMatch = content.match(/(?:secret_key|SECRET_KEY)\s*=\s*['"`]([^'"`]{1,100})['"`]/i);
116
+ if (secretKeyMatch) {
117
+ const lineNum = content.substring(0, secretKeyMatch.index).split('\n').length;
118
+ const key = secretKeyMatch[1];
119
+ if (!/os\.environ|os\.getenv|environ/i.test(content.split('\n')[lineNum - 1] || '')) {
120
+ findings.push({
121
+ rule: 'defaults/hardcoded-secret-key',
122
+ severity: 'critical',
123
+ file: relativePath,
124
+ line: lineNum,
125
+ message: 'Hardcoded SECRET_KEY. AI tools generate a static secret key. This compromises session security and CSRF protection.',
126
+ fix: "Use os.environ.get('SECRET_KEY') with a randomly generated value.",
127
+ });
128
+ }
129
+ }
130
+
131
+ // No CORS restriction (FastAPI)
132
+ if (hasFastAPI) {
133
+ const corsAll = content.match(/allow_origins\s*=\s*\[\s*['"`]\*['"`]\s*\]/);
134
+ if (corsAll) {
135
+ const lineNum = content.substring(0, corsAll.index).split('\n').length;
136
+ findings.push({
137
+ rule: 'defaults/permissive-cors',
138
+ severity: 'high',
139
+ file: relativePath,
140
+ line: lineNum,
141
+ message: 'FastAPI CORS allows all origins. Restrict to your frontend domain.',
142
+ fix: "Set allow_origins=['https://yourdomain.com']",
143
+ });
144
+ }
145
+ }
146
+
147
+ // No rate limiting
148
+ if (!/slowapi|flask.?limiter|ratelimit|throttle/i.test(content)) {
149
+ if (/@app\.(?:route|get|post|put|delete)|@router\./i.test(content)) {
150
+ findings.push({
151
+ rule: 'defaults/no-rate-limiting',
152
+ severity: 'high',
153
+ file: relativePath,
154
+ line: null,
155
+ message: 'No rate limiting on Python server. Add slowapi (FastAPI) or flask-limiter (Flask).',
156
+ fix: 'Install and configure rate limiting: pip install slowapi',
157
+ });
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ return findings;
164
+ }
165
+
166
+ module.exports = { scanDangerousDefaults };