@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,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 2: Framework-Specific Security Checks
|
|
3
|
+
* Detects security issues specific to popular frameworks (Next.js, Express, React, etc.)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Vulnerability, VulnerabilitySeverity } from '../types'
|
|
7
|
+
import {
|
|
8
|
+
isComment,
|
|
9
|
+
isServerOnlyFile,
|
|
10
|
+
isEnvVarReference,
|
|
11
|
+
getServiceRoleKeyContext,
|
|
12
|
+
isTestOrMockFile,
|
|
13
|
+
} from '../utils/context-helpers'
|
|
14
|
+
|
|
15
|
+
interface FrameworkPattern {
|
|
16
|
+
name: string
|
|
17
|
+
pattern: RegExp
|
|
18
|
+
severity: VulnerabilitySeverity
|
|
19
|
+
description: string
|
|
20
|
+
suggestedFix: string
|
|
21
|
+
framework: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const FRAMEWORK_PATTERNS: FrameworkPattern[] = [
|
|
25
|
+
// ==================== Next.js ====================
|
|
26
|
+
{
|
|
27
|
+
name: 'Next.js API route without auth',
|
|
28
|
+
pattern: /export\s+(default\s+)?(async\s+)?function\s+handler\s*\(/gi,
|
|
29
|
+
severity: 'medium',
|
|
30
|
+
description: 'Next.js API route handler - ensure authentication is implemented',
|
|
31
|
+
suggestedFix: 'Add authentication check at the start of the handler',
|
|
32
|
+
framework: 'nextjs',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'Next.js getServerSideProps without auth',
|
|
36
|
+
pattern: /export\s+(async\s+)?function\s+getServerSideProps/gi,
|
|
37
|
+
severity: 'low',
|
|
38
|
+
description: 'Server-side props function - verify auth for protected pages',
|
|
39
|
+
suggestedFix: 'Check authentication and redirect if needed',
|
|
40
|
+
framework: 'nextjs',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Next.js exposed server action',
|
|
44
|
+
pattern: /['"]use server['"]\s*;?\s*\n\s*export\s+(async\s+)?function/gi,
|
|
45
|
+
severity: 'medium',
|
|
46
|
+
description: 'Server action is publicly accessible - ensure proper validation',
|
|
47
|
+
suggestedFix: 'Add authentication and input validation to server actions',
|
|
48
|
+
framework: 'nextjs',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'Next.js revalidate with user input',
|
|
52
|
+
pattern: /revalidatePath\s*\(\s*(req|request|params|searchParams)/gi,
|
|
53
|
+
severity: 'high',
|
|
54
|
+
description: 'revalidatePath with user input could allow cache poisoning',
|
|
55
|
+
suggestedFix: 'Validate and sanitize paths before revalidation',
|
|
56
|
+
framework: 'nextjs',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'Next.js redirect with user input',
|
|
60
|
+
pattern: /redirect\s*\(\s*(req|request|params|searchParams|url)/gi,
|
|
61
|
+
severity: 'high',
|
|
62
|
+
description: 'Redirect with user input can lead to open redirect vulnerability',
|
|
63
|
+
suggestedFix: 'Validate redirect URLs against an allowlist',
|
|
64
|
+
framework: 'nextjs',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'Next.js unstable_cache without tags',
|
|
68
|
+
pattern: /unstable_cache\s*\([^)]*\)(?!.*tags)/gi,
|
|
69
|
+
severity: 'low',
|
|
70
|
+
description: 'unstable_cache without tags makes invalidation difficult',
|
|
71
|
+
suggestedFix: 'Add cache tags for proper cache invalidation',
|
|
72
|
+
framework: 'nextjs',
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// ==================== Express ====================
|
|
76
|
+
{
|
|
77
|
+
name: 'Express without helmet',
|
|
78
|
+
pattern: /express\s*\(\s*\)(?![\s\S]*helmet)/gi,
|
|
79
|
+
severity: 'medium',
|
|
80
|
+
description: 'Express app may lack security headers (helmet middleware)',
|
|
81
|
+
suggestedFix: 'Add helmet middleware: app.use(helmet())',
|
|
82
|
+
framework: 'express',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'Express CORS allow all',
|
|
86
|
+
pattern: /cors\s*\(\s*\{[^}]*origin\s*:\s*['"]\*['"]/gi,
|
|
87
|
+
severity: 'high',
|
|
88
|
+
description: 'CORS configured to allow all origins',
|
|
89
|
+
suggestedFix: 'Restrict CORS to specific trusted origins',
|
|
90
|
+
framework: 'express',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'Express body parser limit',
|
|
94
|
+
pattern: /bodyParser\.(json|urlencoded)\s*\(\s*\)(?!.*limit)/gi,
|
|
95
|
+
severity: 'low',
|
|
96
|
+
description: 'Body parser without size limit can lead to DoS',
|
|
97
|
+
suggestedFix: 'Add limit option: bodyParser.json({ limit: "100kb" })',
|
|
98
|
+
framework: 'express',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'Express trust proxy',
|
|
102
|
+
pattern: /app\.set\s*\(\s*['"]trust proxy['"]\s*,\s*true\s*\)/gi,
|
|
103
|
+
severity: 'medium',
|
|
104
|
+
description: 'Trust proxy enabled - ensure this is intentional',
|
|
105
|
+
suggestedFix: 'Only enable trust proxy behind a known reverse proxy',
|
|
106
|
+
framework: 'express',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'Express error handler exposes stack',
|
|
110
|
+
pattern: /res\.(json|send)\s*\(\s*\{[^}]*stack|err\.stack/gi,
|
|
111
|
+
severity: 'high',
|
|
112
|
+
description: 'Error stack trace may be exposed to clients',
|
|
113
|
+
suggestedFix: 'Only expose stack traces in development mode',
|
|
114
|
+
framework: 'express',
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// ==================== React ====================
|
|
118
|
+
{
|
|
119
|
+
name: 'React useEffect with async',
|
|
120
|
+
pattern: /useEffect\s*\(\s*async\s*\(/gi,
|
|
121
|
+
severity: 'low',
|
|
122
|
+
description: 'useEffect with async function can cause memory leaks',
|
|
123
|
+
suggestedFix: 'Define async function inside useEffect and call it',
|
|
124
|
+
framework: 'react',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'React state with sensitive data',
|
|
128
|
+
pattern: /useState\s*\(\s*['"]?(password|secret|token|apiKey|api_key)/gi,
|
|
129
|
+
severity: 'medium',
|
|
130
|
+
description: 'Sensitive data in React state may be exposed in dev tools',
|
|
131
|
+
suggestedFix: 'Avoid storing sensitive data in client-side state',
|
|
132
|
+
framework: 'react',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'React href javascript:',
|
|
136
|
+
pattern: /href\s*=\s*[{'"]*javascript:/gi,
|
|
137
|
+
severity: 'high',
|
|
138
|
+
description: 'javascript: URLs can lead to XSS vulnerabilities',
|
|
139
|
+
suggestedFix: 'Use onClick handlers instead of javascript: URLs',
|
|
140
|
+
framework: 'react',
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'React uncontrolled input with defaultValue',
|
|
144
|
+
pattern: /defaultValue\s*=\s*\{[^}]*(password|secret|token)/gi,
|
|
145
|
+
severity: 'medium',
|
|
146
|
+
description: 'Sensitive data in defaultValue may persist in DOM',
|
|
147
|
+
suggestedFix: 'Use controlled inputs for sensitive data',
|
|
148
|
+
framework: 'react',
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
// ==================== Vue ====================
|
|
152
|
+
{
|
|
153
|
+
name: 'Vue v-html directive',
|
|
154
|
+
pattern: /v-html\s*=\s*['"]/gi,
|
|
155
|
+
severity: 'high',
|
|
156
|
+
description: 'v-html can lead to XSS if content is not sanitized',
|
|
157
|
+
suggestedFix: 'Sanitize HTML content or use v-text for plain text',
|
|
158
|
+
framework: 'vue',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: 'Vue computed with side effects',
|
|
162
|
+
pattern: /computed\s*:\s*\{[^}]*\b(fetch|axios|http|api)\b/gi,
|
|
163
|
+
severity: 'low',
|
|
164
|
+
description: 'Computed properties with side effects can cause issues',
|
|
165
|
+
suggestedFix: 'Move API calls to methods or lifecycle hooks',
|
|
166
|
+
framework: 'vue',
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
// ==================== Django ====================
|
|
170
|
+
{
|
|
171
|
+
name: 'Django DEBUG in production',
|
|
172
|
+
pattern: /DEBUG\s*=\s*True/gi,
|
|
173
|
+
severity: 'critical',
|
|
174
|
+
description: 'Django DEBUG mode should be disabled in production',
|
|
175
|
+
suggestedFix: 'Set DEBUG = False in production settings',
|
|
176
|
+
framework: 'django',
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'Django ALLOWED_HOSTS wildcard',
|
|
180
|
+
pattern: /ALLOWED_HOSTS\s*=\s*\[\s*['"]\*['"]/gi,
|
|
181
|
+
severity: 'high',
|
|
182
|
+
description: 'ALLOWED_HOSTS with wildcard allows any host',
|
|
183
|
+
suggestedFix: 'Specify exact allowed hostnames',
|
|
184
|
+
framework: 'django',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'Django SECRET_KEY hardcoded',
|
|
188
|
+
pattern: /SECRET_KEY\s*=\s*['"][^'"]+['"]/gi,
|
|
189
|
+
severity: 'critical',
|
|
190
|
+
description: 'Django SECRET_KEY should not be hardcoded',
|
|
191
|
+
suggestedFix: 'Load SECRET_KEY from environment variable',
|
|
192
|
+
framework: 'django',
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
// ==================== Flask ====================
|
|
196
|
+
{
|
|
197
|
+
name: 'Flask debug mode',
|
|
198
|
+
pattern: /app\.run\s*\([^)]*debug\s*=\s*True/gi,
|
|
199
|
+
severity: 'critical',
|
|
200
|
+
description: 'Flask debug mode should be disabled in production',
|
|
201
|
+
suggestedFix: 'Set debug=False in production',
|
|
202
|
+
framework: 'flask',
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: 'Flask secret key hardcoded',
|
|
206
|
+
pattern: /app\.secret_key\s*=\s*['"][^'"]+['"]/gi,
|
|
207
|
+
severity: 'critical',
|
|
208
|
+
description: 'Flask secret_key should not be hardcoded',
|
|
209
|
+
suggestedFix: 'Load secret_key from environment variable',
|
|
210
|
+
framework: 'flask',
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
// ==================== Supabase ====================
|
|
214
|
+
{
|
|
215
|
+
name: 'Supabase service role key exposed',
|
|
216
|
+
pattern: /supabaseServiceRole|service_role|SUPABASE_SERVICE_ROLE/gi,
|
|
217
|
+
severity: 'critical',
|
|
218
|
+
description: 'Supabase service role key should never be exposed to client',
|
|
219
|
+
suggestedFix: 'Only use service role key in server-side code',
|
|
220
|
+
framework: 'supabase',
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'Supabase RLS bypass',
|
|
224
|
+
pattern: /\.rpc\s*\(\s*['"][^'"]+['"]\s*,\s*\{[^}]*\}\s*,\s*\{[^}]*count/gi,
|
|
225
|
+
severity: 'medium',
|
|
226
|
+
description: 'RPC call may bypass Row Level Security',
|
|
227
|
+
suggestedFix: 'Ensure RPC functions have proper security checks',
|
|
228
|
+
framework: 'supabase',
|
|
229
|
+
},
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
// Detect framework from file content and path
|
|
233
|
+
function detectFramework(content: string, filePath: string): string[] {
|
|
234
|
+
const frameworks: string[] = []
|
|
235
|
+
|
|
236
|
+
// Next.js
|
|
237
|
+
if (content.includes('next/') || content.includes('from "next"') ||
|
|
238
|
+
filePath.includes('/app/') || filePath.includes('/pages/')) {
|
|
239
|
+
frameworks.push('nextjs')
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Express
|
|
243
|
+
if (content.includes('express()') || content.includes('from "express"') ||
|
|
244
|
+
content.includes("require('express')")) {
|
|
245
|
+
frameworks.push('express')
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// React
|
|
249
|
+
if (content.includes('from "react"') || content.includes("from 'react'") ||
|
|
250
|
+
content.includes('useState') || content.includes('useEffect')) {
|
|
251
|
+
frameworks.push('react')
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Vue
|
|
255
|
+
if (content.includes('from "vue"') || content.includes('Vue.component') ||
|
|
256
|
+
filePath.endsWith('.vue')) {
|
|
257
|
+
frameworks.push('vue')
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Django
|
|
261
|
+
if (content.includes('from django') || content.includes('import django') ||
|
|
262
|
+
filePath.includes('settings.py')) {
|
|
263
|
+
frameworks.push('django')
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Flask
|
|
267
|
+
if (content.includes('from flask') || content.includes('Flask(__name__)')) {
|
|
268
|
+
frameworks.push('flask')
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Supabase
|
|
272
|
+
if (content.includes('supabase') || content.includes('@supabase/')) {
|
|
273
|
+
frameworks.push('supabase')
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return frameworks
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function detectFrameworkIssues(
|
|
280
|
+
content: string,
|
|
281
|
+
filePath: string
|
|
282
|
+
): Vulnerability[] {
|
|
283
|
+
const vulnerabilities: Vulnerability[] = []
|
|
284
|
+
const lines = content.split('\n')
|
|
285
|
+
const detectedFrameworks = detectFramework(content, filePath)
|
|
286
|
+
const isTestFile = isTestOrMockFile(filePath)
|
|
287
|
+
|
|
288
|
+
lines.forEach((line, index) => {
|
|
289
|
+
// Skip comment lines
|
|
290
|
+
if (isComment(line)) return
|
|
291
|
+
|
|
292
|
+
for (const pattern of FRAMEWORK_PATTERNS) {
|
|
293
|
+
// Only check patterns for detected frameworks (or check all if none detected)
|
|
294
|
+
if (detectedFrameworks.length > 0 && !detectedFrameworks.includes(pattern.framework)) {
|
|
295
|
+
continue
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
|
|
299
|
+
|
|
300
|
+
if (regex.test(line)) {
|
|
301
|
+
// Special handling for Supabase service role key
|
|
302
|
+
if (pattern.name === 'Supabase service role key exposed') {
|
|
303
|
+
// Check if this is a proper Supabase client setup pattern
|
|
304
|
+
// Expected safe pattern:
|
|
305
|
+
// - Variable named supabaseServiceRoleKey (or similar)
|
|
306
|
+
// - Loaded from process.env.SUPABASE_SERVICE_ROLE_KEY
|
|
307
|
+
// - Used in server-only file (lib/supabase/server.ts, /api/, etc.)
|
|
308
|
+
|
|
309
|
+
const isServerFile = isServerOnlyFile(filePath)
|
|
310
|
+
const usesEnvVar = isEnvVarReference(line)
|
|
311
|
+
|
|
312
|
+
// Check if this line is just a variable declaration from env var
|
|
313
|
+
// e.g., const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY!
|
|
314
|
+
const isEnvVarDeclaration = /(?:const|let|var)\s+\w*(?:service.*role|supabase.*key)\w*\s*=\s*process\.env\./i.test(line)
|
|
315
|
+
|
|
316
|
+
// Check if this is a createClient call with variable reference (not hardcoded)
|
|
317
|
+
// e.g., createClient(supabaseUrl, supabaseServiceRoleKey, {...})
|
|
318
|
+
const isCreateClientWithVar = /createClient\s*\(\s*\w+\s*,\s*\w*(?:service.*role|key)\w*\s*[,)]/i.test(line)
|
|
319
|
+
|
|
320
|
+
// If server file + env var pattern OR server file + createClient with variable reference, it's safe
|
|
321
|
+
if (isServerFile) {
|
|
322
|
+
if (isEnvVarDeclaration || usesEnvVar) {
|
|
323
|
+
// Safe: declaring/loading from env var in server file
|
|
324
|
+
continue
|
|
325
|
+
}
|
|
326
|
+
if (isCreateClientWithVar && !/"[a-zA-Z0-9._-]{20,}"/.test(line)) {
|
|
327
|
+
// Safe: using variable (not hardcoded string) in createClient
|
|
328
|
+
// Check surrounding content for env var loading
|
|
329
|
+
const fileHasEnvVarLoading = content.includes('process.env.SUPABASE_SERVICE_ROLE_KEY')
|
|
330
|
+
if (fileHasEnvVarLoading) {
|
|
331
|
+
continue
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const serviceRoleContext = getServiceRoleKeyContext(line, filePath)
|
|
337
|
+
|
|
338
|
+
// Double-check: Server-only file using env var = safe, skip entirely
|
|
339
|
+
if (serviceRoleContext === 'safe_server') {
|
|
340
|
+
continue
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Determine severity and whether AI validation is needed
|
|
344
|
+
let adjustedSeverity = pattern.severity
|
|
345
|
+
let adjustedDescription = pattern.description
|
|
346
|
+
let requiresAIValidation = false
|
|
347
|
+
let adjustedConfidence: 'high' | 'medium' | 'low' = 'medium'
|
|
348
|
+
|
|
349
|
+
if (serviceRoleContext === 'client_exposure') {
|
|
350
|
+
// Client exposure = critical
|
|
351
|
+
adjustedSeverity = 'critical'
|
|
352
|
+
adjustedDescription = 'Supabase service role key may be exposed to client - this bypasses RLS'
|
|
353
|
+
requiresAIValidation = true
|
|
354
|
+
adjustedConfidence = 'high'
|
|
355
|
+
} else {
|
|
356
|
+
// needs_review
|
|
357
|
+
if (isEnvVarReference(line)) {
|
|
358
|
+
// Using env var in non-server file - needs review
|
|
359
|
+
adjustedSeverity = 'medium'
|
|
360
|
+
adjustedDescription = 'Supabase service role key usage - verify this is server-only code'
|
|
361
|
+
requiresAIValidation = true
|
|
362
|
+
} else if (isServerOnlyFile(filePath)) {
|
|
363
|
+
// Hardcoded in server file - bad practice but not critical
|
|
364
|
+
adjustedSeverity = 'high'
|
|
365
|
+
adjustedDescription = 'Supabase service role key should use environment variable'
|
|
366
|
+
requiresAIValidation = true
|
|
367
|
+
} else {
|
|
368
|
+
// Unknown context - flag for review
|
|
369
|
+
adjustedSeverity = 'high'
|
|
370
|
+
requiresAIValidation = true
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Downgrade test file severity
|
|
375
|
+
if (isTestFile) {
|
|
376
|
+
adjustedSeverity = 'low'
|
|
377
|
+
adjustedConfidence = 'low'
|
|
378
|
+
adjustedDescription = `${adjustedDescription} (in test file)`
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
vulnerabilities.push({
|
|
382
|
+
id: `framework-${filePath}-${index + 1}-${pattern.name}`,
|
|
383
|
+
filePath,
|
|
384
|
+
lineNumber: index + 1,
|
|
385
|
+
lineContent: line.trim(),
|
|
386
|
+
severity: adjustedSeverity,
|
|
387
|
+
category: 'insecure_config',
|
|
388
|
+
title: `[${pattern.framework}] ${pattern.name}`,
|
|
389
|
+
description: adjustedDescription,
|
|
390
|
+
suggestedFix: pattern.suggestedFix,
|
|
391
|
+
confidence: adjustedConfidence,
|
|
392
|
+
layer: 2,
|
|
393
|
+
requiresAIValidation,
|
|
394
|
+
})
|
|
395
|
+
break
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Standard handling for other patterns
|
|
399
|
+
let severity = pattern.severity
|
|
400
|
+
let confidence: 'high' | 'medium' | 'low' = 'medium'
|
|
401
|
+
|
|
402
|
+
// Downgrade test file severity
|
|
403
|
+
if (isTestFile) {
|
|
404
|
+
if (severity === 'critical') {
|
|
405
|
+
severity = 'medium'
|
|
406
|
+
} else if (severity === 'high') {
|
|
407
|
+
severity = 'low'
|
|
408
|
+
} else {
|
|
409
|
+
severity = 'info'
|
|
410
|
+
}
|
|
411
|
+
confidence = 'low'
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
vulnerabilities.push({
|
|
415
|
+
id: `framework-${filePath}-${index + 1}-${pattern.name}`,
|
|
416
|
+
filePath,
|
|
417
|
+
lineNumber: index + 1,
|
|
418
|
+
lineContent: line.trim(),
|
|
419
|
+
severity,
|
|
420
|
+
category: 'insecure_config',
|
|
421
|
+
title: `[${pattern.framework}] ${pattern.name}`,
|
|
422
|
+
description: isTestFile ? `${pattern.description} (in test file)` : pattern.description,
|
|
423
|
+
suggestedFix: pattern.suggestedFix,
|
|
424
|
+
confidence,
|
|
425
|
+
layer: 2,
|
|
426
|
+
})
|
|
427
|
+
break // Only report once per line
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
return vulnerabilities
|
|
433
|
+
}
|