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