@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,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 2: Authentication Anti-Pattern Detection
|
|
3
|
+
* Identifies weak or missing authentication/authorization patterns
|
|
4
|
+
*
|
|
5
|
+
* Key improvements:
|
|
6
|
+
* - Respects global middleware protection (caps severity at info)
|
|
7
|
+
* - Detects throwing auth helpers and suppresses redundant null checks
|
|
8
|
+
* - Properly classifies public endpoints
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Vulnerability, VulnerabilitySeverity } from '../types'
|
|
12
|
+
import type { MiddlewareAuthConfig } from '../utils/middleware-detector'
|
|
13
|
+
import { isRouteProtectedByMiddleware, getRoutePathFromFile } from '../utils/middleware-detector'
|
|
14
|
+
import type { AuthHelper, AuthHelperContext } from '../utils/auth-helper-detector'
|
|
15
|
+
import { hasAuthHelperCallBefore, isUserIdAlreadyValidated } from '../utils/auth-helper-detector'
|
|
16
|
+
import type { FileAuthImports } from '../utils/imported-auth-detector'
|
|
17
|
+
|
|
18
|
+
interface AuthAntiPattern {
|
|
19
|
+
name: string
|
|
20
|
+
pattern: RegExp
|
|
21
|
+
severity: VulnerabilitySeverity
|
|
22
|
+
description: string
|
|
23
|
+
suggestedFix: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const AUTH_ANTIPATTERNS: AuthAntiPattern[] = [
|
|
27
|
+
// Missing auth checks
|
|
28
|
+
{
|
|
29
|
+
name: 'Unprotected API route',
|
|
30
|
+
pattern: /export\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)\s*\(/gi,
|
|
31
|
+
severity: 'medium',
|
|
32
|
+
description: 'API route handler may lack authentication - verify auth is checked',
|
|
33
|
+
suggestedFix: 'Add authentication middleware or check session at the start of the handler',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'Express route without auth middleware',
|
|
37
|
+
pattern: /\.(get|post|put|delete|patch)\s*\(\s*['"][^'"]+['"]\s*,\s*(async\s*)?\(\s*(req|request)/gi,
|
|
38
|
+
severity: 'medium',
|
|
39
|
+
description: 'Express route may lack authentication middleware',
|
|
40
|
+
suggestedFix: 'Add authentication middleware before the route handler',
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
// Weak authentication patterns
|
|
44
|
+
{
|
|
45
|
+
name: 'Hardcoded credentials check',
|
|
46
|
+
pattern: /if\s*\(\s*(username|user|email)\s*===?\s*['"][^'"]+['"]\s*&&\s*(password|pass|pwd)\s*===?\s*['"][^'"]+['"]/gi,
|
|
47
|
+
severity: 'critical',
|
|
48
|
+
description: 'Hardcoded credentials in authentication logic',
|
|
49
|
+
suggestedFix: 'Use a proper user database with hashed passwords',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'Password in plain text comparison',
|
|
53
|
+
pattern: /password\s*===?\s*user\.password|user\.password\s*===?\s*password/gi,
|
|
54
|
+
severity: 'high',
|
|
55
|
+
description: 'Plain text password comparison detected',
|
|
56
|
+
suggestedFix: 'Use bcrypt.compare() or similar for password verification',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'JWT without verification',
|
|
60
|
+
pattern: /jwt\.decode\s*\([^)]+\)(?!.*verify)/gi,
|
|
61
|
+
severity: 'high',
|
|
62
|
+
description: 'JWT decoded without signature verification',
|
|
63
|
+
suggestedFix: 'Use jwt.verify() instead of jwt.decode() for authentication',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'Weak JWT secret',
|
|
67
|
+
pattern: /jwt\.sign\s*\([^)]+,\s*['"][^'"]{1,20}['"]/gi,
|
|
68
|
+
severity: 'high',
|
|
69
|
+
description: 'JWT signed with a short/weak secret',
|
|
70
|
+
suggestedFix: 'Use a strong, random secret of at least 256 bits from environment variables',
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// Session issues
|
|
74
|
+
{
|
|
75
|
+
name: 'Session without secure flag',
|
|
76
|
+
pattern: /session\s*\(\s*\{[^}]*(?!secure\s*:\s*true)[^}]*\}/gi,
|
|
77
|
+
severity: 'medium',
|
|
78
|
+
description: 'Session configuration may lack secure flag',
|
|
79
|
+
suggestedFix: 'Set secure: true for cookies in production',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'Cookie without httpOnly',
|
|
83
|
+
pattern: /cookie\s*\(\s*['"][^'"]+['"]\s*,[^)]*(?!httpOnly)[^)]*\)|setCookie\s*\([^)]*(?!httpOnly)/gi,
|
|
84
|
+
severity: 'medium',
|
|
85
|
+
description: 'Cookie set without httpOnly flag',
|
|
86
|
+
suggestedFix: 'Add httpOnly: true to prevent XSS access to cookies',
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// Authorization issues
|
|
90
|
+
// NOTE: We intentionally do NOT flag "if (!user)" or "if (!userId)" patterns as issues
|
|
91
|
+
// when throwing auth helpers are in use. Those helpers guarantee the user exists.
|
|
92
|
+
// This pattern is now much more targeted.
|
|
93
|
+
{
|
|
94
|
+
name: 'Missing role check for admin operation',
|
|
95
|
+
pattern: /\b(admin|superuser|moderator|owner)\b.*(?:delete|remove|update|modify|grant|revoke)/gi,
|
|
96
|
+
severity: 'low',
|
|
97
|
+
description: 'Potentially privileged operation - verify role/permission checks are in place',
|
|
98
|
+
suggestedFix: 'Add role-based access control for sensitive operations',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'Client-side only auth check',
|
|
102
|
+
pattern: /if\s*\(\s*!?\s*(isAuthenticated|isLoggedIn|user)\s*\)\s*\{?\s*(router\.push|navigate|redirect|window\.location)/gi,
|
|
103
|
+
severity: 'medium',
|
|
104
|
+
description: 'Client-side only authentication redirect detected',
|
|
105
|
+
suggestedFix: 'Implement server-side authentication checks as well',
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// Insecure token handling
|
|
109
|
+
{
|
|
110
|
+
name: 'Token in URL',
|
|
111
|
+
pattern: /\?.*token=|&token=|\?.*api_key=|&api_key=/gi,
|
|
112
|
+
severity: 'high',
|
|
113
|
+
description: 'Sensitive token passed in URL query parameter',
|
|
114
|
+
suggestedFix: 'Pass tokens in Authorization header or request body',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'Token in localStorage',
|
|
118
|
+
pattern: /localStorage\.(setItem|getItem)\s*\(\s*['"](token|jwt|auth|session|apiKey)/gi,
|
|
119
|
+
severity: 'medium',
|
|
120
|
+
description: 'Sensitive token stored in localStorage (XSS vulnerable)',
|
|
121
|
+
suggestedFix: 'Use httpOnly cookies for token storage',
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
// OAuth/Social auth issues
|
|
125
|
+
{
|
|
126
|
+
name: 'OAuth state parameter missing',
|
|
127
|
+
pattern: /oauth|authorize\?.*(?!state=)/gi,
|
|
128
|
+
severity: 'medium',
|
|
129
|
+
description: 'OAuth flow may lack state parameter for CSRF protection',
|
|
130
|
+
suggestedFix: 'Include a random state parameter in OAuth requests',
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// Password handling issues
|
|
134
|
+
{
|
|
135
|
+
name: 'Password logged',
|
|
136
|
+
pattern: /console\.(log|info|debug|warn|error)\s*\([^)]*password/gi,
|
|
137
|
+
severity: 'critical',
|
|
138
|
+
description: 'Password may be logged to console',
|
|
139
|
+
suggestedFix: 'Never log passwords or sensitive credentials',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'Password in error message',
|
|
143
|
+
pattern: /throw\s+new\s+Error\s*\([^)]*password|Error\s*\([^)]*password/gi,
|
|
144
|
+
severity: 'high',
|
|
145
|
+
description: 'Password may be included in error message',
|
|
146
|
+
suggestedFix: 'Never include passwords in error messages',
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
// Rate limiting
|
|
150
|
+
{
|
|
151
|
+
name: 'Login without rate limiting',
|
|
152
|
+
pattern: /\/(login|signin|auth|authenticate)\s*['"],\s*(async\s*)?\(/gi,
|
|
153
|
+
severity: 'medium',
|
|
154
|
+
description: 'Login endpoint may lack rate limiting',
|
|
155
|
+
suggestedFix: 'Add rate limiting to prevent brute force attacks',
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
// 2FA bypass
|
|
159
|
+
{
|
|
160
|
+
name: 'Potential 2FA bypass',
|
|
161
|
+
pattern: /skip2fa|bypass2fa|disable2fa|twoFactor\s*[=:]\s*false/gi,
|
|
162
|
+
severity: 'high',
|
|
163
|
+
description: 'Two-factor authentication bypass detected',
|
|
164
|
+
suggestedFix: 'Remove 2FA bypass options in production',
|
|
165
|
+
},
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
// Check if line is a comment
|
|
169
|
+
function isComment(line: string): boolean {
|
|
170
|
+
const trimmed = line.trim()
|
|
171
|
+
return (
|
|
172
|
+
trimmed.startsWith('//') ||
|
|
173
|
+
trimmed.startsWith('#') ||
|
|
174
|
+
trimmed.startsWith('*') ||
|
|
175
|
+
trimmed.startsWith('/*')
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Check if file is likely an auth-related file
|
|
180
|
+
function isAuthRelatedFile(filePath: string): boolean {
|
|
181
|
+
const authKeywords = ['auth', 'login', 'session', 'user', 'account', 'credential', 'password', 'token', 'jwt', 'oauth']
|
|
182
|
+
const lowerPath = filePath.toLowerCase()
|
|
183
|
+
return authKeywords.some(keyword => lowerPath.includes(keyword))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check if endpoint is a known public endpoint (health checks, webhooks, cron)
|
|
187
|
+
function isKnownPublicEndpoint(lineContent: string, filePath: string): boolean {
|
|
188
|
+
const PUBLIC_ENDPOINTS = [
|
|
189
|
+
// Health checks
|
|
190
|
+
/\/health\b/i,
|
|
191
|
+
/\/healthz\b/i,
|
|
192
|
+
/\/ready\b/i,
|
|
193
|
+
/\/live\b/i,
|
|
194
|
+
/\/ping\b/i,
|
|
195
|
+
/\/status\b/i,
|
|
196
|
+
|
|
197
|
+
// Webhooks (receive external calls)
|
|
198
|
+
/\/webhook\b/i,
|
|
199
|
+
/\/webhooks\//i,
|
|
200
|
+
/\/callback\b/i,
|
|
201
|
+
|
|
202
|
+
// Cron/scheduled tasks
|
|
203
|
+
/\/cron\//i,
|
|
204
|
+
/\/scheduled\//i,
|
|
205
|
+
/\/tasks\//i,
|
|
206
|
+
|
|
207
|
+
// Public APIs
|
|
208
|
+
/\/public\//i,
|
|
209
|
+
/\bGET\b.*\/api\/\w+\/\[id\]/i, // Public resource reads with ID param
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
return PUBLIC_ENDPOINTS.some(pattern =>
|
|
213
|
+
pattern.test(lineContent) || pattern.test(filePath)
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Check if there are auth indicators in nearby lines (within 15 lines)
|
|
218
|
+
function hasAuthCheckNearby(lines: string[], lineIndex: number): boolean {
|
|
219
|
+
const startLine = Math.max(0, lineIndex)
|
|
220
|
+
const endLine = Math.min(lines.length, lineIndex + 15)
|
|
221
|
+
const searchWindow = lines.slice(startLine, endLine)
|
|
222
|
+
|
|
223
|
+
const authIndicators = [
|
|
224
|
+
/authorization/i,
|
|
225
|
+
/bearer\s+token/i,
|
|
226
|
+
/req\.user/,
|
|
227
|
+
/request\.user/,
|
|
228
|
+
/isAuthenticated/,
|
|
229
|
+
/requireAuth/,
|
|
230
|
+
/verifyToken/,
|
|
231
|
+
/checkPermission/,
|
|
232
|
+
/getServerSession/,
|
|
233
|
+
/auth\(\)/,
|
|
234
|
+
/middleware.*auth/i,
|
|
235
|
+
/session\s*\.\s*user/,
|
|
236
|
+
// Internal secret checks (network-level auth)
|
|
237
|
+
/internal.?secret/i,
|
|
238
|
+
/INTERNAL_SECRET/,
|
|
239
|
+
/x-internal-secret/i,
|
|
240
|
+
/admin.?secret/i,
|
|
241
|
+
/service.?token/i,
|
|
242
|
+
// BYOK patterns - user provides their own API key (implicit auth)
|
|
243
|
+
/userApiKey|user_api_key|clientApiKey/i,
|
|
244
|
+
/req\.body\.(?:apiKey|api_key|openaiKey|anthropicKey)/i,
|
|
245
|
+
/headers\[['"`]x-(?:openai|api|anthropic)-key['"`]\]/i,
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
return searchWindow.some(line =>
|
|
249
|
+
authIndicators.some(pattern => pattern.test(line))
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export interface AuthAntipatternOptions {
|
|
254
|
+
middlewareConfig?: MiddlewareAuthConfig
|
|
255
|
+
authHelpers?: AuthHelperContext
|
|
256
|
+
fileAuthImports?: Map<string, FileAuthImports>
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function detectAuthAntipatterns(
|
|
260
|
+
content: string,
|
|
261
|
+
filePath: string,
|
|
262
|
+
options: AuthAntipatternOptions = {}
|
|
263
|
+
): Vulnerability[] {
|
|
264
|
+
const { middlewareConfig, authHelpers, fileAuthImports } = options
|
|
265
|
+
const vulnerabilities: Vulnerability[] = []
|
|
266
|
+
const lines = content.split('\n')
|
|
267
|
+
const isAuthFile = isAuthRelatedFile(filePath)
|
|
268
|
+
|
|
269
|
+
// Check if this route is protected by global middleware
|
|
270
|
+
const routePath = getRoutePathFromFile(filePath)
|
|
271
|
+
const middlewareProtection = routePath && middlewareConfig
|
|
272
|
+
? isRouteProtectedByMiddleware(routePath, middlewareConfig)
|
|
273
|
+
: { isProtected: false, reason: '' }
|
|
274
|
+
|
|
275
|
+
// Check if file uses imported auth middleware/helpers
|
|
276
|
+
const importedAuthProtection = fileAuthImports?.get(filePath)
|
|
277
|
+
const usesImportedAuth = importedAuthProtection?.usesImportedAuth ?? false
|
|
278
|
+
|
|
279
|
+
// Check if file uses throwing auth helpers
|
|
280
|
+
const helpersList = authHelpers?.helpers || []
|
|
281
|
+
|
|
282
|
+
lines.forEach((line, index) => {
|
|
283
|
+
// Skip comment lines
|
|
284
|
+
if (isComment(line)) return
|
|
285
|
+
|
|
286
|
+
for (const pattern of AUTH_ANTIPATTERNS) {
|
|
287
|
+
const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
|
|
288
|
+
|
|
289
|
+
if (regex.test(line)) {
|
|
290
|
+
// Special handling for unprotected route patterns
|
|
291
|
+
if (pattern.name === 'Unprotected API route' ||
|
|
292
|
+
pattern.name === 'Express route without auth middleware') {
|
|
293
|
+
|
|
294
|
+
// PRIORITY 0: Check if this is actually a route file
|
|
295
|
+
// In Next.js, routes must be in `route.ts/js` files. Files like `handlers.ts`,
|
|
296
|
+
// `safe-handlers.ts`, `utils.ts` etc. are NOT actual API routes even if they
|
|
297
|
+
// export GET/POST functions.
|
|
298
|
+
const isActualRouteFile = /\/(route|page)\.(ts|js|tsx|jsx)$/i.test(filePath) ||
|
|
299
|
+
/\/(api|routes?)\/.*\/(index|route)\.(ts|js)$/i.test(filePath) ||
|
|
300
|
+
// Express/Koa routes typically have 'routes' or 'router' in path
|
|
301
|
+
/\/(routes?|router|controllers?)\.[tj]s$/i.test(filePath)
|
|
302
|
+
|
|
303
|
+
// Files explicitly named as handlers, helpers, utils are not routes
|
|
304
|
+
const isUtilityFile = /(handler|helper|util|mock|test|fixture|safe|example)/i.test(filePath)
|
|
305
|
+
|
|
306
|
+
if (!isActualRouteFile || isUtilityFile) {
|
|
307
|
+
// Not an actual route file - skip this finding
|
|
308
|
+
break
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// PRIORITY 1: Check if route is protected by global middleware
|
|
312
|
+
// This is the STRONGEST signal - if middleware protects the route, suppress entirely
|
|
313
|
+
if (middlewareProtection.isProtected) {
|
|
314
|
+
// Route is authenticated by middleware - no finding needed
|
|
315
|
+
break // Skip this pattern, route is protected
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// PRIORITY 1.5: Check if file imports and uses auth middleware
|
|
319
|
+
// e.g., import { authMiddleware } from '@/lib/auth' + wraps handlers
|
|
320
|
+
if (usesImportedAuth) {
|
|
321
|
+
// File imports auth middleware and uses it - no finding needed
|
|
322
|
+
break // Skip this pattern, route is protected via imported auth
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// PRIORITY 2: Check if file uses throwing auth helpers
|
|
326
|
+
// If getCurrentUserId() or similar is called, the route is authenticated
|
|
327
|
+
const authHelperCall = hasAuthHelperCallBefore(content, index, helpersList)
|
|
328
|
+
if (authHelperCall.hasCall && authHelperCall.helper) {
|
|
329
|
+
// Route uses a throwing auth helper - no finding needed
|
|
330
|
+
// The auth helper guarantees authenticated context
|
|
331
|
+
break // Skip this pattern, route is protected
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// PRIORITY 3: Check if this is a known public endpoint
|
|
335
|
+
if (isKnownPublicEndpoint(line, filePath)) {
|
|
336
|
+
vulnerabilities.push({
|
|
337
|
+
id: `auth-antipattern-${filePath}-${index + 1}-${pattern.name}`,
|
|
338
|
+
filePath,
|
|
339
|
+
lineNumber: index + 1,
|
|
340
|
+
lineContent: line.trim(),
|
|
341
|
+
severity: 'info',
|
|
342
|
+
category: 'missing_auth',
|
|
343
|
+
title: pattern.name + ' (public endpoint)',
|
|
344
|
+
description: 'This appears to be a public endpoint (health check, webhook, cron, etc.). Verify this is intentionally public and consider rate limiting if needed.',
|
|
345
|
+
suggestedFix: 'If this is a webhook or cron endpoint, ensure it has appropriate authentication (API keys, signatures, etc.). Health checks typically do not need auth.',
|
|
346
|
+
confidence: 'low',
|
|
347
|
+
layer: 2,
|
|
348
|
+
})
|
|
349
|
+
break // Only report once per line
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// PRIORITY 4: Check if auth check exists nearby (inline check)
|
|
353
|
+
if (hasAuthCheckNearby(lines, index)) {
|
|
354
|
+
vulnerabilities.push({
|
|
355
|
+
id: `auth-antipattern-${filePath}-${index + 1}-${pattern.name}`,
|
|
356
|
+
filePath,
|
|
357
|
+
lineNumber: index + 1,
|
|
358
|
+
lineContent: line.trim(),
|
|
359
|
+
severity: 'low',
|
|
360
|
+
category: 'missing_auth',
|
|
361
|
+
title: pattern.name,
|
|
362
|
+
description: pattern.description + ' (auth check detected in nearby lines)',
|
|
363
|
+
suggestedFix: pattern.suggestedFix,
|
|
364
|
+
confidence: 'low',
|
|
365
|
+
layer: 2,
|
|
366
|
+
})
|
|
367
|
+
break // Only report once per line
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Standard handling for all patterns (or fallback for route patterns)
|
|
372
|
+
// Boost confidence for auth-related files
|
|
373
|
+
const confidence = isAuthFile ? 'high' : 'medium'
|
|
374
|
+
|
|
375
|
+
vulnerabilities.push({
|
|
376
|
+
id: `auth-antipattern-${filePath}-${index + 1}-${pattern.name}`,
|
|
377
|
+
filePath,
|
|
378
|
+
lineNumber: index + 1,
|
|
379
|
+
lineContent: line.trim(),
|
|
380
|
+
severity: pattern.severity,
|
|
381
|
+
category: 'missing_auth',
|
|
382
|
+
title: pattern.name,
|
|
383
|
+
description: pattern.description,
|
|
384
|
+
suggestedFix: pattern.suggestedFix,
|
|
385
|
+
confidence,
|
|
386
|
+
layer: 2,
|
|
387
|
+
})
|
|
388
|
+
break // Only report once per line
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
return vulnerabilities
|
|
394
|
+
}
|