@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,387 @@
1
+ /**
2
+ * Layer 2: AI Endpoint Protection Detection
3
+ * Detects AI/LLM endpoints without proper authentication or rate limiting
4
+ *
5
+ * Covers:
6
+ * - M5.2: AI endpoints without auth/rate limiting
7
+ * - Cost-bearing AI endpoints exposed publicly
8
+ * - Missing rate limiting on AI routes
9
+ */
10
+
11
+ import type { Vulnerability, VulnerabilitySeverity } from '../types'
12
+ import type { MiddlewareAuthConfig } from '../utils/middleware-detector'
13
+ import {
14
+ isComment,
15
+ isTestOrMockFile,
16
+ isDocumentationFile,
17
+ isScannerOrFixtureFile,
18
+ isExampleDirectory,
19
+ } from '../utils/context-helpers'
20
+
21
+ // ============================================================================
22
+ // Context Detection
23
+ // ============================================================================
24
+
25
+ /**
26
+ * Check if file is a route/API handler
27
+ */
28
+ function isRouteFile(filePath: string): boolean {
29
+ const routePatterns = [
30
+ /\/api\/.*\.(ts|js)$/i,
31
+ /\/routes?\/.*\.(ts|js)$/i,
32
+ /route\.(ts|js)$/i,
33
+ /\/pages\/api\/.*\.(ts|js)$/i,
34
+ /\/app\/.*\/route\.(ts|js)$/i,
35
+ /\.(controller|handler)\.(ts|js)$/i,
36
+ ]
37
+ return routePatterns.some(p => p.test(filePath))
38
+ }
39
+
40
+ /**
41
+ * Check if content contains AI/LLM API calls
42
+ */
43
+ function hasAIApiCalls(content: string): boolean {
44
+ const aiPatterns = [
45
+ // OpenAI
46
+ /openai\.chat\.completions?\.create/i,
47
+ /openai\.completions?\.create/i,
48
+ /openai\.embeddings?\.create/i,
49
+ // Anthropic
50
+ /anthropic\.messages\.create/i,
51
+ /anthropic\.completions?\.create/i,
52
+ // Vercel AI SDK
53
+ /\bgenerateText\s*\(/i,
54
+ /\bstreamText\s*\(/i,
55
+ /\bgenerateObject\s*\(/i,
56
+ /\bstreamObject\s*\(/i,
57
+ // Generic patterns
58
+ /\.create\s*\(\s*\{[^}]*model\s*:/i,
59
+ /ChatCompletion|MessageCreate/i,
60
+ // LangChain
61
+ /\.invoke\s*\([^)]*\)/i,
62
+ /ChatOpenAI|ChatAnthropic|ChatModel/i,
63
+ // Other providers
64
+ /replicate\.run/i,
65
+ /cohere\.(?:generate|chat)/i,
66
+ /mistral\.chat/i,
67
+ ]
68
+ return aiPatterns.some(p => p.test(content))
69
+ }
70
+
71
+ /**
72
+ * Check if there's authentication in the route
73
+ */
74
+ function hasAuthentication(content: string): boolean {
75
+ const authPatterns = [
76
+ // Session/user checks
77
+ /getSession|getServerSession|getCurrentUser/i,
78
+ /auth\(\)|requireAuth|withAuth|ensureAuth/i,
79
+ /verifyToken|validateToken|checkToken/i,
80
+ /req\.user|request\.user|context\.user/i,
81
+ /isAuthenticated|checkAuth/i,
82
+ // Header checks
83
+ /Authorization.*Bearer/i,
84
+ /headers\[['"`]authorization['"`]\]/i,
85
+ /getHeader\(['"`]authorization['"`]\)/i,
86
+ // API key validation
87
+ /apiKey|api_key|x-api-key/i,
88
+ // Clerk/NextAuth/Auth0
89
+ /currentUser\(\)|auth\(\)\.protect/i,
90
+ /getAuth\(\)|clerkClient/i,
91
+ // User ID extraction (implies auth)
92
+ /userId|user\.id|currentUserId/i,
93
+ // 401/403 responses
94
+ /status\(401\)|status\(403\)/i,
95
+ /Unauthorized|Forbidden/i,
96
+ ]
97
+ return authPatterns.some(p => p.test(content))
98
+ }
99
+
100
+ /**
101
+ * Check if there's rate limiting
102
+ */
103
+ function hasRateLimiting(content: string): boolean {
104
+ const rateLimitPatterns = [
105
+ // Rate limit middleware/libraries
106
+ /rateLimit|rateLimiter/i,
107
+ /express-rate-limit/i,
108
+ /upstash.*ratelimit|@upstash\/ratelimit/i,
109
+ /rate-limiter-flexible/i,
110
+ // Custom rate limiting
111
+ /throttle|Throttle/i,
112
+ /requestsPerMinute|requestsPerHour/i,
113
+ /maxRequests|requestLimit/i,
114
+ // Rate limit headers
115
+ /X-RateLimit|x-ratelimit/i,
116
+ /Retry-After/i,
117
+ // 429 responses
118
+ /status\(429\)|\.status === 429/i,
119
+ /Too Many Requests|TooManyRequests/i,
120
+ // Sliding window/token bucket
121
+ /slidingWindow|tokenBucket|fixedWindow/i,
122
+ ]
123
+ return rateLimitPatterns.some(p => p.test(content))
124
+ }
125
+
126
+ /**
127
+ * Check if this is a BYOK (Bring Your Own Key) endpoint
128
+ * BYOK endpoints have lower risk since user pays for their own usage
129
+ */
130
+ function isBYOKEndpoint(content: string): boolean {
131
+ const byokPatterns = [
132
+ /req\.body\.(?:apiKey|api_key|openaiKey|anthropicKey)/i,
133
+ /request\.(?:apiKey|api_key|openaiKey|anthropicKey)/i,
134
+ /userApiKey|user_api_key|clientApiKey/i,
135
+ /['"`](?:apiKey|api_key|openai_key|anthropic_key)['"`]\s*:/i,
136
+ // Headers with user's key
137
+ /headers\[['"`]x-openai-key['"`]\]/i,
138
+ /headers\[['"`]x-api-key['"`]\]/i,
139
+ ]
140
+ return byokPatterns.some(p => p.test(content))
141
+ }
142
+
143
+ /**
144
+ * Check if route is protected by middleware (from config)
145
+ */
146
+ function isProtectedByMiddleware(
147
+ filePath: string,
148
+ middlewareConfig?: MiddlewareAuthConfig
149
+ ): boolean {
150
+ if (!middlewareConfig?.protectedPaths) return false
151
+
152
+ // Extract route path from file path
153
+ const routePath = filePath
154
+ .replace(/.*\/(pages|app)\/api/, '/api')
155
+ .replace(/.*\/routes?/, '')
156
+ .replace(/\/route\.(ts|js)$/, '')
157
+ .replace(/\.(ts|js)$/, '')
158
+ .replace(/\[([^\]]+)\]/g, ':$1')
159
+
160
+ return middlewareConfig.protectedPaths.some(p => routePath.startsWith(p))
161
+ }
162
+
163
+ /**
164
+ * Check if this is an internal/admin-only route
165
+ */
166
+ function isInternalRoute(filePath: string, content: string): boolean {
167
+ const internalPatterns = [
168
+ /\/internal\//i,
169
+ /\/admin\//i,
170
+ /\/_internal\//i,
171
+ // Content checks
172
+ /adminOnly|internalOnly|isAdmin/i,
173
+ /process\.env\.INTERNAL_SECRET/i,
174
+ ]
175
+ return internalPatterns.some(p => p.test(filePath) || p.test(content))
176
+ }
177
+
178
+ /**
179
+ * Get surrounding context
180
+ */
181
+ function getSurroundingContext(content: string, lineIndex: number, windowSize: number = 50): string {
182
+ const lines = content.split('\n')
183
+ const start = Math.max(0, lineIndex - windowSize)
184
+ const end = Math.min(lines.length, lineIndex + windowSize)
185
+ return lines.slice(start, end).join('\n')
186
+ }
187
+
188
+ // ============================================================================
189
+ // Pattern Definitions
190
+ // ============================================================================
191
+
192
+ interface EndpointProtectionPattern {
193
+ name: string
194
+ pattern: RegExp
195
+ framework: 'nextjs_app' | 'nextjs_pages' | 'express' | 'fastify' | 'generic'
196
+ description: string
197
+ suggestedFix: string
198
+ }
199
+
200
+ /**
201
+ * Route handler patterns by framework
202
+ */
203
+ const ROUTE_HANDLER_PATTERNS: EndpointProtectionPattern[] = [
204
+ // Next.js App Router
205
+ {
206
+ name: 'Next.js App Router AI endpoint',
207
+ pattern: /export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)\s*\(/gi,
208
+ framework: 'nextjs_app',
209
+ description: 'Next.js App Router endpoint with AI API calls lacks protection.',
210
+ suggestedFix: 'Add authentication check at start of handler. Consider using middleware for auth and rate limiting.',
211
+ },
212
+ // Next.js Pages Router
213
+ {
214
+ name: 'Next.js Pages API route',
215
+ pattern: /export\s+default\s+(?:async\s+)?function\s*(?:\w+\s*)?\([^)]*req/gi,
216
+ framework: 'nextjs_pages',
217
+ description: 'Next.js Pages API route with AI calls lacks protection.',
218
+ suggestedFix: 'Add authentication: const session = await getServerSession(req, res, authOptions); if (!session) return res.status(401).json({error: "Unauthorized"})',
219
+ },
220
+ // Express
221
+ {
222
+ name: 'Express AI route',
223
+ pattern: /(?:app|router)\.(get|post|put|patch|delete)\s*\(\s*['"`][^'"`]+['"`]\s*,/gi,
224
+ framework: 'express',
225
+ description: 'Express route with AI API calls lacks middleware protection.',
226
+ suggestedFix: 'Add authentication and rate limiting middleware: app.post("/api/chat", authMiddleware, rateLimiter, handler)',
227
+ },
228
+ // Fastify
229
+ {
230
+ name: 'Fastify AI route',
231
+ pattern: /fastify\.(get|post|put|patch|delete)\s*\(\s*['"`][^'"`]+['"`]/gi,
232
+ framework: 'fastify',
233
+ description: 'Fastify route with AI calls lacks protection.',
234
+ suggestedFix: 'Add preHandler hooks for auth and rate limiting: { preHandler: [authenticate, rateLimit] }',
235
+ },
236
+ // Generic handler exports
237
+ {
238
+ name: 'API handler with AI calls',
239
+ pattern: /export\s+(?:const|async\s+function)\s+(?:handler|apiHandler)\s*[=:]/gi,
240
+ framework: 'generic',
241
+ description: 'API handler with AI calls may lack protection.',
242
+ suggestedFix: 'Ensure authentication and rate limiting are applied before AI API calls.',
243
+ },
244
+ ]
245
+
246
+ // ============================================================================
247
+ // Main Detection Function
248
+ // ============================================================================
249
+
250
+ export interface EndpointProtectionOptions {
251
+ middlewareConfig?: MiddlewareAuthConfig
252
+ }
253
+
254
+ /**
255
+ * Main detection function for AI endpoint protection issues
256
+ */
257
+ export function detectAIEndpointProtection(
258
+ content: string,
259
+ filePath: string,
260
+ options: EndpointProtectionOptions = {}
261
+ ): Vulnerability[] {
262
+ const vulnerabilities: Vulnerability[] = []
263
+
264
+ // Skip non-applicable files
265
+ if (isScannerOrFixtureFile(filePath)) return vulnerabilities
266
+ if (isDocumentationFile(filePath)) return vulnerabilities
267
+
268
+ // Only scan route files that have AI API calls
269
+ if (!isRouteFile(filePath) || !hasAIApiCalls(content)) {
270
+ return vulnerabilities
271
+ }
272
+
273
+ const lines = content.split('\n')
274
+ const isTestFile = isTestOrMockFile(filePath)
275
+ const isExample = isExampleDirectory(filePath)
276
+
277
+ // Check file-level protection
278
+ const fileHasAuth = hasAuthentication(content)
279
+ const fileHasRateLimit = hasRateLimiting(content)
280
+ const isByok = isBYOKEndpoint(content)
281
+ const isInternal = isInternalRoute(filePath, content)
282
+ const isMiddlewareProtected = isProtectedByMiddleware(filePath, options.middlewareConfig)
283
+
284
+ // Scan for route handler patterns
285
+ for (const pattern of ROUTE_HANDLER_PATTERNS) {
286
+ const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
287
+ let match
288
+
289
+ while ((match = regex.exec(content)) !== null) {
290
+ const lineNumber = content.substring(0, match.index).split('\n').length
291
+ const lineContent = lines[lineNumber - 1]?.trim() || ''
292
+
293
+ // Skip comments
294
+ if (isComment(lineContent)) continue
295
+
296
+ // Get surrounding context
297
+ const context = getSurroundingContext(content, lineNumber - 1, 50)
298
+
299
+ // Check for local protection patterns (within handler)
300
+ const handlerHasAuth = hasAuthentication(context)
301
+ const handlerHasRateLimit = hasRateLimiting(context)
302
+
303
+ // Calculate severity based on protection status
304
+ let severity: VulnerabilitySeverity
305
+ const notes: string[] = []
306
+
307
+ if (isMiddlewareProtected || isInternal) {
308
+ severity = 'info'
309
+ notes.push(isMiddlewareProtected ? 'Protected by middleware' : 'Internal route')
310
+ } else if (handlerHasAuth || fileHasAuth) {
311
+ if (handlerHasRateLimit || fileHasRateLimit) {
312
+ // Has both auth and rate limiting
313
+ severity = 'info'
314
+ notes.push('Has authentication and rate limiting')
315
+ } else {
316
+ // Has auth but no rate limiting
317
+ severity = 'low'
318
+ notes.push('Has authentication but missing rate limiting')
319
+ }
320
+ } else if (handlerHasRateLimit || fileHasRateLimit) {
321
+ // Has rate limiting but no auth
322
+ severity = 'medium'
323
+ notes.push('Has rate limiting but missing authentication')
324
+ } else {
325
+ // No protection at all
326
+ severity = 'high'
327
+ notes.push('Missing authentication and rate limiting')
328
+ }
329
+
330
+ // BYOK endpoints have lower risk
331
+ if (isByok && severity !== 'info') {
332
+ severity = severity === 'high' ? 'medium' : (severity === 'medium' ? 'low' : severity)
333
+ notes.push('BYOK endpoint - user provides API key')
334
+ }
335
+
336
+ // Downgrade test files
337
+ if (isTestFile) {
338
+ severity = 'info'
339
+ notes.push('in test file')
340
+ }
341
+
342
+ // Downgrade example/demo directories - these are tutorials, not production code
343
+ if (isExample && severity !== 'info') {
344
+ severity = 'info'
345
+ notes.push('in example/demo directory - tutorial code')
346
+ }
347
+
348
+ // Build description
349
+ let description = pattern.description
350
+ if (notes.length > 0) {
351
+ description += ` (${notes.join('; ')})`
352
+ }
353
+
354
+ // Determine suggested fix based on what's missing
355
+ let suggestedFix = pattern.suggestedFix
356
+ if (handlerHasAuth && !handlerHasRateLimit) {
357
+ suggestedFix = 'Add rate limiting to prevent cost abuse: npm install express-rate-limit or use Upstash ratelimit for serverless.'
358
+ }
359
+
360
+ vulnerabilities.push({
361
+ id: `ai-endpoint-${filePath}-${lineNumber}-${pattern.name.replace(/\s+/g, '-')}`,
362
+ filePath,
363
+ lineNumber,
364
+ lineContent,
365
+ severity,
366
+ category: 'ai_endpoint_unprotected',
367
+ title: pattern.name,
368
+ description,
369
+ suggestedFix,
370
+ confidence: severity === 'info' ? 'low' : 'medium',
371
+ layer: 2,
372
+ requiresAIValidation: severity !== 'info',
373
+ })
374
+
375
+ // Only report one finding per file (file-level issue)
376
+ break
377
+ }
378
+
379
+ // Only process if we found a route handler (avoid duplicate patterns)
380
+ if (vulnerabilities.length > 0) break
381
+ }
382
+
383
+ return vulnerabilities
384
+ }
385
+
386
+ // Export helpers for use in other modules
387
+ export { isRouteFile, hasAIApiCalls, hasAuthentication, hasRateLimiting }