@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.
Files changed (281) hide show
  1. package/dist/formatters/cli-terminal.d.ts +27 -0
  2. package/dist/formatters/cli-terminal.d.ts.map +1 -0
  3. package/dist/formatters/cli-terminal.js +412 -0
  4. package/dist/formatters/cli-terminal.js.map +1 -0
  5. package/dist/formatters/github-comment.d.ts +41 -0
  6. package/dist/formatters/github-comment.d.ts.map +1 -0
  7. package/dist/formatters/github-comment.js +306 -0
  8. package/dist/formatters/github-comment.js.map +1 -0
  9. package/dist/formatters/grouping.d.ts +52 -0
  10. package/dist/formatters/grouping.d.ts.map +1 -0
  11. package/dist/formatters/grouping.js +152 -0
  12. package/dist/formatters/grouping.js.map +1 -0
  13. package/dist/formatters/index.d.ts +9 -0
  14. package/dist/formatters/index.d.ts.map +1 -0
  15. package/dist/formatters/index.js +35 -0
  16. package/dist/formatters/index.js.map +1 -0
  17. package/dist/formatters/vscode-diagnostic.d.ts +103 -0
  18. package/dist/formatters/vscode-diagnostic.d.ts.map +1 -0
  19. package/dist/formatters/vscode-diagnostic.js +151 -0
  20. package/dist/formatters/vscode-diagnostic.js.map +1 -0
  21. package/dist/index.d.ts +52 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +648 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/layer1/comments.d.ts +8 -0
  26. package/dist/layer1/comments.d.ts.map +1 -0
  27. package/dist/layer1/comments.js +203 -0
  28. package/dist/layer1/comments.js.map +1 -0
  29. package/dist/layer1/config-audit.d.ts +8 -0
  30. package/dist/layer1/config-audit.d.ts.map +1 -0
  31. package/dist/layer1/config-audit.js +252 -0
  32. package/dist/layer1/config-audit.js.map +1 -0
  33. package/dist/layer1/entropy.d.ts +8 -0
  34. package/dist/layer1/entropy.d.ts.map +1 -0
  35. package/dist/layer1/entropy.js +500 -0
  36. package/dist/layer1/entropy.js.map +1 -0
  37. package/dist/layer1/file-flags.d.ts +7 -0
  38. package/dist/layer1/file-flags.d.ts.map +1 -0
  39. package/dist/layer1/file-flags.js +112 -0
  40. package/dist/layer1/file-flags.js.map +1 -0
  41. package/dist/layer1/index.d.ts +36 -0
  42. package/dist/layer1/index.d.ts.map +1 -0
  43. package/dist/layer1/index.js +132 -0
  44. package/dist/layer1/index.js.map +1 -0
  45. package/dist/layer1/patterns.d.ts +8 -0
  46. package/dist/layer1/patterns.d.ts.map +1 -0
  47. package/dist/layer1/patterns.js +482 -0
  48. package/dist/layer1/patterns.js.map +1 -0
  49. package/dist/layer1/urls.d.ts +8 -0
  50. package/dist/layer1/urls.d.ts.map +1 -0
  51. package/dist/layer1/urls.js +296 -0
  52. package/dist/layer1/urls.js.map +1 -0
  53. package/dist/layer1/weak-crypto.d.ts +7 -0
  54. package/dist/layer1/weak-crypto.d.ts.map +1 -0
  55. package/dist/layer1/weak-crypto.js +291 -0
  56. package/dist/layer1/weak-crypto.js.map +1 -0
  57. package/dist/layer2/ai-agent-tools.d.ts +19 -0
  58. package/dist/layer2/ai-agent-tools.d.ts.map +1 -0
  59. package/dist/layer2/ai-agent-tools.js +528 -0
  60. package/dist/layer2/ai-agent-tools.js.map +1 -0
  61. package/dist/layer2/ai-endpoint-protection.d.ts +36 -0
  62. package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -0
  63. package/dist/layer2/ai-endpoint-protection.js +332 -0
  64. package/dist/layer2/ai-endpoint-protection.js.map +1 -0
  65. package/dist/layer2/ai-execution-sinks.d.ts +18 -0
  66. package/dist/layer2/ai-execution-sinks.d.ts.map +1 -0
  67. package/dist/layer2/ai-execution-sinks.js +496 -0
  68. package/dist/layer2/ai-execution-sinks.js.map +1 -0
  69. package/dist/layer2/ai-fingerprinting.d.ts +7 -0
  70. package/dist/layer2/ai-fingerprinting.d.ts.map +1 -0
  71. package/dist/layer2/ai-fingerprinting.js +654 -0
  72. package/dist/layer2/ai-fingerprinting.js.map +1 -0
  73. package/dist/layer2/ai-prompt-hygiene.d.ts +19 -0
  74. package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -0
  75. package/dist/layer2/ai-prompt-hygiene.js +356 -0
  76. package/dist/layer2/ai-prompt-hygiene.js.map +1 -0
  77. package/dist/layer2/ai-rag-safety.d.ts +21 -0
  78. package/dist/layer2/ai-rag-safety.d.ts.map +1 -0
  79. package/dist/layer2/ai-rag-safety.js +459 -0
  80. package/dist/layer2/ai-rag-safety.js.map +1 -0
  81. package/dist/layer2/ai-schema-validation.d.ts +25 -0
  82. package/dist/layer2/ai-schema-validation.d.ts.map +1 -0
  83. package/dist/layer2/ai-schema-validation.js +375 -0
  84. package/dist/layer2/ai-schema-validation.js.map +1 -0
  85. package/dist/layer2/auth-antipatterns.d.ts +20 -0
  86. package/dist/layer2/auth-antipatterns.d.ts.map +1 -0
  87. package/dist/layer2/auth-antipatterns.js +333 -0
  88. package/dist/layer2/auth-antipatterns.js.map +1 -0
  89. package/dist/layer2/byok-patterns.d.ts +12 -0
  90. package/dist/layer2/byok-patterns.d.ts.map +1 -0
  91. package/dist/layer2/byok-patterns.js +299 -0
  92. package/dist/layer2/byok-patterns.js.map +1 -0
  93. package/dist/layer2/dangerous-functions.d.ts +7 -0
  94. package/dist/layer2/dangerous-functions.d.ts.map +1 -0
  95. package/dist/layer2/dangerous-functions.js +1375 -0
  96. package/dist/layer2/dangerous-functions.js.map +1 -0
  97. package/dist/layer2/data-exposure.d.ts +16 -0
  98. package/dist/layer2/data-exposure.d.ts.map +1 -0
  99. package/dist/layer2/data-exposure.js +279 -0
  100. package/dist/layer2/data-exposure.js.map +1 -0
  101. package/dist/layer2/framework-checks.d.ts +7 -0
  102. package/dist/layer2/framework-checks.d.ts.map +1 -0
  103. package/dist/layer2/framework-checks.js +388 -0
  104. package/dist/layer2/framework-checks.js.map +1 -0
  105. package/dist/layer2/index.d.ts +58 -0
  106. package/dist/layer2/index.d.ts.map +1 -0
  107. package/dist/layer2/index.js +380 -0
  108. package/dist/layer2/index.js.map +1 -0
  109. package/dist/layer2/logic-gates.d.ts +7 -0
  110. package/dist/layer2/logic-gates.d.ts.map +1 -0
  111. package/dist/layer2/logic-gates.js +182 -0
  112. package/dist/layer2/logic-gates.js.map +1 -0
  113. package/dist/layer2/risky-imports.d.ts +7 -0
  114. package/dist/layer2/risky-imports.d.ts.map +1 -0
  115. package/dist/layer2/risky-imports.js +161 -0
  116. package/dist/layer2/risky-imports.js.map +1 -0
  117. package/dist/layer2/variables.d.ts +8 -0
  118. package/dist/layer2/variables.d.ts.map +1 -0
  119. package/dist/layer2/variables.js +152 -0
  120. package/dist/layer2/variables.js.map +1 -0
  121. package/dist/layer3/anthropic.d.ts +83 -0
  122. package/dist/layer3/anthropic.d.ts.map +1 -0
  123. package/dist/layer3/anthropic.js +1745 -0
  124. package/dist/layer3/anthropic.js.map +1 -0
  125. package/dist/layer3/index.d.ts +24 -0
  126. package/dist/layer3/index.d.ts.map +1 -0
  127. package/dist/layer3/index.js +119 -0
  128. package/dist/layer3/index.js.map +1 -0
  129. package/dist/layer3/openai.d.ts +25 -0
  130. package/dist/layer3/openai.d.ts.map +1 -0
  131. package/dist/layer3/openai.js +238 -0
  132. package/dist/layer3/openai.js.map +1 -0
  133. package/dist/layer3/package-check.d.ts +63 -0
  134. package/dist/layer3/package-check.d.ts.map +1 -0
  135. package/dist/layer3/package-check.js +508 -0
  136. package/dist/layer3/package-check.js.map +1 -0
  137. package/dist/modes/incremental.d.ts +66 -0
  138. package/dist/modes/incremental.d.ts.map +1 -0
  139. package/dist/modes/incremental.js +200 -0
  140. package/dist/modes/incremental.js.map +1 -0
  141. package/dist/tiers.d.ts +125 -0
  142. package/dist/tiers.d.ts.map +1 -0
  143. package/dist/tiers.js +234 -0
  144. package/dist/tiers.js.map +1 -0
  145. package/dist/types.d.ts +175 -0
  146. package/dist/types.d.ts.map +1 -0
  147. package/dist/types.js +50 -0
  148. package/dist/types.js.map +1 -0
  149. package/dist/utils/auth-helper-detector.d.ts +56 -0
  150. package/dist/utils/auth-helper-detector.d.ts.map +1 -0
  151. package/dist/utils/auth-helper-detector.js +360 -0
  152. package/dist/utils/auth-helper-detector.js.map +1 -0
  153. package/dist/utils/context-helpers.d.ts +96 -0
  154. package/dist/utils/context-helpers.d.ts.map +1 -0
  155. package/dist/utils/context-helpers.js +493 -0
  156. package/dist/utils/context-helpers.js.map +1 -0
  157. package/dist/utils/diff-detector.d.ts +53 -0
  158. package/dist/utils/diff-detector.d.ts.map +1 -0
  159. package/dist/utils/diff-detector.js +104 -0
  160. package/dist/utils/diff-detector.js.map +1 -0
  161. package/dist/utils/diff-parser.d.ts +80 -0
  162. package/dist/utils/diff-parser.d.ts.map +1 -0
  163. package/dist/utils/diff-parser.js +202 -0
  164. package/dist/utils/diff-parser.js.map +1 -0
  165. package/dist/utils/imported-auth-detector.d.ts +37 -0
  166. package/dist/utils/imported-auth-detector.d.ts.map +1 -0
  167. package/dist/utils/imported-auth-detector.js +251 -0
  168. package/dist/utils/imported-auth-detector.js.map +1 -0
  169. package/dist/utils/middleware-detector.d.ts +55 -0
  170. package/dist/utils/middleware-detector.d.ts.map +1 -0
  171. package/dist/utils/middleware-detector.js +260 -0
  172. package/dist/utils/middleware-detector.js.map +1 -0
  173. package/dist/utils/oauth-flow-detector.d.ts +41 -0
  174. package/dist/utils/oauth-flow-detector.d.ts.map +1 -0
  175. package/dist/utils/oauth-flow-detector.js +202 -0
  176. package/dist/utils/oauth-flow-detector.js.map +1 -0
  177. package/dist/utils/path-exclusions.d.ts +55 -0
  178. package/dist/utils/path-exclusions.d.ts.map +1 -0
  179. package/dist/utils/path-exclusions.js +222 -0
  180. package/dist/utils/path-exclusions.js.map +1 -0
  181. package/dist/utils/project-context-builder.d.ts +119 -0
  182. package/dist/utils/project-context-builder.d.ts.map +1 -0
  183. package/dist/utils/project-context-builder.js +534 -0
  184. package/dist/utils/project-context-builder.js.map +1 -0
  185. package/dist/utils/registry-clients.d.ts +93 -0
  186. package/dist/utils/registry-clients.d.ts.map +1 -0
  187. package/dist/utils/registry-clients.js +273 -0
  188. package/dist/utils/registry-clients.js.map +1 -0
  189. package/dist/utils/trpc-analyzer.d.ts +78 -0
  190. package/dist/utils/trpc-analyzer.d.ts.map +1 -0
  191. package/dist/utils/trpc-analyzer.js +297 -0
  192. package/dist/utils/trpc-analyzer.js.map +1 -0
  193. package/package.json +45 -0
  194. package/src/__tests__/benchmark/fixtures/false-positives.ts +227 -0
  195. package/src/__tests__/benchmark/fixtures/index.ts +68 -0
  196. package/src/__tests__/benchmark/fixtures/layer1/config-audit.ts +364 -0
  197. package/src/__tests__/benchmark/fixtures/layer1/hardcoded-secrets.ts +173 -0
  198. package/src/__tests__/benchmark/fixtures/layer1/high-entropy.ts +234 -0
  199. package/src/__tests__/benchmark/fixtures/layer1/index.ts +31 -0
  200. package/src/__tests__/benchmark/fixtures/layer1/sensitive-urls.ts +90 -0
  201. package/src/__tests__/benchmark/fixtures/layer1/weak-crypto.ts +197 -0
  202. package/src/__tests__/benchmark/fixtures/layer2/ai-agent-tools.ts +170 -0
  203. package/src/__tests__/benchmark/fixtures/layer2/ai-endpoint-protection.ts +418 -0
  204. package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +189 -0
  205. package/src/__tests__/benchmark/fixtures/layer2/ai-fingerprinting.ts +316 -0
  206. package/src/__tests__/benchmark/fixtures/layer2/ai-prompt-hygiene.ts +178 -0
  207. package/src/__tests__/benchmark/fixtures/layer2/ai-rag-safety.ts +184 -0
  208. package/src/__tests__/benchmark/fixtures/layer2/ai-schema-validation.ts +434 -0
  209. package/src/__tests__/benchmark/fixtures/layer2/auth-antipatterns.ts +159 -0
  210. package/src/__tests__/benchmark/fixtures/layer2/byok-patterns.ts +112 -0
  211. package/src/__tests__/benchmark/fixtures/layer2/dangerous-functions.ts +246 -0
  212. package/src/__tests__/benchmark/fixtures/layer2/data-exposure.ts +168 -0
  213. package/src/__tests__/benchmark/fixtures/layer2/framework-checks.ts +346 -0
  214. package/src/__tests__/benchmark/fixtures/layer2/index.ts +67 -0
  215. package/src/__tests__/benchmark/fixtures/layer2/injection-vulnerabilities.ts +239 -0
  216. package/src/__tests__/benchmark/fixtures/layer2/logic-gates.ts +246 -0
  217. package/src/__tests__/benchmark/fixtures/layer2/risky-imports.ts +231 -0
  218. package/src/__tests__/benchmark/fixtures/layer2/variables.ts +167 -0
  219. package/src/__tests__/benchmark/index.ts +29 -0
  220. package/src/__tests__/benchmark/run-benchmark.ts +144 -0
  221. package/src/__tests__/benchmark/run-depth-validation.ts +206 -0
  222. package/src/__tests__/benchmark/run-real-world-test.ts +243 -0
  223. package/src/__tests__/benchmark/security-benchmark-script.ts +1737 -0
  224. package/src/__tests__/benchmark/tier-integration-script.ts +177 -0
  225. package/src/__tests__/benchmark/types.ts +144 -0
  226. package/src/__tests__/benchmark/utils/test-runner.ts +475 -0
  227. package/src/__tests__/regression/known-false-positives.test.ts +467 -0
  228. package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +178 -0
  229. package/src/__tests__/snapshots/scan-depth.test.ts +258 -0
  230. package/src/__tests__/validation/analyze-results.ts +542 -0
  231. package/src/__tests__/validation/extract-for-triage.ts +146 -0
  232. package/src/__tests__/validation/fp-deep-analysis.ts +327 -0
  233. package/src/__tests__/validation/run-validation.ts +364 -0
  234. package/src/__tests__/validation/triage-template.md +132 -0
  235. package/src/formatters/cli-terminal.ts +446 -0
  236. package/src/formatters/github-comment.ts +382 -0
  237. package/src/formatters/grouping.ts +190 -0
  238. package/src/formatters/index.ts +47 -0
  239. package/src/formatters/vscode-diagnostic.ts +243 -0
  240. package/src/index.ts +823 -0
  241. package/src/layer1/comments.ts +218 -0
  242. package/src/layer1/config-audit.ts +289 -0
  243. package/src/layer1/entropy.ts +583 -0
  244. package/src/layer1/file-flags.ts +127 -0
  245. package/src/layer1/index.ts +181 -0
  246. package/src/layer1/patterns.ts +516 -0
  247. package/src/layer1/urls.ts +334 -0
  248. package/src/layer1/weak-crypto.ts +328 -0
  249. package/src/layer2/ai-agent-tools.ts +601 -0
  250. package/src/layer2/ai-endpoint-protection.ts +387 -0
  251. package/src/layer2/ai-execution-sinks.ts +580 -0
  252. package/src/layer2/ai-fingerprinting.ts +758 -0
  253. package/src/layer2/ai-prompt-hygiene.ts +411 -0
  254. package/src/layer2/ai-rag-safety.ts +511 -0
  255. package/src/layer2/ai-schema-validation.ts +421 -0
  256. package/src/layer2/auth-antipatterns.ts +394 -0
  257. package/src/layer2/byok-patterns.ts +336 -0
  258. package/src/layer2/dangerous-functions.ts +1563 -0
  259. package/src/layer2/data-exposure.ts +315 -0
  260. package/src/layer2/framework-checks.ts +433 -0
  261. package/src/layer2/index.ts +473 -0
  262. package/src/layer2/logic-gates.ts +206 -0
  263. package/src/layer2/risky-imports.ts +186 -0
  264. package/src/layer2/variables.ts +166 -0
  265. package/src/layer3/anthropic.ts +2030 -0
  266. package/src/layer3/index.ts +130 -0
  267. package/src/layer3/package-check.ts +604 -0
  268. package/src/modes/incremental.ts +293 -0
  269. package/src/tiers.ts +318 -0
  270. package/src/types.ts +284 -0
  271. package/src/utils/auth-helper-detector.ts +443 -0
  272. package/src/utils/context-helpers.ts +535 -0
  273. package/src/utils/diff-detector.ts +135 -0
  274. package/src/utils/diff-parser.ts +272 -0
  275. package/src/utils/imported-auth-detector.ts +320 -0
  276. package/src/utils/middleware-detector.ts +333 -0
  277. package/src/utils/oauth-flow-detector.ts +246 -0
  278. package/src/utils/path-exclusions.ts +266 -0
  279. package/src/utils/project-context-builder.ts +707 -0
  280. package/src/utils/registry-clients.ts +351 -0
  281. package/src/utils/trpc-analyzer.ts +382 -0
@@ -0,0 +1,707 @@
1
+ /**
2
+ * Project Context Builder
3
+ *
4
+ * Builds a generic project context summary for AI validation by analyzing
5
+ * common patterns across the codebase. This context helps the AI validator
6
+ * understand the project's security architecture without hardcoding any
7
+ * specific framework or vendor.
8
+ *
9
+ * Key areas analyzed:
10
+ * - Authentication & access control patterns
11
+ * - Data access patterns (ORMs, query builders, raw queries)
12
+ * - Secrets & configuration handling
13
+ * - Framework detection
14
+ */
15
+
16
+ import type { ScanFile } from '../types'
17
+ import { detectGlobalAuthMiddleware, detectUserScopingPatterns, type MiddlewareAuthConfig } from './middleware-detector'
18
+ import { detectAuthHelpers, type AuthHelperContext, type AuthHelper } from './auth-helper-detector'
19
+ import { detectOAuthFlow, isOAuthStateImplemented, getOAuthFlowSummary, type OAuthFlowContext } from './oauth-flow-detector'
20
+ import { analyzeTRPCRouters, getTRPCSummary, type TRPCRouterContext } from './trpc-analyzer'
21
+
22
+ // ============================================================================
23
+ // Types
24
+ // ============================================================================
25
+
26
+ export interface ProjectContext {
27
+ /** Summary string for AI prompt injection */
28
+ summary: string
29
+
30
+ /** Authentication & access control patterns */
31
+ auth: AuthContext
32
+
33
+ /** Data access patterns */
34
+ dataAccess: DataAccessContext
35
+
36
+ /** Secrets & configuration patterns */
37
+ secrets: SecretsContext
38
+
39
+ /** Detected frameworks and libraries */
40
+ frameworks: FrameworkContext
41
+
42
+ /** File structure insights */
43
+ structure: StructureContext
44
+
45
+ /** OAuth flow context */
46
+ oauth: OAuthFlowContext
47
+
48
+ /** tRPC router context */
49
+ trpc: TRPCRouterContext
50
+ }
51
+
52
+ export interface AuthContext {
53
+ /** Whether global auth middleware is detected */
54
+ hasGlobalMiddleware: boolean
55
+ /** Type of auth provider (clerk, nextauth, auth0, custom, unknown) */
56
+ authProvider?: string
57
+ /** Middleware file path */
58
+ middlewareFile?: string
59
+ /** Protected path patterns */
60
+ protectedPaths: string[]
61
+ /** Public/excluded path patterns */
62
+ publicPaths: string[]
63
+ /** Auth helper functions detected (names only) */
64
+ authHelpers: string[]
65
+ /** Full auth helper context with throwing/return info */
66
+ authHelperContext?: AuthHelperContext
67
+ /** Throwing auth helpers that guarantee authenticated context */
68
+ throwingAuthHelpers: AuthHelper[]
69
+ /** Whether user scoping is used in queries */
70
+ hasUserScoping: boolean
71
+ /** User scoping patterns found */
72
+ userScopingPatterns: string[]
73
+ }
74
+
75
+ export interface DataAccessContext {
76
+ /** ORM/query builder detected */
77
+ orm?: 'prisma' | 'drizzle' | 'supabase' | 'mongoose' | 'sequelize' | 'typeorm' | 'knex' | 'raw_sql' | 'unknown'
78
+ /** Whether parameterized queries are the default */
79
+ usesParameterizedQueries: boolean
80
+ /** Database type if detectable */
81
+ databaseType?: 'postgres' | 'mysql' | 'mongodb' | 'sqlite' | 'unknown'
82
+ /** Whether RLS (Row Level Security) is mentioned */
83
+ hasRLS: boolean
84
+ /** Data validation library detected */
85
+ validationLibrary?: string
86
+ }
87
+
88
+ export interface SecretsContext {
89
+ /** Uses environment variables for secrets */
90
+ usesEnvVars: boolean
91
+ /** Uses a secret manager */
92
+ usesSecretManager: boolean
93
+ /** Secret manager type if detected */
94
+ secretManagerType?: string
95
+ /** Has .env files */
96
+ hasEnvFiles: boolean
97
+ /** Uses NEXT_PUBLIC_ pattern (client-exposed) */
98
+ hasClientExposedEnvVars: boolean
99
+ }
100
+
101
+ export interface FrameworkContext {
102
+ /** Primary framework */
103
+ primary?: 'nextjs' | 'express' | 'fastify' | 'nestjs' | 'django' | 'flask' | 'rails' | 'unknown'
104
+ /** Frontend framework */
105
+ frontend?: 'react' | 'vue' | 'svelte' | 'angular' | 'unknown'
106
+ /** Is this a monorepo? */
107
+ isMonorepo: boolean
108
+ /** Uses TypeScript */
109
+ usesTypeScript: boolean
110
+ /** Uses server components (Next.js 13+) */
111
+ usesServerComponents: boolean
112
+ /** Uses server actions */
113
+ usesServerActions: boolean
114
+ }
115
+
116
+ export interface StructureContext {
117
+ /** Has API routes */
118
+ hasApiRoutes: boolean
119
+ /** Has middleware */
120
+ hasMiddleware: boolean
121
+ /** Has server actions */
122
+ hasServerActions: boolean
123
+ /** Total files analyzed */
124
+ totalFiles: number
125
+ /** File types breakdown */
126
+ fileTypes: Record<string, number>
127
+ }
128
+
129
+ // ============================================================================
130
+ // Pattern Definitions
131
+ // ============================================================================
132
+
133
+ const AUTH_HELPER_PATTERNS = [
134
+ { pattern: /getCurrentUser|getUser|fetchUser/i, name: 'getCurrentUser' },
135
+ { pattern: /getCurrentUserId|getUserId/i, name: 'getCurrentUserId' },
136
+ { pattern: /getSession|getServerSession/i, name: 'getSession' },
137
+ { pattern: /requireAuth|ensureAuth|checkAuth/i, name: 'requireAuth' },
138
+ { pattern: /verifyToken|validateToken/i, name: 'verifyToken' },
139
+ { pattern: /isAuthenticated|isLoggedIn/i, name: 'isAuthenticated' },
140
+ { pattern: /withAuth|authGuard/i, name: 'withAuth' },
141
+ { pattern: /useAuth|useSession/i, name: 'useAuth (hook)' },
142
+ { pattern: /auth\(\)|getAuth\(\)/i, name: 'auth()' },
143
+ ]
144
+
145
+ const ORM_PATTERNS = [
146
+ { pattern: /from\s+['"]@prisma\/client['"]|PrismaClient/i, orm: 'prisma' as const },
147
+ { pattern: /from\s+['"]drizzle-orm['"]|drizzle\(/i, orm: 'drizzle' as const },
148
+ { pattern: /from\s+['"]@supabase\/supabase-js['"]|createClient.*supabase/i, orm: 'supabase' as const },
149
+ { pattern: /from\s+['"]mongoose['"]|mongoose\.connect/i, orm: 'mongoose' as const },
150
+ { pattern: /from\s+['"]sequelize['"]|Sequelize/i, orm: 'sequelize' as const },
151
+ { pattern: /from\s+['"]typeorm['"]|DataSource/i, orm: 'typeorm' as const },
152
+ { pattern: /from\s+['"]knex['"]|knex\(/i, orm: 'knex' as const },
153
+ ]
154
+
155
+ const VALIDATION_LIBRARY_PATTERNS = [
156
+ { pattern: /from\s+['"]zod['"]|z\.object/i, lib: 'zod' },
157
+ { pattern: /from\s+['"]yup['"]|yup\.object/i, lib: 'yup' },
158
+ { pattern: /from\s+['"]joi['"]|Joi\.object/i, lib: 'joi' },
159
+ { pattern: /from\s+['"]valibot['"]|v\.object/i, lib: 'valibot' },
160
+ { pattern: /from\s+['"]class-validator['"]|@IsString|@IsEmail/i, lib: 'class-validator' },
161
+ ]
162
+
163
+ const SECRET_MANAGER_PATTERNS = [
164
+ { pattern: /aws-sdk.*secretsmanager|SecretsManager/i, type: 'AWS Secrets Manager' },
165
+ { pattern: /google-cloud.*secret-manager|SecretManagerServiceClient/i, type: 'Google Secret Manager' },
166
+ { pattern: /@azure\/keyvault|KeyVaultSecret/i, type: 'Azure Key Vault' },
167
+ { pattern: /hashicorp.*vault|vault\.read/i, type: 'HashiCorp Vault' },
168
+ { pattern: /doppler|infisical/i, type: 'Doppler/Infisical' },
169
+ ]
170
+
171
+ // ============================================================================
172
+ // Main Builder Function
173
+ // ============================================================================
174
+
175
+ /**
176
+ * Build a comprehensive project context from scanned files
177
+ * This context is used to inform AI validation decisions
178
+ */
179
+ export function buildProjectContext(files: ScanFile[]): ProjectContext {
180
+ // Detect middleware configuration
181
+ const middlewareConfig = detectGlobalAuthMiddleware(files)
182
+
183
+ // Build individual context sections
184
+ const auth = buildAuthContext(files, middlewareConfig)
185
+ const dataAccess = buildDataAccessContext(files)
186
+ const secrets = buildSecretsContext(files)
187
+ const frameworks = buildFrameworkContext(files)
188
+ const structure = buildStructureContext(files)
189
+ const oauth = detectOAuthFlow(files)
190
+ const trpc = analyzeTRPCRouters(files)
191
+
192
+ // Generate summary string for AI prompt
193
+ const summary = generateContextSummary({
194
+ auth,
195
+ dataAccess,
196
+ secrets,
197
+ frameworks,
198
+ structure,
199
+ oauth,
200
+ trpc,
201
+ })
202
+
203
+ return {
204
+ summary,
205
+ auth,
206
+ dataAccess,
207
+ secrets,
208
+ frameworks,
209
+ structure,
210
+ oauth,
211
+ trpc,
212
+ }
213
+ }
214
+
215
+ // ============================================================================
216
+ // Context Builders
217
+ // ============================================================================
218
+
219
+ function buildAuthContext(files: ScanFile[], middlewareConfig: MiddlewareAuthConfig): AuthContext {
220
+ const authHelperNames: string[] = []
221
+ let hasUserScoping = false
222
+ const userScopingPatterns: string[] = []
223
+
224
+ // Detect auth helpers using the dedicated detector (includes throwing detection)
225
+ const authHelperContext = detectAuthHelpers(files)
226
+ const throwingAuthHelpers = authHelperContext.helpers.filter(h => h.throwsOnMissing)
227
+
228
+ // Scan all files for additional auth patterns and user scoping
229
+ for (const file of files) {
230
+ // Skip non-code files
231
+ if (!isCodeFile(file.path)) continue
232
+
233
+ // Detect auth helper functions by name patterns
234
+ for (const { pattern, name } of AUTH_HELPER_PATTERNS) {
235
+ if (pattern.test(file.content) && !authHelperNames.includes(name)) {
236
+ authHelperNames.push(name)
237
+ }
238
+ }
239
+
240
+ // Detect user scoping patterns
241
+ const scoping = detectUserScopingPatterns(file.content)
242
+ if (scoping.hasUserScoping) {
243
+ hasUserScoping = true
244
+ for (const p of scoping.patterns) {
245
+ if (!userScopingPatterns.includes(p)) {
246
+ userScopingPatterns.push(p)
247
+ }
248
+ }
249
+ }
250
+ }
251
+
252
+ // Merge detected helper names from both sources
253
+ for (const helper of authHelperContext.helpers) {
254
+ if (!authHelperNames.includes(helper.name)) {
255
+ authHelperNames.push(helper.name)
256
+ }
257
+ }
258
+
259
+ return {
260
+ hasGlobalMiddleware: middlewareConfig.hasAuthMiddleware,
261
+ authProvider: middlewareConfig.authType,
262
+ middlewareFile: middlewareConfig.middlewareFile,
263
+ protectedPaths: middlewareConfig.protectedPaths,
264
+ publicPaths: middlewareConfig.publicPaths,
265
+ authHelpers: authHelperNames,
266
+ authHelperContext,
267
+ throwingAuthHelpers,
268
+ hasUserScoping,
269
+ userScopingPatterns,
270
+ }
271
+ }
272
+
273
+ function buildDataAccessContext(files: ScanFile[]): DataAccessContext {
274
+ let orm: DataAccessContext['orm'] = undefined
275
+ let usesParameterizedQueries = false
276
+ let databaseType: DataAccessContext['databaseType'] = undefined
277
+ let hasRLS = false
278
+ let validationLibrary: string | undefined = undefined
279
+
280
+ for (const file of files) {
281
+ if (!isCodeFile(file.path)) continue
282
+ const content = file.content
283
+
284
+ // Detect ORM
285
+ if (!orm) {
286
+ for (const { pattern, orm: ormType } of ORM_PATTERNS) {
287
+ if (pattern.test(content)) {
288
+ orm = ormType
289
+ break
290
+ }
291
+ }
292
+ }
293
+
294
+ // Detect parameterized queries
295
+ if (!usesParameterizedQueries) {
296
+ // Prisma, Drizzle, Supabase all use parameterized by default
297
+ if (orm === 'prisma' || orm === 'drizzle' || orm === 'supabase') {
298
+ usesParameterizedQueries = true
299
+ }
300
+ // Check for explicit parameterized patterns
301
+ if (/\$\d+|\?\s*,|\?(?=\s*\))|:[\w]+/i.test(content) && /query|execute|sql/i.test(content)) {
302
+ usesParameterizedQueries = true
303
+ }
304
+ }
305
+
306
+ // Detect database type
307
+ if (!databaseType) {
308
+ if (/postgres|pg\.|@vercel\/postgres/i.test(content)) {
309
+ databaseType = 'postgres'
310
+ } else if (/mysql|mysql2/i.test(content)) {
311
+ databaseType = 'mysql'
312
+ } else if (/mongodb|mongoose/i.test(content)) {
313
+ databaseType = 'mongodb'
314
+ } else if (/sqlite|better-sqlite/i.test(content)) {
315
+ databaseType = 'sqlite'
316
+ }
317
+ }
318
+
319
+ // Detect RLS
320
+ if (!hasRLS && /row.?level.?security|RLS|\.rls\(|enable_rls/i.test(content)) {
321
+ hasRLS = true
322
+ }
323
+
324
+ // Detect validation library
325
+ if (!validationLibrary) {
326
+ for (const { pattern, lib } of VALIDATION_LIBRARY_PATTERNS) {
327
+ if (pattern.test(content)) {
328
+ validationLibrary = lib
329
+ break
330
+ }
331
+ }
332
+ }
333
+ }
334
+
335
+ // If no ORM detected but SQL patterns found, mark as raw_sql
336
+ if (!orm) {
337
+ const hasRawSql = files.some(f =>
338
+ /SELECT\s+.*FROM|INSERT\s+INTO|UPDATE\s+.*SET|DELETE\s+FROM/i.test(f.content)
339
+ )
340
+ if (hasRawSql) {
341
+ orm = 'raw_sql'
342
+ }
343
+ }
344
+
345
+ return {
346
+ orm,
347
+ usesParameterizedQueries,
348
+ databaseType,
349
+ hasRLS,
350
+ validationLibrary,
351
+ }
352
+ }
353
+
354
+ function buildSecretsContext(files: ScanFile[]): SecretsContext {
355
+ let usesEnvVars = false
356
+ let usesSecretManager = false
357
+ let secretManagerType: string | undefined = undefined
358
+ let hasEnvFiles = false
359
+ let hasClientExposedEnvVars = false
360
+
361
+ for (const file of files) {
362
+ // Check for .env files
363
+ if (file.path.includes('.env')) {
364
+ hasEnvFiles = true
365
+ }
366
+
367
+ // Check for env var usage
368
+ if (/process\.env\.|import\.meta\.env\.|Deno\.env/i.test(file.content)) {
369
+ usesEnvVars = true
370
+ }
371
+
372
+ // Check for NEXT_PUBLIC_ pattern
373
+ if (/NEXT_PUBLIC_/i.test(file.content)) {
374
+ hasClientExposedEnvVars = true
375
+ }
376
+
377
+ // Check for secret managers
378
+ if (!usesSecretManager) {
379
+ for (const { pattern, type } of SECRET_MANAGER_PATTERNS) {
380
+ if (pattern.test(file.content)) {
381
+ usesSecretManager = true
382
+ secretManagerType = type
383
+ break
384
+ }
385
+ }
386
+ }
387
+ }
388
+
389
+ return {
390
+ usesEnvVars,
391
+ usesSecretManager,
392
+ secretManagerType,
393
+ hasEnvFiles,
394
+ hasClientExposedEnvVars,
395
+ }
396
+ }
397
+
398
+ function buildFrameworkContext(files: ScanFile[]): FrameworkContext {
399
+ let primary: FrameworkContext['primary'] = undefined
400
+ let frontend: FrameworkContext['frontend'] = undefined
401
+ let isMonorepo = false
402
+ let usesTypeScript = false
403
+ let usesServerComponents = false
404
+ let usesServerActions = false
405
+
406
+ // Check for package.json to detect frameworks
407
+ const packageJson = files.find(f => f.path === 'package.json' || f.path.endsWith('/package.json'))
408
+ if (packageJson) {
409
+ const content = packageJson.content
410
+
411
+ // Primary framework detection
412
+ if (/["']next["']/i.test(content)) {
413
+ primary = 'nextjs'
414
+ } else if (/["']express["']/i.test(content)) {
415
+ primary = 'express'
416
+ } else if (/["']fastify["']/i.test(content)) {
417
+ primary = 'fastify'
418
+ } else if (/["']@nestjs\/core["']/i.test(content)) {
419
+ primary = 'nestjs'
420
+ }
421
+
422
+ // Frontend framework detection
423
+ if (/["']react["']/i.test(content)) {
424
+ frontend = 'react'
425
+ } else if (/["']vue["']/i.test(content)) {
426
+ frontend = 'vue'
427
+ } else if (/["']svelte["']/i.test(content)) {
428
+ frontend = 'svelte'
429
+ } else if (/["']@angular\/core["']/i.test(content)) {
430
+ frontend = 'angular'
431
+ }
432
+
433
+ // Monorepo detection
434
+ if (/["']workspaces["']|turbo\.json|lerna\.json|pnpm-workspace/i.test(content)) {
435
+ isMonorepo = true
436
+ }
437
+ }
438
+
439
+ // Check for TypeScript
440
+ usesTypeScript = files.some(f => f.path.endsWith('.ts') || f.path.endsWith('.tsx'))
441
+
442
+ // Check for Next.js 13+ patterns
443
+ for (const file of files) {
444
+ if (/['"]use server['"]/.test(file.content)) {
445
+ usesServerActions = true
446
+ }
447
+ if (/['"]use client['"]/.test(file.content) || file.path.includes('/app/')) {
448
+ usesServerComponents = true // App Router implies server components
449
+ }
450
+ }
451
+
452
+ return {
453
+ primary,
454
+ frontend,
455
+ isMonorepo,
456
+ usesTypeScript,
457
+ usesServerComponents,
458
+ usesServerActions,
459
+ }
460
+ }
461
+
462
+ function buildStructureContext(files: ScanFile[]): StructureContext {
463
+ const fileTypes: Record<string, number> = {}
464
+ let hasApiRoutes = false
465
+ let hasMiddleware = false
466
+ let hasServerActions = false
467
+
468
+ for (const file of files) {
469
+ // Count file types
470
+ const ext = getFileExtension(file.path)
471
+ fileTypes[ext] = (fileTypes[ext] || 0) + 1
472
+
473
+ // Check for API routes
474
+ if (/\/api\/|route\.(ts|js)$|\/routes\//i.test(file.path)) {
475
+ hasApiRoutes = true
476
+ }
477
+
478
+ // Check for middleware
479
+ if (/middleware\.(ts|js)$/i.test(file.path)) {
480
+ hasMiddleware = true
481
+ }
482
+
483
+ // Check for server actions
484
+ if (/\/actions\/|\.action\.(ts|js)$/i.test(file.path) || /['"]use server['"]/.test(file.content)) {
485
+ hasServerActions = true
486
+ }
487
+ }
488
+
489
+ return {
490
+ hasApiRoutes,
491
+ hasMiddleware,
492
+ hasServerActions,
493
+ totalFiles: files.length,
494
+ fileTypes,
495
+ }
496
+ }
497
+
498
+ // ============================================================================
499
+ // Summary Generator
500
+ // ============================================================================
501
+
502
+ function generateContextSummary(ctx: Omit<ProjectContext, 'summary'>): string {
503
+ const lines: string[] = []
504
+
505
+ lines.push('## Project Security Context')
506
+ lines.push('')
507
+
508
+ // Framework info
509
+ if (ctx.frameworks.primary) {
510
+ lines.push(`**Framework:** ${ctx.frameworks.primary}${ctx.frameworks.frontend ? ` + ${ctx.frameworks.frontend}` : ''}`)
511
+ }
512
+ if (ctx.frameworks.usesTypeScript) {
513
+ lines.push('**Language:** TypeScript')
514
+ }
515
+ if (ctx.frameworks.usesServerComponents || ctx.frameworks.usesServerActions) {
516
+ lines.push(`**Patterns:** ${[
517
+ ctx.frameworks.usesServerComponents && 'Server Components',
518
+ ctx.frameworks.usesServerActions && 'Server Actions',
519
+ ].filter(Boolean).join(', ')}`)
520
+ }
521
+ lines.push('')
522
+
523
+ // Auth info
524
+ lines.push('### Authentication & Access Control')
525
+ if (ctx.auth.hasGlobalMiddleware) {
526
+ lines.push(`- **Global auth middleware detected** (${ctx.auth.authProvider || 'custom'})`)
527
+ if (ctx.auth.middlewareFile) {
528
+ lines.push(` - Middleware file: \`${ctx.auth.middlewareFile}\``)
529
+ }
530
+ if (ctx.auth.protectedPaths.length > 0) {
531
+ lines.push(` - Protected paths: ${ctx.auth.protectedPaths.join(', ')}`)
532
+ }
533
+ if (ctx.auth.publicPaths.length > 0) {
534
+ lines.push(` - Public paths: ${ctx.auth.publicPaths.join(', ')}`)
535
+ }
536
+ } else {
537
+ lines.push('- No global auth middleware detected')
538
+ }
539
+
540
+ if (ctx.auth.authHelpers.length > 0) {
541
+ lines.push(`- **Auth helpers found:** ${ctx.auth.authHelpers.join(', ')}`)
542
+ }
543
+
544
+ // Throwing auth helpers - CRITICAL for AI validation
545
+ if (ctx.auth.throwingAuthHelpers && ctx.auth.throwingAuthHelpers.length > 0) {
546
+ lines.push('- **Throwing auth helpers detected** (these GUARANTEE authenticated context):')
547
+ for (const helper of ctx.auth.throwingAuthHelpers) {
548
+ const location = helper.definedIn ? ` (in ${helper.definedIn})` : ''
549
+ lines.push(` - \`${helper.name}()\`${location} - throws/redirects on missing auth`)
550
+ }
551
+ lines.push(' - Code AFTER these calls is guaranteed to have an authenticated user')
552
+ lines.push(' - Do NOT flag "missing auth" or suggest `if (!userId)` checks after these calls')
553
+ }
554
+
555
+ if (ctx.auth.hasUserScoping) {
556
+ lines.push('- **User scoping detected** in database queries (data is filtered by user/tenant ID)')
557
+ }
558
+ lines.push('')
559
+
560
+ // Data access info
561
+ lines.push('### Data Access')
562
+ if (ctx.dataAccess.orm) {
563
+ lines.push(`- **ORM/Query Builder:** ${ctx.dataAccess.orm}`)
564
+ if (ctx.dataAccess.usesParameterizedQueries) {
565
+ lines.push(' - Uses parameterized queries by default (SQL injection protection)')
566
+ }
567
+ }
568
+ if (ctx.dataAccess.databaseType) {
569
+ lines.push(`- **Database:** ${ctx.dataAccess.databaseType}`)
570
+ }
571
+ if (ctx.dataAccess.hasRLS) {
572
+ lines.push('- **Row Level Security (RLS)** is enabled')
573
+ }
574
+ if (ctx.dataAccess.validationLibrary) {
575
+ lines.push(`- **Input validation:** ${ctx.dataAccess.validationLibrary}`)
576
+ }
577
+ lines.push('')
578
+
579
+ // Secrets info
580
+ lines.push('### Secrets & Configuration')
581
+ if (ctx.secrets.usesEnvVars) {
582
+ lines.push('- Uses environment variables for configuration')
583
+ }
584
+ if (ctx.secrets.usesSecretManager) {
585
+ lines.push(`- Uses secret manager: ${ctx.secrets.secretManagerType}`)
586
+ }
587
+ if (ctx.secrets.hasClientExposedEnvVars) {
588
+ lines.push('- Has NEXT_PUBLIC_ env vars (intentionally client-exposed)')
589
+ }
590
+ lines.push('')
591
+
592
+ // Validation guidance
593
+ lines.push('### Validation Guidance')
594
+ lines.push('Based on this context:')
595
+
596
+ if (ctx.auth.hasGlobalMiddleware) {
597
+ lines.push(`- Routes under protected paths are guarded by ${ctx.auth.authProvider || 'auth'} middleware`)
598
+ lines.push('- Downgrade "missing auth" findings for routes covered by middleware')
599
+ }
600
+
601
+ if (ctx.auth.throwingAuthHelpers && ctx.auth.throwingAuthHelpers.length > 0) {
602
+ lines.push('- Throwing auth helpers provide guaranteed auth context - do NOT suggest redundant null checks')
603
+ }
604
+
605
+ if (ctx.auth.hasUserScoping) {
606
+ lines.push('- Database queries are scoped by user ID - cross-tenant access is mitigated')
607
+ }
608
+
609
+ if (ctx.dataAccess.usesParameterizedQueries) {
610
+ lines.push('- SQL injection risk is low due to parameterized queries')
611
+ }
612
+
613
+ if (ctx.dataAccess.hasRLS) {
614
+ lines.push('- RLS provides database-level access control')
615
+ }
616
+
617
+ // OAuth flow context
618
+ if (ctx.oauth && (ctx.oauth.hasStateGeneration || ctx.oauth.hasStateValidation || ctx.oauth.hasCodeVerifier)) {
619
+ lines.push('')
620
+ lines.push('### OAuth Flow')
621
+ if (isOAuthStateImplemented(ctx.oauth)) {
622
+ lines.push('- **OAuth state is properly implemented** across files')
623
+ if (ctx.oauth.stateGenerationFile) {
624
+ lines.push(` - State generated in: \`${ctx.oauth.stateGenerationFile}\``)
625
+ }
626
+ if (ctx.oauth.stateValidationFile) {
627
+ lines.push(` - State validated in: \`${ctx.oauth.stateValidationFile}\``)
628
+ }
629
+ lines.push('- Do NOT flag "OAuth state missing" - it is implemented correctly')
630
+ } else if (ctx.oauth.flowType === 'client_credentials') {
631
+ lines.push('- **Client credentials flow detected** - no state parameter needed')
632
+ } else if (ctx.oauth.hasCodeVerifier) {
633
+ lines.push('- **PKCE flow detected** - code_verifier provides CSRF protection')
634
+ }
635
+ if (ctx.oauth.providers.length > 0) {
636
+ lines.push(`- OAuth providers: ${ctx.oauth.providers.join(', ')}`)
637
+ }
638
+ }
639
+
640
+ // tRPC context
641
+ if (ctx.trpc && ctx.trpc.hasTRPC) {
642
+ lines.push('')
643
+ lines.push('### tRPC')
644
+ lines.push(`- ${getTRPCSummary(ctx.trpc)}`)
645
+
646
+ const protectedRouters = Array.from(ctx.trpc.routers.values()).filter(r => r.isProtected)
647
+ if (protectedRouters.length > 0) {
648
+ lines.push('- **Protected tRPC routers detected:**')
649
+ for (const router of protectedRouters) {
650
+ lines.push(` - \`${router.name}\` router is protected by auth middleware`)
651
+ }
652
+ lines.push('- Frontend calls to protected tRPC procedures are SAFE - backend handles auth')
653
+ lines.push('- Do NOT flag "missing role check" on frontend components calling protected tRPC procedures')
654
+ }
655
+ }
656
+
657
+ return lines.join('\n')
658
+ }
659
+
660
+ // ============================================================================
661
+ // Helpers
662
+ // ============================================================================
663
+
664
+ function isCodeFile(path: string): boolean {
665
+ return /\.(ts|tsx|js|jsx|mjs|cjs|py|rb|php|go|java|cs)$/i.test(path)
666
+ }
667
+
668
+ function getFileExtension(path: string): string {
669
+ const match = path.match(/\.([^.]+)$/)
670
+ return match ? `.${match[1]}` : 'unknown'
671
+ }
672
+
673
+ /**
674
+ * Generate a compact context string for a single file validation
675
+ * Used when validating findings in a specific file
676
+ */
677
+ export function getFileValidationContext(
678
+ file: ScanFile,
679
+ projectContext: ProjectContext
680
+ ): string {
681
+ const lines: string[] = []
682
+
683
+ // Include relevant project context
684
+ lines.push(projectContext.summary)
685
+ lines.push('')
686
+
687
+ // Add file-specific context
688
+ lines.push(`### Current File: \`${file.path}\``)
689
+
690
+ // Determine if this file is server-only
691
+ const isServerFile = /\/api\/|\/server\/|\.server\.|\/actions\/|route\.(ts|js)$|middleware\.(ts|js)$/i.test(file.path)
692
+ if (isServerFile) {
693
+ lines.push('- This is a **server-only** file (not bundled to client)')
694
+ }
695
+
696
+ // Check if file is under protected routes
697
+ if (projectContext.auth.hasGlobalMiddleware) {
698
+ const isProtected = projectContext.auth.protectedPaths.some(p =>
699
+ file.path.includes(p.replace(/\*\*/g, '').replace(/\*/g, ''))
700
+ )
701
+ if (isProtected) {
702
+ lines.push(`- This route is **protected by ${projectContext.auth.authProvider || 'auth'} middleware**`)
703
+ }
704
+ }
705
+
706
+ return lines.join('\n')
707
+ }