@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,421 @@
1
+ /**
2
+ * Layer 2: AI Schema/Tooling Validation Detection
3
+ * Detects missing or weak validation of AI-generated structured outputs
4
+ *
5
+ * Covers:
6
+ * - M5.3: Schema/tooling mismatch
7
+ * - Unvalidated JSON parsing of AI outputs
8
+ * - Weak schema patterns (any, permissive)
9
+ * - Tool invocation parameters not validated
10
+ */
11
+
12
+ import type { Vulnerability, VulnerabilitySeverity } from '../types'
13
+ import {
14
+ isComment,
15
+ isTestOrMockFile,
16
+ isDocumentationFile,
17
+ isScannerOrFixtureFile,
18
+ } from '../utils/context-helpers'
19
+
20
+ // ============================================================================
21
+ // Context Detection
22
+ // ============================================================================
23
+
24
+ /**
25
+ * Check if file is in an AI/LLM context
26
+ */
27
+ function isAIContextFile(filePath: string, content: string): boolean {
28
+ // File path patterns
29
+ const aiPathPatterns = [
30
+ /\/(ai|llm|chat|openai|anthropic|agents?|tools?)\//i,
31
+ /(ai|llm|chat|agent|tool|function).*\.(ts|js|tsx|jsx|py)$/i,
32
+ ]
33
+
34
+ if (aiPathPatterns.some(p => p.test(filePath))) {
35
+ return true
36
+ }
37
+
38
+ // Content patterns
39
+ const aiContentPatterns = [
40
+ /openai\.|anthropic\.|ChatCompletion|MessageCreate/i,
41
+ /\.chat\.completions?\.create/i,
42
+ /\.messages\.create/i,
43
+ /generateText|streamText|generateObject/i,
44
+ /tool_calls?|function_call|functionCall/i,
45
+ /from\s+['"](?:openai|@anthropic-ai|langchain)/i,
46
+ ]
47
+
48
+ return aiContentPatterns.some(p => p.test(content))
49
+ }
50
+
51
+ /**
52
+ * Check if there's schema validation in the context
53
+ */
54
+ function hasSchemaValidation(context: string): boolean {
55
+ const validationPatterns = [
56
+ // Zod
57
+ /z\.(?:parse|safeParse|parseAsync)\s*\(/i,
58
+ /\.parse\s*\([^)]*response/i,
59
+ /zodSchema|z\.object/i,
60
+ // Ajv
61
+ /ajv\.(?:compile|validate)/i,
62
+ /\.validate\s*\([^)]*schema/i,
63
+ // Joi
64
+ /joi\.(?:object|string|number).*\.validate/i,
65
+ /\.validate\s*\([^)]*joi/i,
66
+ // Yup
67
+ /yup\.(?:object|string|number).*\.validate/i,
68
+ // TypeBox
69
+ /Type\.(?:Object|String|Number)/i,
70
+ /Value\.(?:Check|Decode)/i,
71
+ // Generic validation
72
+ /validateSchema|schemaValidator/i,
73
+ /JSON\.parse.*try.*catch.*schema/i,
74
+ // OpenAI Structured Outputs
75
+ /response_format.*json_schema/i,
76
+ /json_schema\s*:/i,
77
+ ]
78
+ return validationPatterns.some(p => p.test(context))
79
+ }
80
+
81
+ /**
82
+ * Check if there's a security-sensitive sink after parsing
83
+ */
84
+ function hasSecuritySink(context: string): boolean {
85
+ const sinkPatterns = [
86
+ // Execution sinks
87
+ /eval\s*\(/i,
88
+ /Function\s*\(/i,
89
+ /exec\s*\(|spawn\s*\(/i,
90
+ /child_process/i,
91
+ // Database sinks
92
+ /\.query\s*\(/i,
93
+ /\.execute\s*\(/i,
94
+ /\.raw\s*\(/i,
95
+ /\$\{.*\}.*(?:SELECT|INSERT|UPDATE|DELETE)/i,
96
+ // File system sinks
97
+ /fs\.(?:readFile|writeFile|unlink|mkdir)/i,
98
+ /readFileSync|writeFileSync/i,
99
+ // Network sinks
100
+ /fetch\s*\(|axios\.|request\s*\(/i,
101
+ // DOM sinks (if applicable)
102
+ /innerHTML|dangerouslySetInnerHTML/i,
103
+ ]
104
+ return sinkPatterns.some(p => p.test(context))
105
+ }
106
+
107
+ /**
108
+ * Check if there's try-catch around the parsing
109
+ */
110
+ function hasTryCatch(content: string, lineIndex: number): boolean {
111
+ const lines = content.split('\n')
112
+ const start = Math.max(0, lineIndex - 15)
113
+ const end = Math.min(lines.length, lineIndex + 5)
114
+ const context = lines.slice(start, end).join('\n')
115
+
116
+ // Simple check for try block wrapping
117
+ const tryPattern = /try\s*\{[^}]*$/
118
+ const beforeContext = lines.slice(start, lineIndex).join('\n')
119
+ return tryPattern.test(beforeContext)
120
+ }
121
+
122
+ /**
123
+ * Get surrounding context
124
+ */
125
+ function getSurroundingContext(content: string, lineIndex: number, windowSize: number = 20): string {
126
+ const lines = content.split('\n')
127
+ const start = Math.max(0, lineIndex - windowSize)
128
+ const end = Math.min(lines.length, lineIndex + windowSize)
129
+ return lines.slice(start, end).join('\n')
130
+ }
131
+
132
+ // ============================================================================
133
+ // Pattern Definitions
134
+ // ============================================================================
135
+
136
+ interface SchemaValidationPattern {
137
+ name: string
138
+ pattern: RegExp
139
+ riskType: 'unvalidated_parse' | 'weak_schema' | 'tool_params'
140
+ baseSeverity: VulnerabilitySeverity
141
+ description: string
142
+ suggestedFix: string
143
+ }
144
+
145
+ /**
146
+ * Unvalidated AI output parsing patterns
147
+ */
148
+ const UNVALIDATED_PARSING_PATTERNS: SchemaValidationPattern[] = [
149
+ // JSON.parse on AI response content
150
+ {
151
+ name: 'AI response content parsed without validation',
152
+ pattern: /JSON\.parse\s*\(\s*(?:response|result|completion|output|message)(?:\.\w+)*(?:\.content|\.text|\.message)?/gi,
153
+ riskType: 'unvalidated_parse',
154
+ baseSeverity: 'medium',
155
+ description: 'AI-generated JSON parsed without schema validation. Model may return malformed or malicious structures.',
156
+ suggestedFix: 'Validate with schema: const data = schema.parse(JSON.parse(response.content)) // using zod',
157
+ },
158
+ // OpenAI choices parsing
159
+ {
160
+ name: 'OpenAI response parsed directly',
161
+ pattern: /JSON\.parse\s*\(\s*(?:\w+\.)?choices\[\d+\]\.message\.content/gi,
162
+ riskType: 'unvalidated_parse',
163
+ baseSeverity: 'medium',
164
+ description: 'OpenAI response content parsed without validation. AI output structure should be verified.',
165
+ suggestedFix: 'Use OpenAI Structured Outputs (response_format: { type: "json_schema", json_schema: {...} }) or validate with zod.',
166
+ },
167
+ // Anthropic content parsing
168
+ {
169
+ name: 'Anthropic response parsed directly',
170
+ pattern: /JSON\.parse\s*\(\s*(?:\w+\.)?content\[0\]\.text/gi,
171
+ riskType: 'unvalidated_parse',
172
+ baseSeverity: 'medium',
173
+ description: 'Anthropic response parsed without validation. AI output may not match expected schema.',
174
+ suggestedFix: 'Validate response structure with zod or similar schema validation library.',
175
+ },
176
+ // Tool call arguments parsing
177
+ {
178
+ name: 'Tool call arguments parsed without validation',
179
+ pattern: /JSON\.parse\s*\(\s*(?:\w+\.)?(?:tool_calls?|function_call)(?:\[\d+\])?\.(?:function\.)?arguments/gi,
180
+ riskType: 'unvalidated_parse',
181
+ baseSeverity: 'medium',
182
+ description: 'Function calling arguments parsed without schema validation. Tool parameters should be verified.',
183
+ suggestedFix: 'Define and validate tool argument schema: const args = toolSchema.parse(JSON.parse(toolCall.function.arguments))',
184
+ },
185
+ // Generic AI output parsing
186
+ {
187
+ name: 'AI output assigned without parsing/validation',
188
+ pattern: /(?:const|let|var)\s+\w+\s*=\s*(?:response|completion|result)\.(?:content|text|data)\s*;?\s*(?!.*(?:parse|validate|schema))/gi,
189
+ riskType: 'unvalidated_parse',
190
+ baseSeverity: 'low',
191
+ description: 'AI output assigned directly to variable. If used as structured data, validate first.',
192
+ suggestedFix: 'If expecting JSON: const data = schema.safeParse(JSON.parse(response.content))',
193
+ },
194
+ ]
195
+
196
+ /**
197
+ * Weak schema patterns
198
+ */
199
+ const WEAK_SCHEMA_PATTERNS: SchemaValidationPattern[] = [
200
+ // any type for AI response
201
+ {
202
+ name: 'AI response typed as any',
203
+ pattern: /(?:response|aiResponse|completion|result)\s*:\s*any\b/gi,
204
+ riskType: 'weak_schema',
205
+ baseSeverity: 'low',
206
+ description: 'AI response typed as `any`. TypeScript cannot catch type errors at compile time.',
207
+ suggestedFix: 'Define explicit interface and use runtime validation: interface AIResponse { ... }; const data: AIResponse = schema.parse(response)',
208
+ },
209
+ // Permissive zod schemas
210
+ {
211
+ name: 'Permissive zod schema for AI output',
212
+ pattern: /z\.(?:any|unknown)\s*\(\s*\).*(?:response|output|completion)/gi,
213
+ riskType: 'weak_schema',
214
+ baseSeverity: 'low',
215
+ description: 'Using z.any() or z.unknown() defeats the purpose of schema validation.',
216
+ suggestedFix: 'Define specific schema: z.object({ field: z.string(), ... }).strict()',
217
+ },
218
+ // Passthrough schemas
219
+ {
220
+ name: 'Passthrough schema allowing extra properties',
221
+ pattern: /z\.object\s*\([^)]+\)\.passthrough\s*\(\s*\)/gi,
222
+ riskType: 'weak_schema',
223
+ baseSeverity: 'info',
224
+ description: 'Schema uses passthrough() allowing unexpected properties from AI output.',
225
+ suggestedFix: 'Use .strict() instead to reject unexpected properties from AI responses.',
226
+ },
227
+ // Record<string, any>
228
+ {
229
+ name: 'Record<string, any> for AI data',
230
+ pattern: /Record<string,\s*any>\s*.*(?:response|result|output|completion|aiData)/gi,
231
+ riskType: 'weak_schema',
232
+ baseSeverity: 'low',
233
+ description: 'Generic Record<string, any> type used for AI output provides no type safety.',
234
+ suggestedFix: 'Define specific interface or use zod schema for runtime validation.',
235
+ },
236
+ // Object type without specifics
237
+ {
238
+ name: 'Generic object type for AI response',
239
+ pattern: /(?:response|completion|aiOutput)\s*:\s*(?:object|Object|{})\b/gi,
240
+ riskType: 'weak_schema',
241
+ baseSeverity: 'info',
242
+ description: 'Generic object type for AI response. Consider defining specific interface.',
243
+ suggestedFix: 'Replace with specific interface that documents expected AI output structure.',
244
+ },
245
+ ]
246
+
247
+ /**
248
+ * Tool parameter validation issues
249
+ */
250
+ const TOOL_PARAM_PATTERNS: SchemaValidationPattern[] = [
251
+ // Tool parameters used directly in paths
252
+ {
253
+ name: 'Tool parameter used in file path',
254
+ pattern: /(?:tool|args|parameters)(?:\.\w+)*\.(?:path|filename|filepath|file).*(?:readFile|writeFile|fs\.|path\.)/gi,
255
+ riskType: 'tool_params',
256
+ baseSeverity: 'high',
257
+ description: 'AI-generated file path used without validation. Path traversal attack possible.',
258
+ suggestedFix: 'Validate path against allowlist: if (!allowedPaths.some(p => resolvedPath.startsWith(p))) throw new Error("Invalid path")',
259
+ },
260
+ // Tool parameters in commands
261
+ {
262
+ name: 'Tool parameter used in shell command',
263
+ pattern: /(?:tool|args|parameters)(?:\.\w+)*\.(?:command|cmd|script).*(?:exec|spawn|shell)/gi,
264
+ riskType: 'tool_params',
265
+ baseSeverity: 'critical',
266
+ description: 'AI-generated command executed without validation. Command injection possible.',
267
+ suggestedFix: 'Use allowlist for permitted commands. Never execute arbitrary AI-generated commands.',
268
+ },
269
+ // Tool parameters in URLs
270
+ {
271
+ name: 'Tool parameter used in URL',
272
+ pattern: /(?:tool|args|parameters)(?:\.\w+)*\.(?:url|endpoint|host).*(?:fetch|axios|request|http)/gi,
273
+ riskType: 'tool_params',
274
+ baseSeverity: 'high',
275
+ description: 'AI-generated URL used without validation. SSRF attack possible.',
276
+ suggestedFix: 'Validate URL against allowlist of permitted hosts before making request.',
277
+ },
278
+ // Tool parameters in database queries
279
+ {
280
+ name: 'Tool parameter used in database query',
281
+ pattern: /(?:tool|args|parameters)(?:\.\w+)*\.(?:query|sql|table|column).*(?:\.query|\.execute|\.raw)/gi,
282
+ riskType: 'tool_params',
283
+ baseSeverity: 'high',
284
+ description: 'AI-generated value used in database query. SQL injection possible.',
285
+ suggestedFix: 'Use parameterized queries. Validate table/column names against allowlist.',
286
+ },
287
+ // Function call name used in switch/if
288
+ {
289
+ name: 'Tool/function call routed without validation',
290
+ pattern: /(?:tool_call|function_call|functionCall)\.(?:name|function\.name).*(?:switch|if|\[.*\])/gi,
291
+ riskType: 'tool_params',
292
+ baseSeverity: 'medium',
293
+ description: 'AI-selected function name used for routing. Validate against allowed functions.',
294
+ suggestedFix: 'Check function name against allowlist: const allowedFunctions = ["search", "calculate"]; if (!allowedFunctions.includes(name)) throw',
295
+ },
296
+ ]
297
+
298
+ // ============================================================================
299
+ // Main Detection Function
300
+ // ============================================================================
301
+
302
+ /**
303
+ * Main detection function for AI schema/tooling validation issues
304
+ */
305
+ export function detectAISchemaValidation(
306
+ content: string,
307
+ filePath: string
308
+ ): Vulnerability[] {
309
+ const vulnerabilities: Vulnerability[] = []
310
+
311
+ // Skip non-applicable files
312
+ if (isScannerOrFixtureFile(filePath)) return vulnerabilities
313
+ if (isDocumentationFile(filePath)) return vulnerabilities
314
+
315
+ // Only scan files in AI context
316
+ if (!isAIContextFile(filePath, content)) {
317
+ return vulnerabilities
318
+ }
319
+
320
+ const lines = content.split('\n')
321
+ const isTestFile = isTestOrMockFile(filePath)
322
+
323
+ // Check file-level validation presence
324
+ const fileHasValidation = hasSchemaValidation(content)
325
+
326
+ // Process all pattern categories
327
+ const allPatterns: SchemaValidationPattern[] = [
328
+ ...UNVALIDATED_PARSING_PATTERNS,
329
+ ...WEAK_SCHEMA_PATTERNS,
330
+ ...TOOL_PARAM_PATTERNS,
331
+ ]
332
+
333
+ for (const pattern of allPatterns) {
334
+ const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
335
+ let match
336
+
337
+ while ((match = regex.exec(content)) !== null) {
338
+ const lineNumber = content.substring(0, match.index).split('\n').length
339
+ const lineContent = lines[lineNumber - 1]?.trim() || ''
340
+
341
+ // Skip comments
342
+ if (isComment(lineContent)) continue
343
+
344
+ // Get surrounding context
345
+ const context = getSurroundingContext(content, lineNumber - 1, 20)
346
+
347
+ // Check for local validation
348
+ const localHasValidation = hasSchemaValidation(context)
349
+ const localHasTryCatch = hasTryCatch(content, lineNumber - 1)
350
+ const localHasSecuritySink = hasSecuritySink(context)
351
+
352
+ // Calculate severity based on context
353
+ let severity = pattern.baseSeverity
354
+ const notes: string[] = []
355
+
356
+ // Apply context-aware adjustments
357
+ if (pattern.riskType === 'unvalidated_parse') {
358
+ if (localHasValidation || fileHasValidation) {
359
+ severity = 'info'
360
+ notes.push('Schema validation detected nearby')
361
+ } else if (localHasSecuritySink) {
362
+ // Elevate if used in security-sensitive context
363
+ if (severity === 'medium') severity = 'high'
364
+ notes.push('Used in security-sensitive context')
365
+ } else if (localHasTryCatch && !localHasSecuritySink) {
366
+ // Try-catch without sink is minor
367
+ severity = 'low'
368
+ notes.push('Has try-catch but no schema validation')
369
+ }
370
+ }
371
+
372
+ if (pattern.riskType === 'weak_schema') {
373
+ // Weak schemas at API boundaries are more concerning
374
+ if (/export|handler|route|api/i.test(context)) {
375
+ if (severity === 'low') severity = 'medium'
376
+ notes.push('At API boundary')
377
+ }
378
+ }
379
+
380
+ if (pattern.riskType === 'tool_params') {
381
+ // Already high/critical, check for mitigation
382
+ if (localHasValidation) {
383
+ severity = severity === 'critical' ? 'medium' : 'low'
384
+ notes.push('Validation detected')
385
+ }
386
+ }
387
+
388
+ // Downgrade test files
389
+ if (isTestFile) {
390
+ severity = 'info'
391
+ notes.push('in test file')
392
+ }
393
+
394
+ // Build final description
395
+ let description = pattern.description
396
+ if (notes.length > 0) {
397
+ description += ` (${notes.join('; ')})`
398
+ }
399
+
400
+ vulnerabilities.push({
401
+ id: `ai-schema-${filePath}-${lineNumber}-${pattern.name.replace(/\s+/g, '-')}`,
402
+ filePath,
403
+ lineNumber,
404
+ lineContent,
405
+ severity,
406
+ category: 'ai_schema_mismatch',
407
+ title: pattern.name,
408
+ description,
409
+ suggestedFix: pattern.suggestedFix,
410
+ confidence: severity === 'info' ? 'low' : 'medium',
411
+ layer: 2,
412
+ requiresAIValidation: true, // Tier B - always validate with AI
413
+ })
414
+ }
415
+ }
416
+
417
+ return vulnerabilities
418
+ }
419
+
420
+ // Export helpers for use in other modules
421
+ export { isAIContextFile, hasSchemaValidation }