@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,334 @@
1
+ /**
2
+ * Layer 1: URL Pattern Matching
3
+ * Detects hardcoded sensitive URLs that may indicate security issues
4
+ */
5
+
6
+ import type { Vulnerability } from '../types'
7
+
8
+ // Check if file is documentation/README/example
9
+ function isDocumentationFile(filePath: string): boolean {
10
+ const docPatterns = [
11
+ /README/i,
12
+ /CHANGELOG/i,
13
+ /CONTRIBUTING/i,
14
+ /LICENSE/i,
15
+ /\.md$/i,
16
+ /\.mdx$/i,
17
+ /\.rst$/i,
18
+ /\.adoc$/i,
19
+ /\/docs\//i,
20
+ /\/documentation\//i,
21
+ /\/wiki\//i,
22
+ /\/guides?\//i,
23
+ /\/tutorials?\//i,
24
+ /\/examples?\//i,
25
+ ]
26
+ return docPatterns.some(p => p.test(filePath))
27
+ }
28
+
29
+ // Check if file is a config/constants file where localhost is expected
30
+ function isDevConfigFile(filePath: string): boolean {
31
+ const devConfigPatterns = [
32
+ /\.env\.local$/i,
33
+ /\.env\.development$/i,
34
+ /\.env\.dev$/i,
35
+ /\.env\.example$/i,
36
+ /\.env\.sample$/i,
37
+ /config\.dev\./i,
38
+ /config\.development\./i,
39
+ /config\.local\./i,
40
+ /\/config\/development\//i,
41
+ /\/config\/local\//i,
42
+ /constants\.dev\./i,
43
+ ]
44
+ return devConfigPatterns.some(p => p.test(filePath))
45
+ }
46
+
47
+ // URL patterns that may indicate security issues
48
+ const URL_PATTERNS = [
49
+ // Internal/staging endpoints in production code
50
+ {
51
+ pattern: /https?:\/\/localhost[:\d]*/gi,
52
+ name: 'Localhost URL',
53
+ severity: 'medium' as const,
54
+ description: 'Hardcoded localhost URL found - may cause issues in production',
55
+ },
56
+ {
57
+ pattern: /https?:\/\/127\.0\.0\.1[:\d]*/gi,
58
+ name: 'Loopback URL',
59
+ severity: 'medium' as const,
60
+ description: 'Hardcoded loopback IP address found',
61
+ },
62
+ {
63
+ pattern: /https?:\/\/[^\/]*staging[^\/]*\.[a-z]+/gi,
64
+ name: 'Staging URL',
65
+ severity: 'medium' as const,
66
+ description: 'Hardcoded staging environment URL found',
67
+ },
68
+ {
69
+ pattern: /https?:\/\/[^\/]*\bdev\b[^\/]*\.[a-z]+/gi,
70
+ name: 'Development URL',
71
+ severity: 'low' as const,
72
+ description: 'Hardcoded development environment URL found',
73
+ },
74
+ {
75
+ pattern: /https?:\/\/[^\/]*internal[^\/]*\.[a-z]+/gi,
76
+ name: 'Internal URL',
77
+ severity: 'high' as const,
78
+ description: 'Hardcoded internal URL found - may expose internal infrastructure',
79
+ },
80
+ {
81
+ pattern: /https?:\/\/[^\/]*\btest\b[^\/]*\.[a-z]+/gi,
82
+ name: 'Test Environment URL',
83
+ severity: 'low' as const,
84
+ description: 'Hardcoded test environment URL found',
85
+ },
86
+
87
+ // Admin/sensitive endpoints - downgraded to info (these are often intentional)
88
+ {
89
+ pattern: /['"`]\/admin(?:\/|['"`])/gi,
90
+ name: 'Admin Endpoint',
91
+ severity: 'info' as const,
92
+ description: 'Admin endpoint path found - verify access control',
93
+ },
94
+ {
95
+ pattern: /['"`]\/api\/admin/gi,
96
+ name: 'Admin API Endpoint',
97
+ severity: 'low' as const, // Downgraded from medium
98
+ description: 'Admin API endpoint found - verify access control',
99
+ },
100
+
101
+ // API keys in URLs
102
+ {
103
+ pattern: /\?api[_-]?key=[a-zA-Z0-9_-]{10,}/gi,
104
+ name: 'API Key in URL Query',
105
+ severity: 'high' as const,
106
+ description: 'API key exposed in URL query parameter',
107
+ },
108
+ {
109
+ pattern: /&api[_-]?key=[a-zA-Z0-9_-]{10,}/gi,
110
+ name: 'API Key in URL Query',
111
+ severity: 'high' as const,
112
+ description: 'API key exposed in URL query parameter',
113
+ },
114
+ {
115
+ pattern: /\?token=[a-zA-Z0-9_-]{10,}/gi,
116
+ name: 'Token in URL Query',
117
+ severity: 'high' as const,
118
+ description: 'Token exposed in URL query parameter',
119
+ },
120
+ {
121
+ pattern: /\?secret=[a-zA-Z0-9_-]{10,}/gi,
122
+ name: 'Secret in URL Query',
123
+ severity: 'critical' as const,
124
+ description: 'Secret exposed in URL query parameter',
125
+ },
126
+
127
+ // Webhook URLs (may contain secrets)
128
+ {
129
+ pattern: /https:\/\/hooks\.slack\.com\/services\/[a-zA-Z0-9\/]+/gi,
130
+ name: 'Slack Webhook URL',
131
+ severity: 'high' as const,
132
+ description: 'Slack webhook URL found - should be stored as environment variable',
133
+ },
134
+ {
135
+ pattern: /https:\/\/discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[a-zA-Z0-9_-]+/gi,
136
+ name: 'Discord Webhook URL',
137
+ severity: 'high' as const,
138
+ description: 'Discord webhook URL found - should be stored as environment variable',
139
+ },
140
+
141
+ // Debug/test endpoints - downgraded (often in test files or intentional)
142
+ {
143
+ pattern: /['"`]\/debug(?:\/|['"`])/gi,
144
+ name: 'Debug Endpoint',
145
+ severity: 'low' as const, // Downgraded from medium
146
+ description: 'Debug endpoint found - verify not accessible in production',
147
+ },
148
+ {
149
+ pattern: /['"`]\/test(?:\/|['"`])/gi,
150
+ name: 'Test Endpoint',
151
+ severity: 'info' as const, // Downgraded from low
152
+ description: 'Test endpoint found - typically safe in test context',
153
+ },
154
+ ]
155
+
156
+ // Check if line is a comment
157
+ function isComment(lineContent: string): boolean {
158
+ const trimmed = lineContent.trim()
159
+ return (
160
+ trimmed.startsWith('//') ||
161
+ trimmed.startsWith('#') ||
162
+ trimmed.startsWith('*') ||
163
+ trimmed.startsWith('/*')
164
+ )
165
+ }
166
+
167
+ // Check if it's in a test file
168
+ function isTestFile(filePath: string): boolean {
169
+ return /\.(test|spec)\.(ts|tsx|js|jsx)$/i.test(filePath) ||
170
+ /\/__tests__\//i.test(filePath) ||
171
+ /\/test\//i.test(filePath)
172
+ }
173
+
174
+ // Check if URL is in an environment variable reference
175
+ function isEnvVarReference(lineContent: string): boolean {
176
+ return lineContent.includes('process.env') ||
177
+ lineContent.includes('${') ||
178
+ lineContent.includes('import.meta.env')
179
+ }
180
+
181
+ // Get URL context for smarter detection
182
+ function getURLContext(lineContent: string, filePath: string): {
183
+ isDevOnly: boolean
184
+ isConfigFile: boolean
185
+ isTestFile: boolean
186
+ isEnvRef: boolean
187
+ } {
188
+ return {
189
+ isDevOnly: /['"]?BASE_URL['"]?|['"]?API_URL['"]?|['"]?NEXT_PUBLIC_/.test(lineContent),
190
+ isConfigFile: /config|settings|constants/.test(filePath.toLowerCase()),
191
+ isTestFile: isTestFile(filePath),
192
+ isEnvRef: isEnvVarReference(lineContent)
193
+ }
194
+ }
195
+
196
+ // Aggregate repeated localhost findings per file
197
+ export function aggregateLocalhostFindings(
198
+ vulnerabilities: Vulnerability[]
199
+ ): Vulnerability[] {
200
+ const localhostByFile = new Map<string, {
201
+ lines: number[]
202
+ urls: string[]
203
+ original: Vulnerability
204
+ }>()
205
+
206
+ const result: Vulnerability[] = []
207
+
208
+ for (const vuln of vulnerabilities) {
209
+ // Check if this is a localhost/127.0.0.1 URL
210
+ if (vuln.category === 'sensitive_url' &&
211
+ /localhost|127\.0\.0\.1/i.test(vuln.lineContent)) {
212
+
213
+ const key = vuln.filePath
214
+ if (!localhostByFile.has(key)) {
215
+ localhostByFile.set(key, { lines: [], urls: [], original: vuln })
216
+ }
217
+ const entry = localhostByFile.get(key)!
218
+ entry.lines.push(vuln.lineNumber)
219
+ entry.urls.push(vuln.lineContent)
220
+ } else {
221
+ result.push(vuln)
222
+ }
223
+ }
224
+
225
+ // Create aggregated findings for localhost URLs
226
+ for (const [filePath, data] of localhostByFile) {
227
+ const aggregated: Vulnerability = {
228
+ ...data.original,
229
+ title: `Localhost URLs in development code (${data.lines.length} instances)`,
230
+ description: `Found ${data.lines.length} localhost references on lines ${data.lines.join(', ')}. ` +
231
+ `This is typically safe in development but should use environment variables.`,
232
+ lineNumber: data.lines[0],
233
+ severity: 'info', // Downgraded to info
234
+ }
235
+ result.push(aggregated)
236
+ }
237
+
238
+ return result
239
+ }
240
+
241
+ export function detectSensitiveURLs(
242
+ content: string,
243
+ filePath: string
244
+ ): Vulnerability[] {
245
+ const vulnerabilities: Vulnerability[] = []
246
+
247
+ // Skip documentation files entirely - they often contain example URLs
248
+ if (isDocumentationFile(filePath)) {
249
+ return vulnerabilities
250
+ }
251
+
252
+ const lines = content.split('\n')
253
+ const inTestFile = isTestFile(filePath)
254
+ const inDevConfig = isDevConfigFile(filePath)
255
+
256
+ for (let i = 0; i < lines.length; i++) {
257
+ const line = lines[i]
258
+
259
+ // Skip comments
260
+ if (isComment(line)) continue
261
+
262
+ // Get context for this line
263
+ const context = getURLContext(line, filePath)
264
+
265
+ // Skip environment variable references entirely
266
+ if (context.isEnvRef) continue
267
+
268
+ for (const { pattern, name, severity, description } of URL_PATTERNS) {
269
+ // Reset regex state
270
+ const regex = new RegExp(pattern.source, pattern.flags)
271
+
272
+ const match = regex.exec(line)
273
+ if (match) {
274
+ const url = match[0]
275
+
276
+ // Special handling for localhost URLs
277
+ if (/localhost|127\.0\.0\.1/i.test(url)) {
278
+ // Skip localhost in test files, dev-only contexts, or dev config files
279
+ if (context.isTestFile || context.isDevOnly || inDevConfig) {
280
+ continue
281
+ }
282
+
283
+ // Only flag localhost in production env files as high, otherwise info
284
+ const isProduction = filePath.includes('.env.production')
285
+ const adjustedSeverity = isProduction ? 'high' as const : 'info' as const
286
+
287
+ vulnerabilities.push({
288
+ id: `url-${filePath}-${i + 1}-${name}`,
289
+ filePath,
290
+ lineNumber: i + 1,
291
+ lineContent: line.trim(),
292
+ severity: adjustedSeverity,
293
+ category: 'sensitive_url',
294
+ title: name,
295
+ description: description + (isProduction ? ' (in production config!)' : ' (in dev/config file)'),
296
+ suggestedFix: 'Move URLs to environment variables or configuration files. Use process.env.API_URL pattern.',
297
+ confidence: isProduction ? 'high' : 'low',
298
+ layer: 1,
299
+ })
300
+ } else {
301
+ // Normal URL handling (non-localhost)
302
+ // Lower severity for test files - downgrade more aggressively
303
+ let adjustedSeverity = severity
304
+ if (inTestFile) {
305
+ if (severity === 'critical') adjustedSeverity = 'high'
306
+ else if (severity === 'high') adjustedSeverity = 'low'
307
+ else adjustedSeverity = 'info'
308
+ }
309
+
310
+ // Non-critical URL findings require AI validation
311
+ const requiresAIValidation = severity !== 'critical'
312
+
313
+ vulnerabilities.push({
314
+ id: `url-${filePath}-${i + 1}-${name}`,
315
+ filePath,
316
+ lineNumber: i + 1,
317
+ lineContent: line.trim(),
318
+ severity: adjustedSeverity,
319
+ category: 'sensitive_url',
320
+ title: name,
321
+ description: description + (inTestFile ? ' (in test file)' : ''),
322
+ suggestedFix: 'Move URLs to environment variables or configuration files. Use process.env.API_URL pattern.',
323
+ confidence: inTestFile ? 'low' : 'medium',
324
+ layer: 1,
325
+ requiresAIValidation,
326
+ })
327
+ }
328
+ break // Only report one URL issue per line
329
+ }
330
+ }
331
+ }
332
+
333
+ return vulnerabilities
334
+ }
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Layer 1: Weak Cryptography Detection
3
+ * Detects usage of deprecated or weak cryptographic algorithms
4
+ */
5
+
6
+ import type { Vulnerability } from '../types'
7
+
8
+ // Weak/deprecated cryptographic patterns
9
+ const WEAK_CRYPTO_PATTERNS = [
10
+ // Weak hash algorithms
11
+ {
12
+ pattern: /\bMD5\s*\(/gi,
13
+ name: 'MD5 Hash Usage',
14
+ severity: 'high' as const,
15
+ description: 'MD5 is cryptographically broken and should not be used for security purposes',
16
+ fix: 'Use SHA-256 or SHA-3 for hashing. For passwords, use bcrypt, scrypt, or Argon2.',
17
+ contextCheck: (line: string) => {
18
+ // Only flag as high if used in security context (passwords, tokens, etc.)
19
+ // Checksum/etag usage is acceptable for non-security purposes
20
+ const securityContexts = [/password/i, /token/i, /secret/i, /credential/i, /auth/i, /session/i, /key/i]
21
+ const checksumContexts = [/checksum/i, /hash.*file/i, /file.*hash/i, /etag/i, /content.*hash/i, /integrity/i, /digest/i, /fingerprint/i]
22
+
23
+ const isSecurityContext = securityContexts.some(p => p.test(line))
24
+ const isChecksumContext = checksumContexts.some(p => p.test(line))
25
+
26
+ // If it's clearly a checksum context, don't flag
27
+ if (isChecksumContext && !isSecurityContext) {
28
+ return false
29
+ }
30
+ return true
31
+ },
32
+ },
33
+ {
34
+ pattern: /createHash\s*\(\s*['"]md5['"]\s*\)/gi,
35
+ name: 'MD5 Hash Creation',
36
+ severity: 'high' as const,
37
+ description: 'MD5 is cryptographically broken and should not be used for security purposes',
38
+ fix: 'Use createHash(\'sha256\') or createHash(\'sha3-256\') instead.',
39
+ contextCheck: (line: string, _match: RegExpMatchArray, funcName?: string) => {
40
+ // Check function name and line context for checksum indicators
41
+ const checksumIndicators = [/checksum/i, /etag/i, /content.*hash/i, /file.*hash/i, /integrity/i, /digest/i, /fingerprint/i, /verify.*file/i]
42
+ const securityIndicators = [/password/i, /token/i, /secret/i, /credential/i, /auth/i, /session/i]
43
+
44
+ const lineAndFunc = funcName ? `${funcName} ${line}` : line
45
+
46
+ const isChecksum = checksumIndicators.some(p => p.test(lineAndFunc))
47
+ const isSecurity = securityIndicators.some(p => p.test(lineAndFunc))
48
+
49
+ // Checksum use without security context = acceptable, don't flag
50
+ if (isChecksum && !isSecurity) {
51
+ return false
52
+ }
53
+ return true
54
+ },
55
+ },
56
+ {
57
+ pattern: /\bSHA1\s*\(/gi,
58
+ name: 'SHA1 Hash Usage',
59
+ severity: 'medium' as const,
60
+ description: 'SHA1 is deprecated and vulnerable to collision attacks',
61
+ fix: 'Use SHA-256 or SHA-3 for hashing.',
62
+ },
63
+ {
64
+ pattern: /createHash\s*\(\s*['"]sha1['"]\s*\)/gi,
65
+ name: 'SHA1 Hash Creation',
66
+ severity: 'medium' as const,
67
+ description: 'SHA1 is deprecated and vulnerable to collision attacks',
68
+ fix: 'Use createHash(\'sha256\') or createHash(\'sha3-256\') instead.',
69
+ },
70
+
71
+ // Weak encryption algorithms
72
+ {
73
+ pattern: /\bDES\b(?!ede3|3)/gi,
74
+ name: 'DES Encryption',
75
+ severity: 'high' as const,
76
+ description: 'DES is obsolete and easily broken. Use AES instead.',
77
+ fix: 'Use AES-256-GCM for symmetric encryption.',
78
+ },
79
+ {
80
+ pattern: /createCipher(?:iv)?\s*\(\s*['"]des['"]/gi,
81
+ name: 'DES Cipher Creation',
82
+ severity: 'high' as const,
83
+ description: 'DES is obsolete and easily broken',
84
+ fix: 'Use createCipheriv(\'aes-256-gcm\', ...) instead.',
85
+ },
86
+ {
87
+ pattern: /\bRC4\b/gi,
88
+ name: 'RC4 Encryption',
89
+ severity: 'high' as const,
90
+ description: 'RC4 is broken and should never be used',
91
+ fix: 'Use AES-256-GCM for symmetric encryption.',
92
+ },
93
+ {
94
+ pattern: /createCipher(?:iv)?\s*\(\s*['"]rc4['"]/gi,
95
+ name: 'RC4 Cipher Creation',
96
+ severity: 'high' as const,
97
+ description: 'RC4 is broken and should never be used',
98
+ fix: 'Use createCipheriv(\'aes-256-gcm\', ...) instead.',
99
+ },
100
+ {
101
+ pattern: /\bBlowfish\b/gi,
102
+ name: 'Blowfish Encryption',
103
+ severity: 'medium' as const,
104
+ description: 'Blowfish has a small block size and is not recommended for new applications',
105
+ fix: 'Use AES-256-GCM for symmetric encryption.',
106
+ },
107
+
108
+ // Insecure random number generation
109
+ {
110
+ pattern: /Math\.random\s*\(\s*\)/g,
111
+ name: 'Math.random() for Security',
112
+ severity: 'high' as const,
113
+ description: 'Math.random() is not cryptographically secure and should not be used for security purposes',
114
+ fix: 'Use crypto.randomBytes() or crypto.getRandomValues() for cryptographic operations.',
115
+ contextCheck: (line: string) => {
116
+ // Only flag if it looks like it's being used for security
117
+ const securityContexts = [
118
+ /token/i, /secret/i, /key/i, /password/i, /salt/i,
119
+ /nonce/i, /iv/i, /random.*id/i, /uuid/i, /session/i,
120
+ ]
121
+ return securityContexts.some(ctx => ctx.test(line))
122
+ },
123
+ },
124
+
125
+ // Weak key derivation
126
+ {
127
+ pattern: /pbkdf2.*iterations?\s*[=:]\s*(\d+)/gi,
128
+ name: 'Weak PBKDF2 Iterations',
129
+ severity: 'medium' as const,
130
+ description: 'PBKDF2 with low iteration count is vulnerable to brute force attacks',
131
+ fix: 'Use at least 100,000 iterations for PBKDF2, or switch to Argon2.',
132
+ contextCheck: (line: string, match: RegExpMatchArray) => {
133
+ const iterations = parseInt(match[1], 10)
134
+ return iterations < 10000
135
+ },
136
+ },
137
+
138
+ // Weak bcrypt rounds
139
+ {
140
+ pattern: /bcrypt\.hash\s*\([^,]+,\s*(\d+)/gi,
141
+ name: 'Weak bcrypt Rounds',
142
+ severity: 'medium' as const,
143
+ description: 'bcrypt with low cost factor is vulnerable to brute force attacks',
144
+ fix: 'Use at least 10 rounds for bcrypt (12 recommended).',
145
+ contextCheck: (line: string, match: RegExpMatchArray) => {
146
+ const rounds = parseInt(match[1], 10)
147
+ return rounds < 10
148
+ },
149
+ },
150
+
151
+ // ECB mode (insecure)
152
+ {
153
+ pattern: /['"]aes-\d+-ecb['"]/gi,
154
+ name: 'AES ECB Mode',
155
+ severity: 'high' as const,
156
+ description: 'ECB mode is insecure as it does not provide semantic security',
157
+ fix: 'Use AES-GCM or AES-CBC with proper IV handling.',
158
+ },
159
+
160
+ // Deprecated createCipher (no IV)
161
+ {
162
+ pattern: /createCipher\s*\(/g,
163
+ name: 'Deprecated createCipher',
164
+ severity: 'high' as const,
165
+ description: 'createCipher is deprecated and does not use an IV, making it insecure',
166
+ fix: 'Use createCipheriv() with a random IV instead.',
167
+ },
168
+
169
+ // Hardcoded encryption keys
170
+ {
171
+ pattern: /(?:encryption|cipher|aes)[_-]?key\s*[=:]\s*['"][a-zA-Z0-9+/=]{16,}['"]/gi,
172
+ name: 'Hardcoded Encryption Key',
173
+ severity: 'critical' as const,
174
+ description: 'Encryption key is hardcoded in source code',
175
+ fix: 'Store encryption keys in environment variables or a secure key management system.',
176
+ },
177
+
178
+ // Hardcoded IVs
179
+ {
180
+ pattern: /\biv\s*[=:]\s*['"][a-zA-Z0-9+/=]{16,}['"]/gi,
181
+ name: 'Hardcoded IV',
182
+ severity: 'high' as const,
183
+ description: 'Initialization vector (IV) should be random for each encryption operation',
184
+ fix: 'Generate a random IV using crypto.randomBytes() for each encryption.',
185
+ },
186
+ ]
187
+
188
+ // Check if line is a comment
189
+ function isComment(lineContent: string): boolean {
190
+ const trimmed = lineContent.trim()
191
+ return (
192
+ trimmed.startsWith('//') ||
193
+ trimmed.startsWith('#') ||
194
+ trimmed.startsWith('*') ||
195
+ trimmed.startsWith('/*')
196
+ )
197
+ }
198
+
199
+ // Check if line is a pattern definition (regex or string literal in detector code)
200
+ function isPatternDefinition(lineContent: string): boolean {
201
+ const trimmed = lineContent.trim()
202
+ // Pattern definitions typically look like:
203
+ // pattern: /regex/
204
+ // name: 'string'
205
+ // description: 'string'
206
+ // fix: 'string'
207
+ return (
208
+ trimmed.startsWith('pattern:') ||
209
+ trimmed.startsWith('name:') ||
210
+ trimmed.startsWith('description:') ||
211
+ trimmed.startsWith('fix:') ||
212
+ // Also check for object property assignments with these names
213
+ /^\s*(pattern|name|description|fix|severity)\s*:/.test(lineContent)
214
+ )
215
+ }
216
+
217
+ // Check if file is part of the scanner's own detection code
218
+ function isScannerDetectorFile(filePath: string): boolean {
219
+ return (
220
+ filePath.includes('scanner/src/layer1/') ||
221
+ filePath.includes('scanner/src/layer2/') ||
222
+ filePath.includes('scanner/src/layer3/') ||
223
+ filePath.includes('/lib/scanner/layer1/') ||
224
+ filePath.includes('/lib/scanner/layer2/') ||
225
+ filePath.includes('/lib/scanner/layer3/')
226
+ )
227
+ }
228
+
229
+ // Check if file is a test file or fixture
230
+ function isTestOrFixtureFile(filePath: string): boolean {
231
+ const lowerPath = filePath.toLowerCase()
232
+ return (
233
+ lowerPath.includes('__tests__') ||
234
+ lowerPath.includes('__mocks__') ||
235
+ lowerPath.includes('/test/') ||
236
+ lowerPath.includes('/tests/') ||
237
+ lowerPath.includes('/fixtures/') ||
238
+ lowerPath.includes('/fixture/') ||
239
+ lowerPath.includes('.test.') ||
240
+ lowerPath.includes('.spec.') ||
241
+ lowerPath.includes('-test.') ||
242
+ lowerPath.includes('-spec.') ||
243
+ lowerPath.includes('benchmark')
244
+ )
245
+ }
246
+
247
+ /**
248
+ * Find the enclosing function name for a given line
249
+ */
250
+ function findEnclosingFunctionName(lines: string[], lineIndex: number): string | undefined {
251
+ // Look backwards for function declaration
252
+ for (let i = lineIndex; i >= 0 && i >= lineIndex - 20; i--) {
253
+ const line = lines[i]
254
+ // Match function declarations: function name(), const name = (), async function name()
255
+ const funcMatch = line.match(/(?:function\s+|const\s+|let\s+|var\s+)(\w+)\s*(?:=\s*(?:async\s*)?\(|=\s*(?:async\s+)?function|\()/)
256
+ if (funcMatch) {
257
+ return funcMatch[1]
258
+ }
259
+ // Match method declarations: name() { or name: function()
260
+ const methodMatch = line.match(/^\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/)
261
+ if (methodMatch) {
262
+ return methodMatch[1]
263
+ }
264
+ }
265
+ return undefined
266
+ }
267
+
268
+ export function detectWeakCrypto(
269
+ content: string,
270
+ filePath: string
271
+ ): Vulnerability[] {
272
+ const vulnerabilities: Vulnerability[] = []
273
+ const lines = content.split('\n')
274
+
275
+ // Skip scanner's own detector files to avoid self-detection
276
+ if (isScannerDetectorFile(filePath)) {
277
+ return vulnerabilities
278
+ }
279
+
280
+ // Skip test files and fixtures (intentional vulnerable code for testing)
281
+ if (isTestOrFixtureFile(filePath)) {
282
+ return vulnerabilities
283
+ }
284
+
285
+ for (let i = 0; i < lines.length; i++) {
286
+ const line = lines[i]
287
+
288
+ // Skip comments
289
+ if (isComment(line)) continue
290
+
291
+ // Skip pattern definitions (detector rule definitions)
292
+ if (isPatternDefinition(line)) continue
293
+
294
+ for (const cryptoPattern of WEAK_CRYPTO_PATTERNS) {
295
+ const { pattern, name, severity, description, fix, contextCheck } = cryptoPattern
296
+
297
+ // Reset regex state
298
+ const regex = new RegExp(pattern.source, pattern.flags)
299
+ const match = regex.exec(line)
300
+
301
+ if (match) {
302
+ // If there's a context check, apply it
303
+ // Pass function name for additional context
304
+ const funcName = findEnclosingFunctionName(lines, i)
305
+ if (contextCheck && !contextCheck(line, match, funcName)) {
306
+ continue
307
+ }
308
+
309
+ vulnerabilities.push({
310
+ id: `weak-crypto-${filePath}-${i + 1}-${name}`,
311
+ filePath,
312
+ lineNumber: i + 1,
313
+ lineContent: line.trim(),
314
+ severity,
315
+ category: 'weak_crypto',
316
+ title: name,
317
+ description,
318
+ suggestedFix: fix,
319
+ confidence: 'high',
320
+ layer: 1,
321
+ })
322
+ break // Only report one crypto issue per line
323
+ }
324
+ }
325
+ }
326
+
327
+ return vulnerabilities
328
+ }