@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,443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Helper Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects authentication helper functions that throw on missing auth,
|
|
5
|
+
* return non-null types, and provide security guarantees.
|
|
6
|
+
*
|
|
7
|
+
* When these helpers are called, subsequent code is GUARANTEED to have
|
|
8
|
+
* an authenticated user - no additional `if (!userId)` checks are needed.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ScanFile } from '../types'
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Types
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
export interface AuthHelper {
|
|
18
|
+
/** Name of the helper function */
|
|
19
|
+
name: string
|
|
20
|
+
/** File where it's defined (if detected) */
|
|
21
|
+
definedIn?: string
|
|
22
|
+
/** Whether it throws on missing auth (vs returning null) */
|
|
23
|
+
throwsOnMissing: boolean
|
|
24
|
+
/** Return type if detected (e.g., 'string', 'User', 'Promise<string>') */
|
|
25
|
+
returnType?: string
|
|
26
|
+
/** Whether return type is non-null */
|
|
27
|
+
returnsNonNull: boolean
|
|
28
|
+
/** Pattern that matches calls to this helper */
|
|
29
|
+
callPattern: RegExp
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AuthHelperContext {
|
|
33
|
+
/** All detected auth helpers */
|
|
34
|
+
helpers: AuthHelper[]
|
|
35
|
+
/** Whether the project has any throwing auth helpers */
|
|
36
|
+
hasThrowingHelpers: boolean
|
|
37
|
+
/** Summary for AI validation */
|
|
38
|
+
summary: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Well-Known Auth Helper Patterns
|
|
43
|
+
// ============================================================================
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Common patterns for auth helpers that THROW on missing auth
|
|
47
|
+
* These are functions that guarantee authenticated context after call
|
|
48
|
+
*/
|
|
49
|
+
const THROWING_AUTH_HELPER_PATTERNS = [
|
|
50
|
+
// Generic patterns
|
|
51
|
+
{
|
|
52
|
+
namePattern: /^(get|fetch|require|ensure)(Current)?(User|UserId|Auth|Session|Principal)(Id)?$/i,
|
|
53
|
+
callPattern: /\b(get|fetch|require|ensure)(Current)?(User|UserId|Auth|Session|Principal)(Id)?\s*\(/gi,
|
|
54
|
+
description: 'Auth helper that retrieves authenticated user',
|
|
55
|
+
},
|
|
56
|
+
// Clerk patterns
|
|
57
|
+
{
|
|
58
|
+
namePattern: /^auth$/,
|
|
59
|
+
callPattern: /\bauth\s*\(\s*\)/gi,
|
|
60
|
+
description: 'Clerk auth() helper',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
namePattern: /^currentUser$/,
|
|
64
|
+
callPattern: /\bcurrentUser\s*\(\s*\)/gi,
|
|
65
|
+
description: 'Clerk currentUser() helper',
|
|
66
|
+
},
|
|
67
|
+
// NextAuth patterns
|
|
68
|
+
{
|
|
69
|
+
namePattern: /^getServerSession$/,
|
|
70
|
+
callPattern: /\bgetServerSession\s*\(/gi,
|
|
71
|
+
description: 'NextAuth getServerSession()',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
namePattern: /^getSession$/,
|
|
75
|
+
callPattern: /\bgetSession\s*\(/gi,
|
|
76
|
+
description: 'Session helper',
|
|
77
|
+
},
|
|
78
|
+
// Supabase patterns
|
|
79
|
+
{
|
|
80
|
+
namePattern: /^getUser$/,
|
|
81
|
+
callPattern: /\bsupabase\.auth\.getUser\s*\(/gi,
|
|
82
|
+
description: 'Supabase getUser()',
|
|
83
|
+
},
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Patterns that indicate a function THROWS on missing auth
|
|
88
|
+
*/
|
|
89
|
+
const THROWING_INDICATORS = [
|
|
90
|
+
/throw\s+new\s+(Error|UnauthorizedError|AuthError|HttpException)/i,
|
|
91
|
+
/throw\s+.*401/i,
|
|
92
|
+
/throw\s+.*unauthorized/i,
|
|
93
|
+
/throw\s+.*unauthenticated/i,
|
|
94
|
+
/return\s+.*401/i,
|
|
95
|
+
/NextResponse\.json\s*\([^)]*401/i,
|
|
96
|
+
/res\.status\s*\(\s*401\s*\)/i,
|
|
97
|
+
/redirect\s*\(\s*['"`].*login/i,
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Patterns that indicate NON-NULL return type
|
|
102
|
+
*/
|
|
103
|
+
const NON_NULL_RETURN_PATTERNS = [
|
|
104
|
+
/:\s*Promise<string>/i, // : Promise<string>
|
|
105
|
+
/:\s*string(?!\s*\|)/i, // : string (not string | null)
|
|
106
|
+
/:\s*Promise<User>/i, // : Promise<User>
|
|
107
|
+
/:\s*User(?!\s*\|)/i, // : User (not User | null)
|
|
108
|
+
/:\s*Promise<\w+>(?!\s*\|)/i, // : Promise<SomeType>
|
|
109
|
+
/\w+!$/i, // Non-null assertion in return
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// Detection Functions
|
|
114
|
+
// ============================================================================
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Detect auth helper functions in the codebase
|
|
118
|
+
*/
|
|
119
|
+
export function detectAuthHelpers(files: ScanFile[]): AuthHelperContext {
|
|
120
|
+
const helpers: AuthHelper[] = []
|
|
121
|
+
const detectedNames = new Set<string>()
|
|
122
|
+
|
|
123
|
+
// First pass: find auth helper definitions
|
|
124
|
+
for (const file of files) {
|
|
125
|
+
// Skip non-code files
|
|
126
|
+
if (!/\.(ts|tsx|js|jsx)$/i.test(file.path)) continue
|
|
127
|
+
|
|
128
|
+
// Look for function definitions that look like auth helpers
|
|
129
|
+
const functionMatches = findAuthHelperDefinitions(file.content, file.path)
|
|
130
|
+
for (const helper of functionMatches) {
|
|
131
|
+
if (!detectedNames.has(helper.name)) {
|
|
132
|
+
detectedNames.add(helper.name)
|
|
133
|
+
helpers.push(helper)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Add well-known helpers if not already detected
|
|
139
|
+
for (const pattern of THROWING_AUTH_HELPER_PATTERNS) {
|
|
140
|
+
const nameMatch = pattern.namePattern.source.replace(/[\^\$\\b]/g, '')
|
|
141
|
+
if (!detectedNames.has(nameMatch)) {
|
|
142
|
+
// Check if this pattern is used in any file
|
|
143
|
+
const isUsed = files.some(f => pattern.callPattern.test(f.content))
|
|
144
|
+
if (isUsed) {
|
|
145
|
+
helpers.push({
|
|
146
|
+
name: nameMatch,
|
|
147
|
+
throwsOnMissing: true, // Assume throwing for well-known patterns
|
|
148
|
+
returnsNonNull: true,
|
|
149
|
+
callPattern: pattern.callPattern,
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Generate summary
|
|
156
|
+
const throwingHelpers = helpers.filter(h => h.throwsOnMissing)
|
|
157
|
+
const summary = generateAuthHelperSummary(helpers)
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
helpers,
|
|
161
|
+
hasThrowingHelpers: throwingHelpers.length > 0,
|
|
162
|
+
summary,
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Find auth helper function definitions in a file
|
|
168
|
+
*/
|
|
169
|
+
function findAuthHelperDefinitions(content: string, filePath: string): AuthHelper[] {
|
|
170
|
+
const helpers: AuthHelper[] = []
|
|
171
|
+
const lines = content.split('\n')
|
|
172
|
+
|
|
173
|
+
// Patterns for function definitions
|
|
174
|
+
const funcDefPatterns = [
|
|
175
|
+
// async function getCurrentUserId(): Promise<string> { ... throw
|
|
176
|
+
/(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*(?::\s*([^{]+))?\s*\{/gi,
|
|
177
|
+
// const getCurrentUserId = async (): Promise<string> => { ... throw
|
|
178
|
+
/(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*(?::\s*([^=]+))?\s*=>/gi,
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
for (let i = 0; i < lines.length; i++) {
|
|
182
|
+
const line = lines[i]
|
|
183
|
+
|
|
184
|
+
for (const pattern of funcDefPatterns) {
|
|
185
|
+
pattern.lastIndex = 0
|
|
186
|
+
const match = pattern.exec(line)
|
|
187
|
+
if (!match) continue
|
|
188
|
+
|
|
189
|
+
const funcName = match[1]
|
|
190
|
+
const returnType = match[2]?.trim()
|
|
191
|
+
|
|
192
|
+
// Check if this looks like an auth helper by name
|
|
193
|
+
const isAuthHelperName = THROWING_AUTH_HELPER_PATTERNS.some(p =>
|
|
194
|
+
p.namePattern.test(funcName)
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if (!isAuthHelperName) continue
|
|
198
|
+
|
|
199
|
+
// Look ahead for throwing patterns and return type
|
|
200
|
+
const functionBody = extractFunctionBody(lines, i)
|
|
201
|
+
const throwsOnMissing = THROWING_INDICATORS.some(p => p.test(functionBody))
|
|
202
|
+
const returnsNonNull = returnType
|
|
203
|
+
? NON_NULL_RETURN_PATTERNS.some(p => p.test(`: ${returnType}`))
|
|
204
|
+
: false
|
|
205
|
+
|
|
206
|
+
// Create call pattern for this helper
|
|
207
|
+
const callPattern = new RegExp(`\\b${escapeRegex(funcName)}\\s*\\(`, 'gi')
|
|
208
|
+
|
|
209
|
+
helpers.push({
|
|
210
|
+
name: funcName,
|
|
211
|
+
definedIn: filePath,
|
|
212
|
+
throwsOnMissing,
|
|
213
|
+
returnType,
|
|
214
|
+
returnsNonNull: returnsNonNull || throwsOnMissing, // If it throws, the return is non-null
|
|
215
|
+
callPattern,
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return helpers
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Extract function body for analysis (up to closing brace)
|
|
225
|
+
*/
|
|
226
|
+
function extractFunctionBody(lines: string[], startLine: number, maxLines: number = 50): string {
|
|
227
|
+
let braceCount = 0
|
|
228
|
+
let started = false
|
|
229
|
+
const bodyLines: string[] = []
|
|
230
|
+
|
|
231
|
+
for (let i = startLine; i < Math.min(lines.length, startLine + maxLines); i++) {
|
|
232
|
+
const line = lines[i]
|
|
233
|
+
bodyLines.push(line)
|
|
234
|
+
|
|
235
|
+
for (const char of line) {
|
|
236
|
+
if (char === '{') {
|
|
237
|
+
braceCount++
|
|
238
|
+
started = true
|
|
239
|
+
} else if (char === '}') {
|
|
240
|
+
braceCount--
|
|
241
|
+
if (started && braceCount === 0) {
|
|
242
|
+
return bodyLines.join('\n')
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return bodyLines.join('\n')
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Check if a code line uses a throwing auth helper
|
|
253
|
+
* Returns the helper if found, undefined otherwise
|
|
254
|
+
*/
|
|
255
|
+
export function usesThrowingAuthHelper(
|
|
256
|
+
lineContent: string,
|
|
257
|
+
surroundingContent: string,
|
|
258
|
+
helpers: AuthHelper[]
|
|
259
|
+
): AuthHelper | undefined {
|
|
260
|
+
// Check if any throwing helper is called in the surrounding context
|
|
261
|
+
const throwingHelpers = helpers.filter(h => h.throwsOnMissing)
|
|
262
|
+
|
|
263
|
+
for (const helper of throwingHelpers) {
|
|
264
|
+
helper.callPattern.lastIndex = 0
|
|
265
|
+
if (helper.callPattern.test(surroundingContent)) {
|
|
266
|
+
return helper
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return undefined
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Well-known auth helper call patterns - used as fallback when no helpers are detected
|
|
275
|
+
* These patterns are common across frameworks and should be recognized even without project analysis
|
|
276
|
+
*/
|
|
277
|
+
const WELL_KNOWN_AUTH_CALL_PATTERNS: Array<{ pattern: RegExp; name: string }> = [
|
|
278
|
+
// Generic throwing auth patterns
|
|
279
|
+
{ pattern: /\bgetCurrentUserId\s*\(/i, name: 'getCurrentUserId' },
|
|
280
|
+
{ pattern: /\bgetCurrentUser\s*\(/i, name: 'getCurrentUser' },
|
|
281
|
+
{ pattern: /\brequireAuth\s*\(/i, name: 'requireAuth' },
|
|
282
|
+
{ pattern: /\brequireUser\s*\(/i, name: 'requireUser' },
|
|
283
|
+
{ pattern: /\bensureAuth\s*\(/i, name: 'ensureAuth' },
|
|
284
|
+
{ pattern: /\bensureAuthenticated\s*\(/i, name: 'ensureAuthenticated' },
|
|
285
|
+
{ pattern: /\bverifyAuth\s*\(/i, name: 'verifyAuth' },
|
|
286
|
+
{ pattern: /\bcheckAuth\s*\(/i, name: 'checkAuth' },
|
|
287
|
+
{ pattern: /\bvalidateAuth\s*\(/i, name: 'validateAuth' },
|
|
288
|
+
{ pattern: /\bassertAuth\s*\(/i, name: 'assertAuth' },
|
|
289
|
+
{ pattern: /\bgetAuth\s*\(/i, name: 'getAuth' },
|
|
290
|
+
{ pattern: /\bfetchCurrentUser\s*\(/i, name: 'fetchCurrentUser' },
|
|
291
|
+
// Clerk
|
|
292
|
+
{ pattern: /\bauth\s*\(\s*\)\.protect\s*\(/i, name: 'auth().protect' },
|
|
293
|
+
{ pattern: /\bcurrentUser\s*\(\s*\)/i, name: 'currentUser' },
|
|
294
|
+
// NextAuth
|
|
295
|
+
{ pattern: /\bgetServerSession\s*\(/i, name: 'getServerSession' },
|
|
296
|
+
// Supabase
|
|
297
|
+
{ pattern: /\bsupabase\.auth\.getUser\s*\(/i, name: 'supabase.auth.getUser' },
|
|
298
|
+
// Destructuring pattern
|
|
299
|
+
{ pattern: /const\s+\{\s*user\s*\}\s*=\s*await\s+auth/i, name: 'destructured auth' },
|
|
300
|
+
]
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Check if a file has auth helper calls before a given line
|
|
304
|
+
* This indicates the code after the call is in authenticated context
|
|
305
|
+
*/
|
|
306
|
+
export function hasAuthHelperCallBefore(
|
|
307
|
+
content: string,
|
|
308
|
+
lineNumber: number,
|
|
309
|
+
helpers: AuthHelper[]
|
|
310
|
+
): { hasCall: boolean; helper?: AuthHelper; callLine?: number } {
|
|
311
|
+
const lines = content.split('\n')
|
|
312
|
+
const throwingHelpers = helpers.filter(h => h.throwsOnMissing)
|
|
313
|
+
|
|
314
|
+
// Increase search window from 30 to 100 lines for better coverage
|
|
315
|
+
const searchStart = Math.max(0, lineNumber - 100)
|
|
316
|
+
|
|
317
|
+
// Look backwards from the current line
|
|
318
|
+
for (let i = lineNumber - 1; i >= searchStart; i--) {
|
|
319
|
+
const line = lines[i]
|
|
320
|
+
|
|
321
|
+
// Check detected helpers first
|
|
322
|
+
for (const helper of throwingHelpers) {
|
|
323
|
+
helper.callPattern.lastIndex = 0
|
|
324
|
+
if (helper.callPattern.test(line)) {
|
|
325
|
+
return { hasCall: true, helper, callLine: i + 1 }
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Stop at function boundaries (for module-level helpers, we still check inside function)
|
|
330
|
+
if (/\b(function|async function|=>|export\s+default)\b/.test(line) && /\{/.test(line)) {
|
|
331
|
+
// If this is the route handler definition line, continue checking inside it
|
|
332
|
+
if (i !== lineNumber - 1) {
|
|
333
|
+
break
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// FORWARD SEARCH: Always search forward into the function body
|
|
339
|
+
// This catches auth helpers called INSIDE the route handler, not just before it
|
|
340
|
+
// Example: export async function GET() { const userId = await getCurrentUserId(); ... }
|
|
341
|
+
const endLine = Math.min(lines.length, lineNumber + 30)
|
|
342
|
+
for (let i = lineNumber; i < endLine; i++) {
|
|
343
|
+
const line = lines[i]
|
|
344
|
+
|
|
345
|
+
// Check detected helpers
|
|
346
|
+
for (const helper of throwingHelpers) {
|
|
347
|
+
helper.callPattern.lastIndex = 0
|
|
348
|
+
if (helper.callPattern.test(line)) {
|
|
349
|
+
return { hasCall: true, helper, callLine: i + 1 }
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Also check well-known patterns (fallback for single-file scans)
|
|
354
|
+
for (const known of WELL_KNOWN_AUTH_CALL_PATTERNS) {
|
|
355
|
+
if (known.pattern.test(line)) {
|
|
356
|
+
return {
|
|
357
|
+
hasCall: true,
|
|
358
|
+
helper: {
|
|
359
|
+
name: known.name,
|
|
360
|
+
throwsOnMissing: true,
|
|
361
|
+
returnsNonNull: true,
|
|
362
|
+
callPattern: known.pattern,
|
|
363
|
+
},
|
|
364
|
+
callLine: i + 1
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Stop at another function boundary (but not the current line)
|
|
370
|
+
if (i > lineNumber && /\bexport\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)\s*\(/i.test(line)) {
|
|
371
|
+
break
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return { hasCall: false }
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Generate summary for AI validation
|
|
380
|
+
*/
|
|
381
|
+
function generateAuthHelperSummary(helpers: AuthHelper[]): string {
|
|
382
|
+
if (helpers.length === 0) {
|
|
383
|
+
return 'No auth helper functions detected.'
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const throwing = helpers.filter(h => h.throwsOnMissing)
|
|
387
|
+
const lines: string[] = []
|
|
388
|
+
|
|
389
|
+
lines.push('### Auth Helper Functions')
|
|
390
|
+
lines.push('')
|
|
391
|
+
|
|
392
|
+
if (throwing.length > 0) {
|
|
393
|
+
lines.push('**Throwing auth helpers** (guarantee authenticated context when called):')
|
|
394
|
+
for (const h of throwing) {
|
|
395
|
+
const location = h.definedIn ? ` (defined in ${h.definedIn})` : ''
|
|
396
|
+
lines.push(`- \`${h.name}()\`${location}`)
|
|
397
|
+
}
|
|
398
|
+
lines.push('')
|
|
399
|
+
lines.push('When these helpers are called at the start of a function, subsequent code is GUARANTEED to have an authenticated user. Do NOT flag "missing auth" or suggest `if (!userId)` checks after these calls.')
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return lines.join('\n')
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Escape special regex characters
|
|
407
|
+
*/
|
|
408
|
+
function escapeRegex(str: string): string {
|
|
409
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Check if code suggests user ID is already validated
|
|
414
|
+
* Detects patterns like: const userId = await getCurrentUserId()
|
|
415
|
+
*/
|
|
416
|
+
export function isUserIdAlreadyValidated(
|
|
417
|
+
content: string,
|
|
418
|
+
lineNumber: number,
|
|
419
|
+
helpers: AuthHelper[]
|
|
420
|
+
): boolean {
|
|
421
|
+
const lines = content.split('\n')
|
|
422
|
+
const contextStart = Math.max(0, lineNumber - 20)
|
|
423
|
+
const context = lines.slice(contextStart, lineNumber).join('\n')
|
|
424
|
+
|
|
425
|
+
// Check for throwing helper calls
|
|
426
|
+
const throwingHelpers = helpers.filter(h => h.throwsOnMissing)
|
|
427
|
+
for (const helper of throwingHelpers) {
|
|
428
|
+
helper.callPattern.lastIndex = 0
|
|
429
|
+
if (helper.callPattern.test(context)) {
|
|
430
|
+
return true
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Check for common validation patterns
|
|
435
|
+
const validationPatterns = [
|
|
436
|
+
/const\s+(?:userId|user_id|currentUserId)\s*=\s*await/i,
|
|
437
|
+
/if\s*\(\s*!(?:userId|user_id|user|session)\s*\)/i, // Already has the check
|
|
438
|
+
/(?:userId|user_id)\s*\|\|\s*throw/i,
|
|
439
|
+
/auth\(\)\.protect\(\)/i, // Clerk protect
|
|
440
|
+
]
|
|
441
|
+
|
|
442
|
+
return validationPatterns.some(p => p.test(context))
|
|
443
|
+
}
|