@oculum/scanner 1.0.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/dist/formatters/cli-terminal.d.ts +27 -0
- package/dist/formatters/cli-terminal.d.ts.map +1 -0
- package/dist/formatters/cli-terminal.js +412 -0
- package/dist/formatters/cli-terminal.js.map +1 -0
- package/dist/formatters/github-comment.d.ts +41 -0
- package/dist/formatters/github-comment.d.ts.map +1 -0
- package/dist/formatters/github-comment.js +306 -0
- package/dist/formatters/github-comment.js.map +1 -0
- package/dist/formatters/grouping.d.ts +52 -0
- package/dist/formatters/grouping.d.ts.map +1 -0
- package/dist/formatters/grouping.js +152 -0
- package/dist/formatters/grouping.js.map +1 -0
- package/dist/formatters/index.d.ts +9 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +35 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/formatters/vscode-diagnostic.d.ts +103 -0
- package/dist/formatters/vscode-diagnostic.d.ts.map +1 -0
- package/dist/formatters/vscode-diagnostic.js +151 -0
- package/dist/formatters/vscode-diagnostic.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +648 -0
- package/dist/index.js.map +1 -0
- package/dist/layer1/comments.d.ts +8 -0
- package/dist/layer1/comments.d.ts.map +1 -0
- package/dist/layer1/comments.js +203 -0
- package/dist/layer1/comments.js.map +1 -0
- package/dist/layer1/config-audit.d.ts +8 -0
- package/dist/layer1/config-audit.d.ts.map +1 -0
- package/dist/layer1/config-audit.js +252 -0
- package/dist/layer1/config-audit.js.map +1 -0
- package/dist/layer1/entropy.d.ts +8 -0
- package/dist/layer1/entropy.d.ts.map +1 -0
- package/dist/layer1/entropy.js +500 -0
- package/dist/layer1/entropy.js.map +1 -0
- package/dist/layer1/file-flags.d.ts +7 -0
- package/dist/layer1/file-flags.d.ts.map +1 -0
- package/dist/layer1/file-flags.js +112 -0
- package/dist/layer1/file-flags.js.map +1 -0
- package/dist/layer1/index.d.ts +36 -0
- package/dist/layer1/index.d.ts.map +1 -0
- package/dist/layer1/index.js +132 -0
- package/dist/layer1/index.js.map +1 -0
- package/dist/layer1/patterns.d.ts +8 -0
- package/dist/layer1/patterns.d.ts.map +1 -0
- package/dist/layer1/patterns.js +482 -0
- package/dist/layer1/patterns.js.map +1 -0
- package/dist/layer1/urls.d.ts +8 -0
- package/dist/layer1/urls.d.ts.map +1 -0
- package/dist/layer1/urls.js +296 -0
- package/dist/layer1/urls.js.map +1 -0
- package/dist/layer1/weak-crypto.d.ts +7 -0
- package/dist/layer1/weak-crypto.d.ts.map +1 -0
- package/dist/layer1/weak-crypto.js +291 -0
- package/dist/layer1/weak-crypto.js.map +1 -0
- package/dist/layer2/ai-agent-tools.d.ts +19 -0
- package/dist/layer2/ai-agent-tools.d.ts.map +1 -0
- package/dist/layer2/ai-agent-tools.js +528 -0
- package/dist/layer2/ai-agent-tools.js.map +1 -0
- package/dist/layer2/ai-endpoint-protection.d.ts +36 -0
- package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -0
- package/dist/layer2/ai-endpoint-protection.js +332 -0
- package/dist/layer2/ai-endpoint-protection.js.map +1 -0
- package/dist/layer2/ai-execution-sinks.d.ts +18 -0
- package/dist/layer2/ai-execution-sinks.d.ts.map +1 -0
- package/dist/layer2/ai-execution-sinks.js +496 -0
- package/dist/layer2/ai-execution-sinks.js.map +1 -0
- package/dist/layer2/ai-fingerprinting.d.ts +7 -0
- package/dist/layer2/ai-fingerprinting.d.ts.map +1 -0
- package/dist/layer2/ai-fingerprinting.js +654 -0
- package/dist/layer2/ai-fingerprinting.js.map +1 -0
- package/dist/layer2/ai-prompt-hygiene.d.ts +19 -0
- package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -0
- package/dist/layer2/ai-prompt-hygiene.js +356 -0
- package/dist/layer2/ai-prompt-hygiene.js.map +1 -0
- package/dist/layer2/ai-rag-safety.d.ts +21 -0
- package/dist/layer2/ai-rag-safety.d.ts.map +1 -0
- package/dist/layer2/ai-rag-safety.js +459 -0
- package/dist/layer2/ai-rag-safety.js.map +1 -0
- package/dist/layer2/ai-schema-validation.d.ts +25 -0
- package/dist/layer2/ai-schema-validation.d.ts.map +1 -0
- package/dist/layer2/ai-schema-validation.js +375 -0
- package/dist/layer2/ai-schema-validation.js.map +1 -0
- package/dist/layer2/auth-antipatterns.d.ts +20 -0
- package/dist/layer2/auth-antipatterns.d.ts.map +1 -0
- package/dist/layer2/auth-antipatterns.js +333 -0
- package/dist/layer2/auth-antipatterns.js.map +1 -0
- package/dist/layer2/byok-patterns.d.ts +12 -0
- package/dist/layer2/byok-patterns.d.ts.map +1 -0
- package/dist/layer2/byok-patterns.js +299 -0
- package/dist/layer2/byok-patterns.js.map +1 -0
- package/dist/layer2/dangerous-functions.d.ts +7 -0
- package/dist/layer2/dangerous-functions.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions.js +1375 -0
- package/dist/layer2/dangerous-functions.js.map +1 -0
- package/dist/layer2/data-exposure.d.ts +16 -0
- package/dist/layer2/data-exposure.d.ts.map +1 -0
- package/dist/layer2/data-exposure.js +279 -0
- package/dist/layer2/data-exposure.js.map +1 -0
- package/dist/layer2/framework-checks.d.ts +7 -0
- package/dist/layer2/framework-checks.d.ts.map +1 -0
- package/dist/layer2/framework-checks.js +388 -0
- package/dist/layer2/framework-checks.js.map +1 -0
- package/dist/layer2/index.d.ts +58 -0
- package/dist/layer2/index.d.ts.map +1 -0
- package/dist/layer2/index.js +380 -0
- package/dist/layer2/index.js.map +1 -0
- package/dist/layer2/logic-gates.d.ts +7 -0
- package/dist/layer2/logic-gates.d.ts.map +1 -0
- package/dist/layer2/logic-gates.js +182 -0
- package/dist/layer2/logic-gates.js.map +1 -0
- package/dist/layer2/risky-imports.d.ts +7 -0
- package/dist/layer2/risky-imports.d.ts.map +1 -0
- package/dist/layer2/risky-imports.js +161 -0
- package/dist/layer2/risky-imports.js.map +1 -0
- package/dist/layer2/variables.d.ts +8 -0
- package/dist/layer2/variables.d.ts.map +1 -0
- package/dist/layer2/variables.js +152 -0
- package/dist/layer2/variables.js.map +1 -0
- package/dist/layer3/anthropic.d.ts +83 -0
- package/dist/layer3/anthropic.d.ts.map +1 -0
- package/dist/layer3/anthropic.js +1745 -0
- package/dist/layer3/anthropic.js.map +1 -0
- package/dist/layer3/index.d.ts +24 -0
- package/dist/layer3/index.d.ts.map +1 -0
- package/dist/layer3/index.js +119 -0
- package/dist/layer3/index.js.map +1 -0
- package/dist/layer3/openai.d.ts +25 -0
- package/dist/layer3/openai.d.ts.map +1 -0
- package/dist/layer3/openai.js +238 -0
- package/dist/layer3/openai.js.map +1 -0
- package/dist/layer3/package-check.d.ts +63 -0
- package/dist/layer3/package-check.d.ts.map +1 -0
- package/dist/layer3/package-check.js +508 -0
- package/dist/layer3/package-check.js.map +1 -0
- package/dist/modes/incremental.d.ts +66 -0
- package/dist/modes/incremental.d.ts.map +1 -0
- package/dist/modes/incremental.js +200 -0
- package/dist/modes/incremental.js.map +1 -0
- package/dist/tiers.d.ts +125 -0
- package/dist/tiers.d.ts.map +1 -0
- package/dist/tiers.js +234 -0
- package/dist/tiers.js.map +1 -0
- package/dist/types.d.ts +175 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +50 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/auth-helper-detector.d.ts +56 -0
- package/dist/utils/auth-helper-detector.d.ts.map +1 -0
- package/dist/utils/auth-helper-detector.js +360 -0
- package/dist/utils/auth-helper-detector.js.map +1 -0
- package/dist/utils/context-helpers.d.ts +96 -0
- package/dist/utils/context-helpers.d.ts.map +1 -0
- package/dist/utils/context-helpers.js +493 -0
- package/dist/utils/context-helpers.js.map +1 -0
- package/dist/utils/diff-detector.d.ts +53 -0
- package/dist/utils/diff-detector.d.ts.map +1 -0
- package/dist/utils/diff-detector.js +104 -0
- package/dist/utils/diff-detector.js.map +1 -0
- package/dist/utils/diff-parser.d.ts +80 -0
- package/dist/utils/diff-parser.d.ts.map +1 -0
- package/dist/utils/diff-parser.js +202 -0
- package/dist/utils/diff-parser.js.map +1 -0
- package/dist/utils/imported-auth-detector.d.ts +37 -0
- package/dist/utils/imported-auth-detector.d.ts.map +1 -0
- package/dist/utils/imported-auth-detector.js +251 -0
- package/dist/utils/imported-auth-detector.js.map +1 -0
- package/dist/utils/middleware-detector.d.ts +55 -0
- package/dist/utils/middleware-detector.d.ts.map +1 -0
- package/dist/utils/middleware-detector.js +260 -0
- package/dist/utils/middleware-detector.js.map +1 -0
- package/dist/utils/oauth-flow-detector.d.ts +41 -0
- package/dist/utils/oauth-flow-detector.d.ts.map +1 -0
- package/dist/utils/oauth-flow-detector.js +202 -0
- package/dist/utils/oauth-flow-detector.js.map +1 -0
- package/dist/utils/path-exclusions.d.ts +55 -0
- package/dist/utils/path-exclusions.d.ts.map +1 -0
- package/dist/utils/path-exclusions.js +222 -0
- package/dist/utils/path-exclusions.js.map +1 -0
- package/dist/utils/project-context-builder.d.ts +119 -0
- package/dist/utils/project-context-builder.d.ts.map +1 -0
- package/dist/utils/project-context-builder.js +534 -0
- package/dist/utils/project-context-builder.js.map +1 -0
- package/dist/utils/registry-clients.d.ts +93 -0
- package/dist/utils/registry-clients.d.ts.map +1 -0
- package/dist/utils/registry-clients.js +273 -0
- package/dist/utils/registry-clients.js.map +1 -0
- package/dist/utils/trpc-analyzer.d.ts +78 -0
- package/dist/utils/trpc-analyzer.d.ts.map +1 -0
- package/dist/utils/trpc-analyzer.js +297 -0
- package/dist/utils/trpc-analyzer.js.map +1 -0
- package/package.json +45 -0
- package/src/__tests__/benchmark/fixtures/false-positives.ts +227 -0
- package/src/__tests__/benchmark/fixtures/index.ts +68 -0
- package/src/__tests__/benchmark/fixtures/layer1/config-audit.ts +364 -0
- package/src/__tests__/benchmark/fixtures/layer1/hardcoded-secrets.ts +173 -0
- package/src/__tests__/benchmark/fixtures/layer1/high-entropy.ts +234 -0
- package/src/__tests__/benchmark/fixtures/layer1/index.ts +31 -0
- package/src/__tests__/benchmark/fixtures/layer1/sensitive-urls.ts +90 -0
- package/src/__tests__/benchmark/fixtures/layer1/weak-crypto.ts +197 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-agent-tools.ts +170 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-endpoint-protection.ts +418 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +189 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-fingerprinting.ts +316 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-prompt-hygiene.ts +178 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-rag-safety.ts +184 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-schema-validation.ts +434 -0
- package/src/__tests__/benchmark/fixtures/layer2/auth-antipatterns.ts +159 -0
- package/src/__tests__/benchmark/fixtures/layer2/byok-patterns.ts +112 -0
- package/src/__tests__/benchmark/fixtures/layer2/dangerous-functions.ts +246 -0
- package/src/__tests__/benchmark/fixtures/layer2/data-exposure.ts +168 -0
- package/src/__tests__/benchmark/fixtures/layer2/framework-checks.ts +346 -0
- package/src/__tests__/benchmark/fixtures/layer2/index.ts +67 -0
- package/src/__tests__/benchmark/fixtures/layer2/injection-vulnerabilities.ts +239 -0
- package/src/__tests__/benchmark/fixtures/layer2/logic-gates.ts +246 -0
- package/src/__tests__/benchmark/fixtures/layer2/risky-imports.ts +231 -0
- package/src/__tests__/benchmark/fixtures/layer2/variables.ts +167 -0
- package/src/__tests__/benchmark/index.ts +29 -0
- package/src/__tests__/benchmark/run-benchmark.ts +144 -0
- package/src/__tests__/benchmark/run-depth-validation.ts +206 -0
- package/src/__tests__/benchmark/run-real-world-test.ts +243 -0
- package/src/__tests__/benchmark/security-benchmark-script.ts +1737 -0
- package/src/__tests__/benchmark/tier-integration-script.ts +177 -0
- package/src/__tests__/benchmark/types.ts +144 -0
- package/src/__tests__/benchmark/utils/test-runner.ts +475 -0
- package/src/__tests__/regression/known-false-positives.test.ts +467 -0
- package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +178 -0
- package/src/__tests__/snapshots/scan-depth.test.ts +258 -0
- package/src/__tests__/validation/analyze-results.ts +542 -0
- package/src/__tests__/validation/extract-for-triage.ts +146 -0
- package/src/__tests__/validation/fp-deep-analysis.ts +327 -0
- package/src/__tests__/validation/run-validation.ts +364 -0
- package/src/__tests__/validation/triage-template.md +132 -0
- package/src/formatters/cli-terminal.ts +446 -0
- package/src/formatters/github-comment.ts +382 -0
- package/src/formatters/grouping.ts +190 -0
- package/src/formatters/index.ts +47 -0
- package/src/formatters/vscode-diagnostic.ts +243 -0
- package/src/index.ts +823 -0
- package/src/layer1/comments.ts +218 -0
- package/src/layer1/config-audit.ts +289 -0
- package/src/layer1/entropy.ts +583 -0
- package/src/layer1/file-flags.ts +127 -0
- package/src/layer1/index.ts +181 -0
- package/src/layer1/patterns.ts +516 -0
- package/src/layer1/urls.ts +334 -0
- package/src/layer1/weak-crypto.ts +328 -0
- package/src/layer2/ai-agent-tools.ts +601 -0
- package/src/layer2/ai-endpoint-protection.ts +387 -0
- package/src/layer2/ai-execution-sinks.ts +580 -0
- package/src/layer2/ai-fingerprinting.ts +758 -0
- package/src/layer2/ai-prompt-hygiene.ts +411 -0
- package/src/layer2/ai-rag-safety.ts +511 -0
- package/src/layer2/ai-schema-validation.ts +421 -0
- package/src/layer2/auth-antipatterns.ts +394 -0
- package/src/layer2/byok-patterns.ts +336 -0
- package/src/layer2/dangerous-functions.ts +1563 -0
- package/src/layer2/data-exposure.ts +315 -0
- package/src/layer2/framework-checks.ts +433 -0
- package/src/layer2/index.ts +473 -0
- package/src/layer2/logic-gates.ts +206 -0
- package/src/layer2/risky-imports.ts +186 -0
- package/src/layer2/variables.ts +166 -0
- package/src/layer3/anthropic.ts +2030 -0
- package/src/layer3/index.ts +130 -0
- package/src/layer3/package-check.ts +604 -0
- package/src/modes/incremental.ts +293 -0
- package/src/tiers.ts +318 -0
- package/src/types.ts +284 -0
- package/src/utils/auth-helper-detector.ts +443 -0
- package/src/utils/context-helpers.ts +535 -0
- package/src/utils/diff-detector.ts +135 -0
- package/src/utils/diff-parser.ts +272 -0
- package/src/utils/imported-auth-detector.ts +320 -0
- package/src/utils/middleware-detector.ts +333 -0
- package/src/utils/oauth-flow-detector.ts +246 -0
- package/src/utils/path-exclusions.ts +266 -0
- package/src/utils/project-context-builder.ts +707 -0
- package/src/utils/registry-clients.ts +351 -0
- package/src/utils/trpc-analyzer.ts +382 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 2: AI Code Fingerprinting
|
|
3
|
+
* Detects patterns commonly found in AI-generated code that may indicate security risks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Vulnerability, VulnerabilitySeverity } from '../types'
|
|
7
|
+
import { isExampleFile, isTestOrMockFile, isPlaceholderValue } from '../utils/context-helpers'
|
|
8
|
+
|
|
9
|
+
interface AIFingerprint {
|
|
10
|
+
name: string
|
|
11
|
+
pattern: RegExp
|
|
12
|
+
severity: VulnerabilitySeverity
|
|
13
|
+
description: string
|
|
14
|
+
suggestedFix: string
|
|
15
|
+
confidence: 'high' | 'medium' | 'low'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const AI_FINGERPRINTS: AIFingerprint[] = [
|
|
19
|
+
// ==================== Placeholder/TODO patterns - downgraded ====================
|
|
20
|
+
{
|
|
21
|
+
name: 'AI placeholder comment',
|
|
22
|
+
pattern: /\/\/\s*(TODO|FIXME|XXX|HACK):\s*(implement|add|replace|update|fix)\s+(this|here|later|authentication|validation|error handling)/gi,
|
|
23
|
+
severity: 'low', // Downgraded from medium - often harmless or addressed
|
|
24
|
+
description: 'AI-generated placeholder that may indicate incomplete implementation',
|
|
25
|
+
suggestedFix: 'Complete the implementation before deploying',
|
|
26
|
+
confidence: 'low', // Downgraded
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'Placeholder implementation',
|
|
30
|
+
// More specific: only match "placeholder implementation/code/function" not just "placeholder" in any context
|
|
31
|
+
pattern: /\/\/\s*placeholder\s+(implementation|code|function|method|logic|here)|\/\/\s*stub\s+(implementation|code|function|method)|\/\/\s*mock\s+implementation|\/\/\s*temporary\s+(implementation|code|fix|hack|workaround)/gi,
|
|
32
|
+
severity: 'low', // Downgraded from medium
|
|
33
|
+
description: 'Placeholder code that should be replaced with real implementation',
|
|
34
|
+
suggestedFix: 'Replace placeholder with actual implementation',
|
|
35
|
+
confidence: 'low', // Downgraded
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
// ==================== Overly permissive patterns ====================
|
|
39
|
+
{
|
|
40
|
+
name: 'AI catch-all error handler',
|
|
41
|
+
pattern: /catch\s*\([^)]*\)\s*\{\s*(console\.(log|error)|\/\/\s*handle)/gi,
|
|
42
|
+
severity: 'info', // Downgraded from low - this is standard practice
|
|
43
|
+
description: 'Generic error handling pattern - consider more specific handling',
|
|
44
|
+
suggestedFix: 'Add specific error handling based on error types',
|
|
45
|
+
confidence: 'low', // Downgraded
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'AI permissive CORS',
|
|
49
|
+
pattern: /cors\s*\(\s*\)|Access-Control-Allow-Origin['": ]*\*/gi,
|
|
50
|
+
severity: 'medium', // Downgraded from high - often intentional in dev
|
|
51
|
+
description: 'Overly permissive CORS configuration - verify if intentional',
|
|
52
|
+
suggestedFix: 'Restrict CORS to specific trusted origins in production',
|
|
53
|
+
confidence: 'medium', // Downgraded
|
|
54
|
+
},
|
|
55
|
+
// NOTE: 'any' type detection is now handled by detectSmartAnyUsage() function below
|
|
56
|
+
// This prevents overwhelming noise from internal utility 'any' usage
|
|
57
|
+
|
|
58
|
+
// ==================== Incomplete security patterns - heavily downgraded ====================
|
|
59
|
+
{
|
|
60
|
+
name: 'AI incomplete validation',
|
|
61
|
+
pattern: /if\s*\(\s*!?\s*(input|data|value|body|params)\s*\)\s*\{?\s*(return|throw)/gi,
|
|
62
|
+
severity: 'info', // Downgraded from low - this is often fine
|
|
63
|
+
description: 'Basic existence check - consider adding type validation if needed',
|
|
64
|
+
suggestedFix: 'Add comprehensive input validation with type and format checks',
|
|
65
|
+
confidence: 'low',
|
|
66
|
+
},
|
|
67
|
+
// NOTE: Removed 'AI basic auth check' pattern entirely - too many false positives
|
|
68
|
+
// Basic auth checks like if (!user) return are correct and common
|
|
69
|
+
|
|
70
|
+
// ==================== AI-specific comment patterns - suppressed (style only) ====================
|
|
71
|
+
// NOTE: Removed 'AI explanatory comment' pattern - verbose comments are style, not security
|
|
72
|
+
// NOTE: Removed 'AI step-by-step comment' pattern - step comments are style, not security
|
|
73
|
+
|
|
74
|
+
// ==================== Dangerous AI patterns ====================
|
|
75
|
+
{
|
|
76
|
+
name: 'AI hardcoded secret pattern',
|
|
77
|
+
pattern: /const\s+(API_KEY|SECRET|PASSWORD|TOKEN)\s*=\s*['"][^'"]+['"]/gi,
|
|
78
|
+
severity: 'critical',
|
|
79
|
+
description: 'Hardcoded secret - common mistake in AI-generated code',
|
|
80
|
+
suggestedFix: 'Move secrets to environment variables',
|
|
81
|
+
confidence: 'high',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'AI example credentials',
|
|
85
|
+
pattern: /(admin|test|demo|example|sample|your)[_-]?(password|secret|key|token)\s*[=:]\s*['"][^'"]+['"]/gi,
|
|
86
|
+
severity: 'high',
|
|
87
|
+
description: 'Example/placeholder credentials that should be replaced',
|
|
88
|
+
suggestedFix: 'Replace example credentials with proper secret management',
|
|
89
|
+
confidence: 'high',
|
|
90
|
+
},
|
|
91
|
+
// NOTE: localhost/example URL detection moved to special handling below
|
|
92
|
+
// to allow context-aware skipping for config/example files
|
|
93
|
+
|
|
94
|
+
// ==================== AI code smell patterns - heavily downgraded ====================
|
|
95
|
+
{
|
|
96
|
+
name: 'AI console.log debugging',
|
|
97
|
+
pattern: /console\.log\s*\(\s*['"]?(debug|testing|here|check|log|data|result|response)/gi,
|
|
98
|
+
severity: 'info', // Downgraded from low - debug logs are common in dev
|
|
99
|
+
description: 'Debug logging that could be removed before production',
|
|
100
|
+
suggestedFix: 'Consider removing debug console.log statements or use proper logging',
|
|
101
|
+
confidence: 'low', // Downgraded
|
|
102
|
+
},
|
|
103
|
+
// NOTE: Removed 'AI magic number' pattern - magic numbers are style, not security
|
|
104
|
+
{
|
|
105
|
+
name: 'AI empty function body',
|
|
106
|
+
pattern: /function\s+\w+\s*\([^)]*\)\s*\{\s*(\/\/.*)?(\n\s*)?\}|=>\s*\{\s*(\/\/.*)?(\n\s*)?\}/gi,
|
|
107
|
+
severity: 'low', // Downgraded from medium
|
|
108
|
+
description: 'Empty function body - may be incomplete implementation',
|
|
109
|
+
suggestedFix: 'Implement the function or remove if not needed',
|
|
110
|
+
confidence: 'low', // Downgraded
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
// ==================== AI boilerplate patterns - heavily downgraded ====================
|
|
114
|
+
{
|
|
115
|
+
name: 'AI boilerplate error message',
|
|
116
|
+
pattern: /['"]Something went wrong['"]|['"]An error occurred['"]|['"]Error processing request['"]/gi,
|
|
117
|
+
severity: 'info', // Downgraded from low - generic messages are acceptable
|
|
118
|
+
description: 'Generic error message - consider more specific information',
|
|
119
|
+
suggestedFix: 'Replace with specific, actionable error messages',
|
|
120
|
+
confidence: 'low', // Downgraded
|
|
121
|
+
},
|
|
122
|
+
// NOTE: Removed 'AI success message' pattern - success messages are style, not security
|
|
123
|
+
|
|
124
|
+
// ==================== AI security bypass patterns - moderated ====================
|
|
125
|
+
{
|
|
126
|
+
name: 'AI disabled security for testing',
|
|
127
|
+
pattern: /\/\/\s*(disable|skip|bypass|ignore)\s*(for\s+)?(testing|development|now|temporarily)/gi,
|
|
128
|
+
severity: 'medium', // Downgraded from high - often intentional in dev
|
|
129
|
+
description: 'Security may be disabled for testing - verify production config',
|
|
130
|
+
suggestedFix: 'Remove testing bypasses and implement proper security',
|
|
131
|
+
confidence: 'medium', // Downgraded
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'AI TODO security',
|
|
135
|
+
pattern: /\/\/\s*TODO:\s*(add|implement|fix)\s*(security|auth|validation|sanitization)/gi,
|
|
136
|
+
severity: 'low', // Downgraded from high - often outdated or already addressed
|
|
137
|
+
description: 'Security feature marked as TODO - verify if addressed',
|
|
138
|
+
suggestedFix: 'Implement the security feature or remove if already done',
|
|
139
|
+
confidence: 'low', // Downgraded
|
|
140
|
+
},
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
// ==================== Smart 'any' Type Detection ====================
|
|
144
|
+
|
|
145
|
+
interface AnyUsageContext {
|
|
146
|
+
lineNumber: number
|
|
147
|
+
lineContent: string
|
|
148
|
+
context: 'api_boundary' | 'database_layer' | 'auth_handler' | 'internal_util' | 'type_definition'
|
|
149
|
+
priority: number
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Check if 'any' usage is a safe/common ORM pattern that should be ignored
|
|
154
|
+
*/
|
|
155
|
+
function isSafeORMPattern(line: string): boolean {
|
|
156
|
+
const safePatterns = [
|
|
157
|
+
// Dexie/IndexedDB patterns
|
|
158
|
+
/\.equals\s*\(\s*null\s+as\s+any/i,
|
|
159
|
+
/\.equals\s*\(\s*\d+\s+as\s+any/i,
|
|
160
|
+
/\.equals\s*\(\s*['"`][^'"`]*['"`]\s+as\s+any/i,
|
|
161
|
+
/\.where\s*\(\s*.*as\s+any\s*\)/i,
|
|
162
|
+
/\.filter\s*\(\s*.*as\s+any\s*\)/i,
|
|
163
|
+
|
|
164
|
+
// Prisma patterns
|
|
165
|
+
/prisma\.\w+\.findMany/i,
|
|
166
|
+
/prisma\.\w+\.findFirst/i,
|
|
167
|
+
/prisma\.\w+\.findUnique/i,
|
|
168
|
+
|
|
169
|
+
// Supabase patterns
|
|
170
|
+
/supabase\.from\s*\(/i,
|
|
171
|
+
|
|
172
|
+
// Internal array maps over DB records (not untrusted input)
|
|
173
|
+
/\.map\s*\(\s*\(\s*\w+\s*:\s*any\s*\)\s*=>/i,
|
|
174
|
+
/\.filter\s*\(\s*\(\s*\w+\s*:\s*any\s*\)\s*=>/i,
|
|
175
|
+
/\.forEach\s*\(\s*\(\s*\w+\s*:\s*any\s*\)\s*=>/i,
|
|
176
|
+
/\.reduce\s*\(\s*\(\s*\w+\s*,\s*\w+\s*:\s*any\s*\)\s*=>/i,
|
|
177
|
+
|
|
178
|
+
// Type coercion for internal data
|
|
179
|
+
/\[\s*\d+\s*\]\s+as\s+any/, // Array index access
|
|
180
|
+
/\.data\s+as\s+any/i, // .data as any (common ORM pattern)
|
|
181
|
+
/\.result\s+as\s+any/i, // .result as any
|
|
182
|
+
/\.rows?\s+as\s+any/i, // .row or .rows as any
|
|
183
|
+
/\.records?\s+as\s+any/i, // .record or .records as any
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
return safePatterns.some(p => p.test(line))
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Check if 'any' usage is in a browser API event handler (safe pattern)
|
|
191
|
+
* These APIs often have incomplete TypeScript typings and require 'any' as a workaround
|
|
192
|
+
*/
|
|
193
|
+
function isBrowserAPIEventHandler(line: string, filePath: string): boolean {
|
|
194
|
+
// Skip if this is likely a server-side file
|
|
195
|
+
if (/\/(api|server|backend|lib\/supabase)\//i.test(filePath)) {
|
|
196
|
+
return false
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const browserAPIPatterns = [
|
|
200
|
+
// Web Speech API (SpeechRecognition)
|
|
201
|
+
/\.onresult\s*=\s*\(?.*:\s*any/i,
|
|
202
|
+
/\.onerror\s*=\s*\(?.*:\s*any/i,
|
|
203
|
+
/\.onend\s*=\s*\(?.*:\s*any/i,
|
|
204
|
+
/\.onstart\s*=\s*\(?.*:\s*any/i,
|
|
205
|
+
/\.onaudiostart\s*=\s*\(?.*:\s*any/i,
|
|
206
|
+
/\.onaudioend\s*=\s*\(?.*:\s*any/i,
|
|
207
|
+
/\.onspeechstart\s*=\s*\(?.*:\s*any/i,
|
|
208
|
+
/\.onspeechend\s*=\s*\(?.*:\s*any/i,
|
|
209
|
+
/speechRecognition/i,
|
|
210
|
+
/SpeechRecognition/i,
|
|
211
|
+
/webkitSpeechRecognition/i,
|
|
212
|
+
|
|
213
|
+
// MediaRecorder / Media APIs
|
|
214
|
+
/\.ondataavailable\s*=\s*\(?.*:\s*any/i,
|
|
215
|
+
/\.onstop\s*=\s*\(?.*:\s*any/i,
|
|
216
|
+
/\.onpause\s*=\s*\(?.*:\s*any/i,
|
|
217
|
+
/\.onresume\s*=\s*\(?.*:\s*any/i,
|
|
218
|
+
/mediaRecorder/i,
|
|
219
|
+
/MediaRecorder/i,
|
|
220
|
+
/MediaStream/i,
|
|
221
|
+
/getUserMedia/i,
|
|
222
|
+
|
|
223
|
+
// WebSocket events
|
|
224
|
+
/\.onopen\s*=\s*\(?.*:\s*any/i,
|
|
225
|
+
/\.onclose\s*=\s*\(?.*:\s*any/i,
|
|
226
|
+
/\.onmessage\s*=\s*\(?.*:\s*any/i,
|
|
227
|
+
/webSocket/i,
|
|
228
|
+
/WebSocket/i,
|
|
229
|
+
|
|
230
|
+
// WebRTC / PeerConnection
|
|
231
|
+
/\.onicecandidate\s*=\s*\(?.*:\s*any/i,
|
|
232
|
+
/\.ontrack\s*=\s*\(?.*:\s*any/i,
|
|
233
|
+
/\.onnegotiationneeded\s*=\s*\(?.*:\s*any/i,
|
|
234
|
+
/RTCPeerConnection/i,
|
|
235
|
+
/peerConnection/i,
|
|
236
|
+
|
|
237
|
+
// Generic browser event handlers with common event names
|
|
238
|
+
/\.(on[a-z]+)\s*=\s*\(\s*(?:event|e|evt)\s*:\s*any\s*\)\s*=>/i,
|
|
239
|
+
/addEventListener\s*\([^,]+,\s*\([^:]+:\s*any\)/i,
|
|
240
|
+
|
|
241
|
+
// React/UI library event handler patterns (Tiptap, ProseMirror, etc.)
|
|
242
|
+
/onStart\s*:\s*\(?.*:\s*any/i,
|
|
243
|
+
/onUpdate\s*:\s*\(?.*:\s*any/i,
|
|
244
|
+
/onTransaction\s*:\s*\(?.*:\s*any/i,
|
|
245
|
+
/onSelectionChange\s*:\s*\(?.*:\s*any/i,
|
|
246
|
+
/onBlur\s*:\s*\(?.*:\s*any/i,
|
|
247
|
+
/onFocus\s*:\s*\(?.*:\s*any/i,
|
|
248
|
+
/props\s*:\s*any/i, // Third-party library props (common workaround)
|
|
249
|
+
|
|
250
|
+
// Intersection Observer, Resize Observer, etc.
|
|
251
|
+
/IntersectionObserver/i,
|
|
252
|
+
/ResizeObserver/i,
|
|
253
|
+
/MutationObserver/i,
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
return browserAPIPatterns.some(p => p.test(line))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Check if 'any' usage is on untrusted external input
|
|
261
|
+
*/
|
|
262
|
+
function isUntrustedInputContext(line: string): boolean {
|
|
263
|
+
const untrustedPatterns = [
|
|
264
|
+
// Request body/params parsing
|
|
265
|
+
/await\s+request\.json\s*\(\s*\)\s*as\s+any/i,
|
|
266
|
+
/req\.body\s+as\s+any/i,
|
|
267
|
+
/request\.body\s+as\s+any/i,
|
|
268
|
+
/req\.params\s+as\s+any/i,
|
|
269
|
+
/req\.query\s+as\s+any/i,
|
|
270
|
+
/event\.body\s+as\s+any/i,
|
|
271
|
+
|
|
272
|
+
// External API responses (if not validated)
|
|
273
|
+
/fetch\s*\([^)]+\).*as\s+any/i,
|
|
274
|
+
/axios\.[^)]+\).*as\s+any/i,
|
|
275
|
+
|
|
276
|
+
// Direct parameter typing without validation
|
|
277
|
+
/\(\s*\w+\s*:\s*any\s*\)\s*=>\s*\{/, // Arrow function with any param (if in API context)
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
return untrustedPatterns.some(p => p.test(line))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Categorize TypeScript 'any' usage by security context
|
|
285
|
+
* Returns sorted list by priority (highest risk first)
|
|
286
|
+
*/
|
|
287
|
+
function categorizeAnyUsage(
|
|
288
|
+
lines: string[],
|
|
289
|
+
filePath: string
|
|
290
|
+
): AnyUsageContext[] {
|
|
291
|
+
const usages: AnyUsageContext[] = []
|
|
292
|
+
const isAPIFile = /api|route|handler|controller|endpoint/.test(filePath.toLowerCase())
|
|
293
|
+
const isDBFile = /repository|model|database|query|prisma|supabase|dexie|db/.test(filePath.toLowerCase())
|
|
294
|
+
const isAuthFile = /auth|login|session|token|password|credential/.test(filePath.toLowerCase())
|
|
295
|
+
|
|
296
|
+
lines.forEach((line, idx) => {
|
|
297
|
+
// Skip if line doesn't contain 'any' type
|
|
298
|
+
if (!/:\s*any\b|<any>|as any/.test(line)) return
|
|
299
|
+
|
|
300
|
+
// Skip comments
|
|
301
|
+
const trimmed = line.trim()
|
|
302
|
+
if (
|
|
303
|
+
trimmed.startsWith('//') ||
|
|
304
|
+
trimmed.startsWith('/*') ||
|
|
305
|
+
trimmed.startsWith('*')
|
|
306
|
+
) {
|
|
307
|
+
return
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Skip safe ORM/database patterns (Dexie, Prisma, Supabase, internal array maps)
|
|
311
|
+
if (isSafeORMPattern(line)) {
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Skip browser API event handlers (SpeechRecognition, MediaRecorder, WebSocket, etc.)
|
|
316
|
+
// These APIs often have incomplete TypeScript typings and 'any' is a legitimate workaround
|
|
317
|
+
if (isBrowserAPIEventHandler(line, filePath)) {
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let context: AnyUsageContext['context'] = 'internal_util'
|
|
322
|
+
let priority = 1
|
|
323
|
+
|
|
324
|
+
// Check if this is untrusted input (highest priority)
|
|
325
|
+
if (isUntrustedInputContext(line)) {
|
|
326
|
+
context = 'api_boundary'
|
|
327
|
+
priority = 10
|
|
328
|
+
}
|
|
329
|
+
// API boundary detection - only if actually on untrusted data
|
|
330
|
+
else if (isAPIFile && /\b(req|request)\.(body|params|query|json)\b/.test(line)) {
|
|
331
|
+
context = 'api_boundary'
|
|
332
|
+
priority = 10
|
|
333
|
+
}
|
|
334
|
+
// Auth handler detection (high priority for auth bypass)
|
|
335
|
+
else if (isAuthFile && /\b(password|token|session|auth|verify|jwt|credential)\b/i.test(line)) {
|
|
336
|
+
context = 'auth_handler'
|
|
337
|
+
priority = 9
|
|
338
|
+
}
|
|
339
|
+
// Database layer - only flag if it's SQL string interpolation, not ORM methods
|
|
340
|
+
else if (isDBFile && /\.(execute|query|raw)\s*\(/i.test(line)) {
|
|
341
|
+
context = 'database_layer'
|
|
342
|
+
priority = 8
|
|
343
|
+
}
|
|
344
|
+
// Type definitions (lowest priority - often unavoidable)
|
|
345
|
+
else if (/\btype\s+\w+|interface\s+\w+|declare\s+/.test(line)) {
|
|
346
|
+
context = 'type_definition'
|
|
347
|
+
priority = 1
|
|
348
|
+
}
|
|
349
|
+
// Internal utilities / array operations on DB results (low priority, skip entirely for now)
|
|
350
|
+
else if (isDBFile || /\.map\s*\(|\.filter\s*\(|\.forEach\s*\(|\.reduce\s*\(/.test(line)) {
|
|
351
|
+
// Skip internal array operations - they're operating on already-fetched data
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
// Other internal utilities
|
|
355
|
+
else {
|
|
356
|
+
context = 'internal_util'
|
|
357
|
+
priority = 3
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
usages.push({
|
|
361
|
+
lineNumber: idx + 1,
|
|
362
|
+
lineContent: line.trim(),
|
|
363
|
+
context,
|
|
364
|
+
priority
|
|
365
|
+
})
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
// Sort by priority (highest first)
|
|
369
|
+
return usages.sort((a, b) => b.priority - a.priority)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Detect TypeScript 'any' usage at security boundaries ONLY
|
|
374
|
+
* Returns vulnerabilities for high-priority 'any' usage, capped at top 5 per file
|
|
375
|
+
*/
|
|
376
|
+
function detectSmartAnyUsage(
|
|
377
|
+
lines: string[],
|
|
378
|
+
filePath: string
|
|
379
|
+
): Vulnerability[] {
|
|
380
|
+
const vulnerabilities: Vulnerability[] = []
|
|
381
|
+
|
|
382
|
+
// Only scan TypeScript files
|
|
383
|
+
if (!/\.(ts|tsx)$/.test(filePath)) {
|
|
384
|
+
return vulnerabilities
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Categorize all 'any' usages by context
|
|
388
|
+
const anyUsageByContext = categorizeAnyUsage(lines, filePath)
|
|
389
|
+
|
|
390
|
+
// Only report high-priority 'any' usages (security boundaries)
|
|
391
|
+
const priorityAny = anyUsageByContext.filter(usage =>
|
|
392
|
+
usage.context === 'api_boundary' ||
|
|
393
|
+
usage.context === 'database_layer' ||
|
|
394
|
+
usage.context === 'auth_handler'
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
// Cap reporting to top 5 per file to avoid overwhelming reports
|
|
398
|
+
const cappedAny = priorityAny.slice(0, 5)
|
|
399
|
+
|
|
400
|
+
if (cappedAny.length === 0) {
|
|
401
|
+
return vulnerabilities
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// If there are many 'any' usages, create a grouped finding
|
|
405
|
+
if (cappedAny.length >= 3) {
|
|
406
|
+
// Create single grouped vulnerability
|
|
407
|
+
const contexts = [...new Set(cappedAny.map(a => a.context))]
|
|
408
|
+
const contextDescriptions = contexts.map(ctx => {
|
|
409
|
+
const count = cappedAny.filter(a => a.context === ctx).length
|
|
410
|
+
const names: Record<string, string> = {
|
|
411
|
+
'api_boundary': 'API request/response handlers',
|
|
412
|
+
'database_layer': 'Database queries',
|
|
413
|
+
'auth_handler': 'Authentication logic'
|
|
414
|
+
}
|
|
415
|
+
return `${count}x in ${names[ctx] || ctx}`
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
vulnerabilities.push({
|
|
419
|
+
id: `ai-fingerprint-any-${filePath}`,
|
|
420
|
+
filePath,
|
|
421
|
+
lineNumber: cappedAny[0].lineNumber,
|
|
422
|
+
lineContent: `Multiple TypeScript 'any' usages at security boundaries`,
|
|
423
|
+
severity: 'low',
|
|
424
|
+
category: 'ai_pattern',
|
|
425
|
+
title: `[AI Pattern] TypeScript 'any' at security boundaries (${cappedAny.length} instances)`,
|
|
426
|
+
description: `Found ${cappedAny.length} 'any' types at critical security boundaries: ${contextDescriptions.join(', ')}. ` +
|
|
427
|
+
`Lines: ${cappedAny.map(a => a.lineNumber).join(', ')}. ` +
|
|
428
|
+
`Consider using explicit types for type safety and to prevent type confusion vulnerabilities.`,
|
|
429
|
+
suggestedFix: 'Replace "any" with explicit types. For request handlers use typed schemas (Zod, Yup). For database queries use typed ORM models.',
|
|
430
|
+
confidence: 'medium',
|
|
431
|
+
layer: 2,
|
|
432
|
+
})
|
|
433
|
+
} else {
|
|
434
|
+
// Report individual findings for 1-2 high-priority 'any' usages
|
|
435
|
+
for (const usage of cappedAny) {
|
|
436
|
+
const contextNames: Record<string, string> = {
|
|
437
|
+
'api_boundary': 'API request/response handler',
|
|
438
|
+
'database_layer': 'Database query',
|
|
439
|
+
'auth_handler': 'Authentication logic'
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
vulnerabilities.push({
|
|
443
|
+
id: `ai-fingerprint-any-${filePath}-${usage.lineNumber}`,
|
|
444
|
+
filePath,
|
|
445
|
+
lineNumber: usage.lineNumber,
|
|
446
|
+
lineContent: usage.lineContent,
|
|
447
|
+
severity: 'low',
|
|
448
|
+
category: 'ai_pattern',
|
|
449
|
+
title: `[AI Pattern] TypeScript 'any' in ${contextNames[usage.context] || usage.context}`,
|
|
450
|
+
description: `Using 'any' type at a security boundary bypasses type checking and can lead to type confusion vulnerabilities. ` +
|
|
451
|
+
`This is especially risky in ${contextNames[usage.context] || usage.context}.`,
|
|
452
|
+
suggestedFix: 'Replace "any" with an explicit type. Use typed request schemas, ORM models, or interface definitions.',
|
|
453
|
+
confidence: 'medium',
|
|
454
|
+
layer: 2,
|
|
455
|
+
})
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return vulnerabilities
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Detect managed AI endpoints without rate limiting (cost abuse risk)
|
|
464
|
+
* Finds routes using provider env keys without rate limiting protection
|
|
465
|
+
*/
|
|
466
|
+
function detectManagedAICostAbuse(
|
|
467
|
+
content: string,
|
|
468
|
+
filePath: string,
|
|
469
|
+
lines: string[]
|
|
470
|
+
): Vulnerability[] {
|
|
471
|
+
const vulnerabilities: Vulnerability[] = []
|
|
472
|
+
|
|
473
|
+
// Only check actual API route files, not utility/handler files
|
|
474
|
+
const isActualRouteFile = /\/(route|page)\.(ts|js|tsx|jsx)$/i.test(filePath) ||
|
|
475
|
+
/\/(api|routes?)\/.*\/index\.(ts|js)$/i.test(filePath)
|
|
476
|
+
|
|
477
|
+
// Files named as handlers, helpers, utils, fixtures etc. are NOT actual routes
|
|
478
|
+
const isUtilityFile = /(handler|helper|util|mock|test|fixture|safe|example|config)/i.test(filePath)
|
|
479
|
+
|
|
480
|
+
if (!isActualRouteFile || isUtilityFile) return vulnerabilities
|
|
481
|
+
|
|
482
|
+
// Check if file uses managed provider keys (from environment)
|
|
483
|
+
const managedKeyPatterns = [
|
|
484
|
+
/process\.env\.OPENAI_API_KEY/i,
|
|
485
|
+
/process\.env\.ANTHROPIC_API_KEY/i,
|
|
486
|
+
/process\.env\.\w*_(API_KEY|SECRET_KEY)/i,
|
|
487
|
+
/import\.meta\.env\.OPENAI/i,
|
|
488
|
+
/import\.meta\.env\.ANTHROPIC/i,
|
|
489
|
+
]
|
|
490
|
+
|
|
491
|
+
const usesManagedKey = managedKeyPatterns.some(p => p.test(content))
|
|
492
|
+
if (!usesManagedKey) return vulnerabilities
|
|
493
|
+
|
|
494
|
+
// Skip if this is a config check (checking if key exists) rather than actual API usage
|
|
495
|
+
// Pattern: if (!process.env.OPENAI_API_KEY) or if (process.env.OPENAI_API_KEY === undefined)
|
|
496
|
+
const isConfigCheck = /if\s*\(\s*!?\s*process\.env\.\w*_API_KEY\s*[=!]|!process\.env\.\w*_API_KEY/i.test(content)
|
|
497
|
+
const hasActualAPICall = /\.chat\.completions|\.messages\.create|\.complete\(|anthropic\.\w+\(/i.test(content)
|
|
498
|
+
|
|
499
|
+
// If it's just a config check without actual API calls, skip
|
|
500
|
+
if (isConfigCheck && !hasActualAPICall) return vulnerabilities
|
|
501
|
+
|
|
502
|
+
// Check for rate limiting patterns nearby
|
|
503
|
+
const rateLimitPatterns = [
|
|
504
|
+
/rateLimit/i,
|
|
505
|
+
/rateLimiter/i,
|
|
506
|
+
/limiter/i,
|
|
507
|
+
/throttle/i,
|
|
508
|
+
/upstash.*ratelimit/i,
|
|
509
|
+
/redis.*limit/i,
|
|
510
|
+
/bucket/i,
|
|
511
|
+
/token.*bucket/i,
|
|
512
|
+
/sliding.*window/i,
|
|
513
|
+
/@upstash\/ratelimit/i,
|
|
514
|
+
/rate-limiter-flexible/i,
|
|
515
|
+
]
|
|
516
|
+
|
|
517
|
+
const hasRateLimiting = rateLimitPatterns.some(p => p.test(content))
|
|
518
|
+
|
|
519
|
+
// Check for auth patterns - expanded to catch more middleware patterns
|
|
520
|
+
const authPatterns = [
|
|
521
|
+
/getServerSession/i,
|
|
522
|
+
/auth\(\)/i,
|
|
523
|
+
/auth\.protect/i,
|
|
524
|
+
/currentUser/i,
|
|
525
|
+
/getCurrentUser/i,
|
|
526
|
+
/getCurrentUserId/i,
|
|
527
|
+
/requireAuth/i,
|
|
528
|
+
/verifyToken/i,
|
|
529
|
+
/session\.user/i,
|
|
530
|
+
/authorization/i,
|
|
531
|
+
/withAuth/i,
|
|
532
|
+
/isAuthenticated/i,
|
|
533
|
+
/checkAuth/i,
|
|
534
|
+
/validateSession/i,
|
|
535
|
+
/clerk/i, // Clerk auth
|
|
536
|
+
/supabase.*auth/i, // Supabase auth
|
|
537
|
+
/nextauth/i, // NextAuth
|
|
538
|
+
/authMiddleware/i,
|
|
539
|
+
/protectedRoute/i,
|
|
540
|
+
/requireSession/i,
|
|
541
|
+
/userId.*=.*auth/i, // userId from auth
|
|
542
|
+
/user\.id/i, // Accessing user.id implies auth
|
|
543
|
+
]
|
|
544
|
+
|
|
545
|
+
const hasAuth = authPatterns.some(p => p.test(content))
|
|
546
|
+
|
|
547
|
+
// Check if route is likely protected by middleware (file path based)
|
|
548
|
+
const isLikelyMiddlewareProtected =
|
|
549
|
+
/\/api\/(protected|private|admin|user|account|dashboard)\//i.test(filePath) ||
|
|
550
|
+
/\/\(authenticated\)\//i.test(filePath) || // Next.js route groups
|
|
551
|
+
/\/\(protected\)\//i.test(filePath) ||
|
|
552
|
+
/\/\(auth\)\//i.test(filePath)
|
|
553
|
+
|
|
554
|
+
// Determine severity based on auth + rate limiting
|
|
555
|
+
if (!hasRateLimiting) {
|
|
556
|
+
// Find the line with the env key usage
|
|
557
|
+
let keyLine = 1
|
|
558
|
+
for (let i = 0; i < lines.length; i++) {
|
|
559
|
+
if (managedKeyPatterns.some(p => p.test(lines[i]))) {
|
|
560
|
+
keyLine = i + 1
|
|
561
|
+
break
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// If route is authenticated (inline or via middleware), this is just operational concern
|
|
566
|
+
if (hasAuth || isLikelyMiddlewareProtected) {
|
|
567
|
+
// Authenticated route without rate limiting - operational concern, not security vuln
|
|
568
|
+
vulnerabilities.push({
|
|
569
|
+
id: `ai-cost-abuse-${filePath}`,
|
|
570
|
+
filePath,
|
|
571
|
+
lineNumber: keyLine,
|
|
572
|
+
lineContent: lines[keyLine - 1]?.trim() || 'process.env.*_API_KEY',
|
|
573
|
+
severity: 'info',
|
|
574
|
+
category: 'ai_pattern',
|
|
575
|
+
title: 'Managed AI endpoint without rate limiting (authenticated)',
|
|
576
|
+
description: 'This authenticated API route uses a managed AI provider key but lacks rate limiting. Authenticated users could potentially abuse the endpoint. This is an operational concern, not a security vulnerability.',
|
|
577
|
+
suggestedFix: 'Consider adding per-user rate limiting (e.g., @upstash/ratelimit) to prevent cost abuse by authenticated users.',
|
|
578
|
+
confidence: 'low',
|
|
579
|
+
layer: 2,
|
|
580
|
+
})
|
|
581
|
+
} else {
|
|
582
|
+
// Unauthenticated route - higher risk
|
|
583
|
+
vulnerabilities.push({
|
|
584
|
+
id: `ai-cost-abuse-${filePath}`,
|
|
585
|
+
filePath,
|
|
586
|
+
lineNumber: keyLine,
|
|
587
|
+
lineContent: lines[keyLine - 1]?.trim() || 'process.env.*_API_KEY',
|
|
588
|
+
severity: 'medium',
|
|
589
|
+
category: 'ai_pattern',
|
|
590
|
+
title: 'Managed AI endpoint without authentication or rate limiting',
|
|
591
|
+
description: 'This API route uses a managed AI provider key without apparent authentication or rate limiting. This could allow unauthenticated cost abuse.',
|
|
592
|
+
suggestedFix: 'Add authentication or rate limiting (e.g., @upstash/ratelimit, rate-limiter-flexible) to prevent cost abuse.',
|
|
593
|
+
confidence: 'medium',
|
|
594
|
+
layer: 2,
|
|
595
|
+
})
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return vulnerabilities
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Check if line contains clearly placeholder credential values
|
|
604
|
+
*/
|
|
605
|
+
function isPlaceholderCredential(line: string): boolean {
|
|
606
|
+
const placeholderPatterns = [
|
|
607
|
+
/your[-_]?api[-_]?key/i,
|
|
608
|
+
/your[-_]?secret/i,
|
|
609
|
+
/your[-_]?password/i,
|
|
610
|
+
/replace[-_]?with/i,
|
|
611
|
+
/example[-_]?key/i,
|
|
612
|
+
/sample[-_]?key/i,
|
|
613
|
+
/demo[-_]?key/i,
|
|
614
|
+
/test[-_]?key/i,
|
|
615
|
+
/fake[-_]?key/i,
|
|
616
|
+
/mock[-_]?key/i,
|
|
617
|
+
/placeholder/i,
|
|
618
|
+
/<.*>/, // <YOUR_KEY>
|
|
619
|
+
/\[.*\]/, // [API_KEY]
|
|
620
|
+
/xxx+/i,
|
|
621
|
+
]
|
|
622
|
+
return placeholderPatterns.some(p => p.test(line))
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Check if file path indicates a config/settings file
|
|
627
|
+
*/
|
|
628
|
+
function isConfigFile(filePath: string): boolean {
|
|
629
|
+
const lowerPath = filePath.toLowerCase()
|
|
630
|
+
return /config|settings|constants|urls|endpoints|env/i.test(lowerPath)
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
export function detectAIFingerprints(
|
|
634
|
+
content: string,
|
|
635
|
+
filePath: string
|
|
636
|
+
): Vulnerability[] {
|
|
637
|
+
const vulnerabilities: Vulnerability[] = []
|
|
638
|
+
const lines = content.split('\n')
|
|
639
|
+
|
|
640
|
+
// Skip example/demo files entirely - they contain placeholder code by design
|
|
641
|
+
if (isExampleFile(filePath)) {
|
|
642
|
+
return vulnerabilities
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const isTestFile = isTestOrMockFile(filePath)
|
|
646
|
+
const isConfigOrSettings = isConfigFile(filePath)
|
|
647
|
+
|
|
648
|
+
// First, run smart 'any' detection (TypeScript files only, context-aware)
|
|
649
|
+
const anyVulns = detectSmartAnyUsage(lines, filePath)
|
|
650
|
+
vulnerabilities.push(...anyVulns)
|
|
651
|
+
|
|
652
|
+
// Detect managed AI cost abuse risk
|
|
653
|
+
const costAbuseVulns = detectManagedAICostAbuse(content, filePath, lines)
|
|
654
|
+
vulnerabilities.push(...costAbuseVulns)
|
|
655
|
+
|
|
656
|
+
// Track AI pattern density for file-level assessment
|
|
657
|
+
let aiPatternCount = 0
|
|
658
|
+
|
|
659
|
+
lines.forEach((line, index) => {
|
|
660
|
+
for (const fingerprint of AI_FINGERPRINTS) {
|
|
661
|
+
const regex = new RegExp(fingerprint.pattern.source, fingerprint.pattern.flags)
|
|
662
|
+
|
|
663
|
+
if (regex.test(line)) {
|
|
664
|
+
// Skip placeholder/example credentials for the "AI example credentials" pattern
|
|
665
|
+
if (fingerprint.name === 'AI example credentials') {
|
|
666
|
+
if (isPlaceholderCredential(line) || isPlaceholderValue('', line) || isTestFile) {
|
|
667
|
+
continue // Skip this pattern, check others
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
aiPatternCount++
|
|
672
|
+
|
|
673
|
+
// Downgrade severity for test files
|
|
674
|
+
let severity = fingerprint.severity
|
|
675
|
+
let confidence = fingerprint.confidence
|
|
676
|
+
if (isTestFile) {
|
|
677
|
+
if (severity === 'critical') severity = 'medium'
|
|
678
|
+
else if (severity === 'high') severity = 'low'
|
|
679
|
+
else severity = 'info'
|
|
680
|
+
confidence = 'low'
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
vulnerabilities.push({
|
|
684
|
+
id: `ai-fingerprint-${filePath}-${index + 1}-${fingerprint.name}`,
|
|
685
|
+
filePath,
|
|
686
|
+
lineNumber: index + 1,
|
|
687
|
+
lineContent: line.trim(),
|
|
688
|
+
severity,
|
|
689
|
+
category: 'ai_pattern',
|
|
690
|
+
title: `[AI Pattern] ${fingerprint.name}`,
|
|
691
|
+
description: fingerprint.description + (isTestFile ? ' (in test file)' : ''),
|
|
692
|
+
suggestedFix: fingerprint.suggestedFix,
|
|
693
|
+
confidence,
|
|
694
|
+
layer: 2,
|
|
695
|
+
})
|
|
696
|
+
break // Only report once per line
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
// Context-aware localhost/example URL detection
|
|
702
|
+
// Skip for config files and test files (they legitimately contain example URLs)
|
|
703
|
+
if (!isConfigOrSettings && !isTestFile) {
|
|
704
|
+
const localhostPattern = /['"]https?:\/\/(localhost|127\.0\.0\.1|example\.com|your-domain|api\.example)[^'"]*['"]/gi
|
|
705
|
+
lines.forEach((line, index) => {
|
|
706
|
+
if (localhostPattern.test(line)) {
|
|
707
|
+
// Reset regex state
|
|
708
|
+
localhostPattern.lastIndex = 0
|
|
709
|
+
// Skip if it's a comment
|
|
710
|
+
const trimmed = line.trim()
|
|
711
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
|
|
712
|
+
return
|
|
713
|
+
}
|
|
714
|
+
// Skip if it looks like env var fallback (process.env.X || "http://localhost")
|
|
715
|
+
if (/process\.env\.\w+\s*\|\|\s*['"]/.test(line)) {
|
|
716
|
+
return
|
|
717
|
+
}
|
|
718
|
+
vulnerabilities.push({
|
|
719
|
+
id: `ai-fingerprint-${filePath}-${index + 1}-localhost-url`,
|
|
720
|
+
filePath,
|
|
721
|
+
lineNumber: index + 1,
|
|
722
|
+
lineContent: line.trim(),
|
|
723
|
+
severity: 'medium',
|
|
724
|
+
category: 'ai_pattern',
|
|
725
|
+
title: '[AI Pattern] AI localhost/example URL',
|
|
726
|
+
description: 'Placeholder URL that should be replaced with actual endpoint',
|
|
727
|
+
suggestedFix: 'Replace with actual production URL from environment variable',
|
|
728
|
+
confidence: 'high',
|
|
729
|
+
layer: 2,
|
|
730
|
+
})
|
|
731
|
+
aiPatternCount++
|
|
732
|
+
}
|
|
733
|
+
})
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// If file has high density of AI patterns, add a summary finding
|
|
737
|
+
const lineCount = lines.length
|
|
738
|
+
const aiDensity = aiPatternCount / Math.max(lineCount, 1)
|
|
739
|
+
|
|
740
|
+
// Raised threshold to 10% and require high-severity patterns to reduce noise
|
|
741
|
+
if (aiDensity > 0.10 && aiPatternCount >= 5) {
|
|
742
|
+
vulnerabilities.push({
|
|
743
|
+
id: `ai-fingerprint-${filePath}-summary`,
|
|
744
|
+
filePath,
|
|
745
|
+
lineNumber: 1,
|
|
746
|
+
lineContent: `File contains ${aiPatternCount} AI-generated code patterns`,
|
|
747
|
+
severity: 'medium',
|
|
748
|
+
category: 'ai_pattern',
|
|
749
|
+
title: '[AI Pattern] High AI-generated code density',
|
|
750
|
+
description: `This file shows ${aiPatternCount} patterns commonly found in AI-generated code. Consider a thorough security review.`,
|
|
751
|
+
suggestedFix: 'Review this file carefully for security issues, incomplete implementations, and placeholder code',
|
|
752
|
+
confidence: 'medium',
|
|
753
|
+
layer: 2,
|
|
754
|
+
})
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return vulnerabilities
|
|
758
|
+
}
|