@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,315 @@
1
+ /**
2
+ * Layer 2: Data Exposure Detection
3
+ * Identifies sensitive data in logs vs API responses with appropriate severity
4
+ * Separates "logging concerns" from "response exposure" which have different risk profiles
5
+ */
6
+
7
+ import type { Vulnerability, VulnerabilitySeverity } from '../types'
8
+ import { isComment, isTestOrMockFile } from '../utils/context-helpers'
9
+
10
+ interface DataExposurePattern {
11
+ name: string
12
+ pattern: RegExp
13
+ sink: 'log' | 'response' | 'both'
14
+ severity: VulnerabilitySeverity
15
+ description: string
16
+ suggestedFix: string
17
+ }
18
+
19
+ const DATA_EXPOSURE_PATTERNS: DataExposurePattern[] = [
20
+ // ============================================================================
21
+ // LOG SINKS (generally lower severity - server-side only)
22
+ // These are hygiene issues, not security vulnerabilities in most cases
23
+ // ============================================================================
24
+
25
+ // Logging sensitive variables - kept low/info
26
+ // NOTE: Generic "query" or "search" logging is NOT flagged - only actual sensitive data
27
+ {
28
+ name: 'Logging user ID',
29
+ pattern: /console\.(log|info|debug|warn|error)\s*\([^)]*\b(userId|user_id|user\.id)\b/gi,
30
+ sink: 'log',
31
+ severity: 'info',
32
+ description: 'User ID logged to console. Common debugging practice - verify logs are secured.',
33
+ suggestedFix: 'Use structured logging with appropriate access controls. Remove before production if not needed.',
34
+ },
35
+ {
36
+ name: 'Logging error objects',
37
+ pattern: /console\.(log|error|warn)\s*\(\s*(err|error|e)\s*\)/gi,
38
+ sink: 'log',
39
+ severity: 'info', // Downgraded from 'low' - this is standard practice
40
+ description: 'Error object logged to console. Standard debugging practice, but may expose stack traces in logs.',
41
+ suggestedFix: 'Consider logging only error.message in production. Use structured logging for better control.',
42
+ },
43
+ {
44
+ name: 'Logging request body',
45
+ pattern: /console\.(log|info|debug)\s*\([^)]*\b(req\.body|request\.body|body)\b/gi,
46
+ sink: 'log',
47
+ severity: 'low',
48
+ description: 'Request body logged to console. May contain sensitive user input.',
49
+ suggestedFix: 'Redact sensitive fields (passwords, tokens) before logging.',
50
+ },
51
+ {
52
+ name: 'JSON.stringify error to log',
53
+ pattern: /console\.(log|error|warn)\s*\([^)]*JSON\.stringify\s*\(\s*(err|error|e)\s*\)/gi,
54
+ sink: 'log',
55
+ severity: 'info',
56
+ description: 'Error object serialized to JSON for logging. May include stack traces.',
57
+ suggestedFix: 'Consider logging specific error properties instead of the full serialized object.',
58
+ },
59
+
60
+ // ============================================================================
61
+ // RESPONSE SINKS (higher severity - exposed to clients)
62
+ // These are actual information disclosure risks
63
+ // ============================================================================
64
+
65
+ // Error stack traces in responses - CRITICAL
66
+ {
67
+ name: 'Stack trace in response',
68
+ pattern: /res\.(json|send|status\(\d+\)\.json)\s*\([^)]*\.stack|NextResponse\.json\s*\([^)]*\.stack/gi,
69
+ sink: 'response',
70
+ severity: 'high',
71
+ description: 'Stack trace exposed in API response. Reveals internal code paths and file structure to clients.',
72
+ suggestedFix: 'Never return stack traces to clients. Log server-side, return generic error message.',
73
+ },
74
+ {
75
+ name: 'Full error object in response',
76
+ pattern: /res\.(json|send)\s*\(\s*(err|error|e)\s*\)|NextResponse\.json\s*\(\s*(err|error|e)\s*\)/gi,
77
+ sink: 'response',
78
+ severity: 'high',
79
+ description: 'Entire error object returned to client. May expose stack traces, internal paths, and sensitive details.',
80
+ suggestedFix: 'Return only { error: message } or a structured error response. Never return raw error objects.',
81
+ },
82
+ {
83
+ name: 'Error object spread in response',
84
+ pattern: /res\.(json|send)\s*\(\s*\{[^}]*\.\.\.\s*(err|error|e)[^}]*\}|NextResponse\.json\s*\(\s*\{[^}]*\.\.\.\s*(err|error|e)/gi,
85
+ sink: 'response',
86
+ severity: 'high',
87
+ description: 'Error object spread into response. May expose stack traces and internal details.',
88
+ suggestedFix: 'Pick only safe properties: { error: err.message, code: err.code }',
89
+ },
90
+ {
91
+ name: 'Detailed error in response',
92
+ pattern: /res\.(json|send|status\(\d+\)\.json)\s*\(\s*\{[^}]*(details|internal|debug|trace|stack)/gi,
93
+ sink: 'response',
94
+ severity: 'medium',
95
+ description: 'Detailed/internal error information in response may leak implementation details.',
96
+ suggestedFix: 'Remove detailed/internal/debug/trace information from client-facing error responses.',
97
+ },
98
+
99
+ // Error message in response - SAFE PATTERN (info only)
100
+ {
101
+ name: 'Error message in response (safe pattern)',
102
+ pattern: /res\.(json|send)\s*\([^)]*error:\s*(error|err|e)\.message|NextResponse\.json\s*\([^)]*error:\s*(error|err|e)\.message/gi,
103
+ sink: 'response',
104
+ severity: 'info',
105
+ description: 'Error message returned to client. Generally safe - this is the recommended error response pattern.',
106
+ suggestedFix: 'Verify error messages don\'t contain sensitive data. Consider using generic messages for auth errors.',
107
+ },
108
+ {
109
+ name: 'Error message with toString',
110
+ pattern: /res\.(json|send)\s*\([^)]*error:\s*(error|err|e)\.toString|NextResponse\.json\s*\([^)]*error:\s*(error|err|e)\.toString/gi,
111
+ sink: 'response',
112
+ severity: 'low',
113
+ description: 'Error.toString() returned to client. May include error name and message - verify no sensitive data.',
114
+ suggestedFix: 'Prefer error.message over error.toString() for cleaner output.',
115
+ },
116
+ {
117
+ name: 'String error message in response',
118
+ pattern: /res\.(json|send|status\(\d+\)\.json)\s*\(\s*\{\s*(error|message):\s*['"`][^'"`]+['"`]\s*\}/gi,
119
+ sink: 'response',
120
+ severity: 'info',
121
+ description: 'Static/string error message returned to client. This is the safest error response pattern.',
122
+ suggestedFix: 'No action needed - static error messages are safe.',
123
+ },
124
+
125
+ // ============================================================================
126
+ // BOTH SINKS (depends on context)
127
+ // ============================================================================
128
+
129
+ // Sensitive data exposure patterns
130
+ {
131
+ name: 'Exposing user object',
132
+ pattern: /res\.(json|send)\s*\([^)]*user\s*\)|NextResponse\.json\s*\([^)]*user\s*\)/gi,
133
+ sink: 'response',
134
+ severity: 'low',
135
+ description: 'User object returned in response. Ensure password hashes and sensitive fields are excluded.',
136
+ suggestedFix: 'Select only necessary user fields. Never expose password hashes, tokens, or internal IDs.',
137
+ },
138
+ ]
139
+
140
+ /**
141
+ * Check if file path indicates low-risk logging context
142
+ */
143
+ function isLowRiskLoggingFile(filePath: string): boolean {
144
+ // Test files
145
+ if (isTestOrMockFile(filePath)) {
146
+ return true
147
+ }
148
+
149
+ // Scripts, tools, CLI utilities
150
+ if (/\/(scripts?|tools?|cli|bin)\//i.test(filePath)) {
151
+ return true
152
+ }
153
+
154
+ // Internal services/utilities (not API-facing)
155
+ if (/\/(services?|lib|utils?|helpers?)\//i.test(filePath) &&
156
+ !/\/(api|routes?)\//i.test(filePath)) {
157
+ return true
158
+ }
159
+
160
+ // Component files (client-side, not API)
161
+ if (/\/(components?|pages?|views?)\//i.test(filePath) &&
162
+ !/route\.(ts|js)$/i.test(filePath)) {
163
+ return true
164
+ }
165
+
166
+ return false
167
+ }
168
+
169
+ /**
170
+ * Detect sensitive data exposure in logs and API responses
171
+ */
172
+ export function detectDataExposure(
173
+ content: string,
174
+ filePath: string
175
+ ): Vulnerability[] {
176
+ const vulnerabilities: Vulnerability[] = []
177
+ const lines = content.split('\n')
178
+ const isTestFile = isTestOrMockFile(filePath)
179
+ const isLowRiskFile = isLowRiskLoggingFile(filePath)
180
+
181
+ // Determine if this is likely an API route file
182
+ const isApiFile = /\/(api|routes?|handlers?|controllers?)\//i.test(filePath) ||
183
+ /route\.(ts|js)$/i.test(filePath)
184
+
185
+ // Track log findings for aggregation
186
+ const logFindings: { lineNumber: number; lineContent: string; name: string }[] = []
187
+
188
+ lines.forEach((line, index) => {
189
+ // Skip comments
190
+ if (isComment(line)) return
191
+
192
+ for (const pattern of DATA_EXPOSURE_PATTERNS) {
193
+ const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
194
+
195
+ if (regex.test(line)) {
196
+ let severity = pattern.severity
197
+ let description = pattern.description
198
+
199
+ // Adjust severity based on context
200
+ if (isTestFile) {
201
+ severity = 'info'
202
+ description = `${description} (in test file)`
203
+ }
204
+
205
+ // Log sinks get special handling for aggregation
206
+ if (pattern.sink === 'log') {
207
+ // In low-risk files, just aggregate without reporting individual findings
208
+ if (isLowRiskFile && severity === 'info') {
209
+ logFindings.push({ lineNumber: index + 1, lineContent: line.trim(), name: pattern.name })
210
+ break
211
+ }
212
+
213
+ // Log sinks in non-API files are lower priority
214
+ if (!isApiFile) {
215
+ if (severity === 'low') severity = 'info'
216
+ }
217
+
218
+ // Track for aggregation if info severity
219
+ if (severity === 'info') {
220
+ logFindings.push({ lineNumber: index + 1, lineContent: line.trim(), name: pattern.name })
221
+ break
222
+ }
223
+ }
224
+
225
+ // Response sinks in API files are higher priority
226
+ if (pattern.sink === 'response' && isApiFile) {
227
+ // Keep original severity - these are more critical in API routes
228
+ }
229
+
230
+ vulnerabilities.push({
231
+ id: `data-exposure-${filePath}-${index + 1}-${pattern.name}`,
232
+ filePath,
233
+ lineNumber: index + 1,
234
+ lineContent: line.trim(),
235
+ severity,
236
+ category: 'data_exposure',
237
+ title: pattern.name,
238
+ description,
239
+ suggestedFix: pattern.suggestedFix,
240
+ confidence: isTestFile ? 'low' : 'medium',
241
+ layer: 2,
242
+ })
243
+ break // Only one finding per line
244
+ }
245
+ }
246
+ })
247
+
248
+ // Aggregate info-level log findings if there are many
249
+ if (logFindings.length >= 3) {
250
+ const lineNumbers = logFindings.map(f => f.lineNumber).slice(0, 5)
251
+ const moreText = logFindings.length > 5 ? `... (${logFindings.length} total)` : ''
252
+
253
+ // Group by pattern name
254
+ const patternCounts = new Map<string, number>()
255
+ for (const finding of logFindings) {
256
+ patternCounts.set(finding.name, (patternCounts.get(finding.name) || 0) + 1)
257
+ }
258
+ const patternSummary = Array.from(patternCounts.entries())
259
+ .map(([name, count]) => `${count}x ${name}`)
260
+ .join(', ')
261
+
262
+ vulnerabilities.push({
263
+ id: `data-exposure-aggregated-${filePath}`,
264
+ filePath,
265
+ lineNumber: logFindings[0].lineNumber,
266
+ lineContent: `${logFindings.length} instances across this file`,
267
+ severity: 'info',
268
+ category: 'data_exposure',
269
+ title: `Logging patterns (${logFindings.length} instances)`,
270
+ description: `${patternSummary}. Review for sensitive data exposure.\n\nFound ${logFindings.length} occurrences at lines: ${lineNumbers.join(', ')}${moreText}`,
271
+ suggestedFix: 'Ensure logs have appropriate access controls and do not contain sensitive user data.',
272
+ confidence: 'low',
273
+ layer: 2,
274
+ })
275
+ } else if (logFindings.length > 0) {
276
+ // Report individually for small counts
277
+ for (const finding of logFindings) {
278
+ const pattern = DATA_EXPOSURE_PATTERNS.find(p => p.name === finding.name)
279
+ if (pattern) {
280
+ vulnerabilities.push({
281
+ id: `data-exposure-${filePath}-${finding.lineNumber}-${finding.name}`,
282
+ filePath,
283
+ lineNumber: finding.lineNumber,
284
+ lineContent: finding.lineContent,
285
+ severity: 'info',
286
+ category: 'data_exposure',
287
+ title: pattern.name,
288
+ description: pattern.description,
289
+ suggestedFix: pattern.suggestedFix,
290
+ confidence: 'low',
291
+ layer: 2,
292
+ })
293
+ }
294
+ }
295
+ }
296
+
297
+ return vulnerabilities
298
+ }
299
+
300
+ /**
301
+ * Check if error handling follows safe patterns
302
+ * Returns true if the error handling appears safe
303
+ */
304
+ export function isSafeErrorHandling(lineContent: string, surroundingLines: string[]): boolean {
305
+ // Safe patterns: only returning error.message, using generic messages
306
+ const safePatterns = [
307
+ /error:\s*['"`].*['"`]/, // Generic string message
308
+ /error:\s*error\.message/, // Only message property
309
+ /error:\s*err\.message/,
310
+ /message:\s*(error|err)\.message/,
311
+ /status\(\d+\)\.json\(\s*\{\s*error:/, // Status code + error object (common pattern)
312
+ ]
313
+
314
+ return safePatterns.some(p => p.test(lineContent))
315
+ }