@oculum/scanner 1.0.9 → 1.0.11

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 (365) hide show
  1. package/dist/baseline/diff.d.ts +32 -0
  2. package/dist/baseline/diff.d.ts.map +1 -0
  3. package/dist/baseline/diff.js +119 -0
  4. package/dist/baseline/diff.js.map +1 -0
  5. package/dist/baseline/index.d.ts +9 -0
  6. package/dist/baseline/index.d.ts.map +1 -0
  7. package/dist/baseline/index.js +19 -0
  8. package/dist/baseline/index.js.map +1 -0
  9. package/dist/baseline/manager.d.ts +67 -0
  10. package/dist/baseline/manager.d.ts.map +1 -0
  11. package/dist/baseline/manager.js +180 -0
  12. package/dist/baseline/manager.js.map +1 -0
  13. package/dist/baseline/types.d.ts +91 -0
  14. package/dist/baseline/types.d.ts.map +1 -0
  15. package/dist/baseline/types.js +12 -0
  16. package/dist/baseline/types.js.map +1 -0
  17. package/dist/formatters/cli-terminal.d.ts +38 -0
  18. package/dist/formatters/cli-terminal.d.ts.map +1 -1
  19. package/dist/formatters/cli-terminal.js +365 -42
  20. package/dist/formatters/cli-terminal.js.map +1 -1
  21. package/dist/formatters/github-comment.d.ts +1 -1
  22. package/dist/formatters/github-comment.d.ts.map +1 -1
  23. package/dist/formatters/github-comment.js +75 -11
  24. package/dist/formatters/github-comment.js.map +1 -1
  25. package/dist/formatters/index.d.ts +1 -1
  26. package/dist/formatters/index.d.ts.map +1 -1
  27. package/dist/formatters/index.js +4 -1
  28. package/dist/formatters/index.js.map +1 -1
  29. package/dist/index.d.ts +7 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +155 -16
  32. package/dist/index.js.map +1 -1
  33. package/dist/layer1/config-audit.d.ts.map +1 -1
  34. package/dist/layer1/config-audit.js +20 -3
  35. package/dist/layer1/config-audit.js.map +1 -1
  36. package/dist/layer1/config-mcp-audit.d.ts +20 -0
  37. package/dist/layer1/config-mcp-audit.d.ts.map +1 -0
  38. package/dist/layer1/config-mcp-audit.js +239 -0
  39. package/dist/layer1/config-mcp-audit.js.map +1 -0
  40. package/dist/layer1/index.d.ts +1 -0
  41. package/dist/layer1/index.d.ts.map +1 -1
  42. package/dist/layer1/index.js +9 -1
  43. package/dist/layer1/index.js.map +1 -1
  44. package/dist/layer2/ai-agent-tools.d.ts.map +1 -1
  45. package/dist/layer2/ai-agent-tools.js +303 -0
  46. package/dist/layer2/ai-agent-tools.js.map +1 -1
  47. package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -1
  48. package/dist/layer2/ai-endpoint-protection.js +17 -3
  49. package/dist/layer2/ai-endpoint-protection.js.map +1 -1
  50. package/dist/layer2/ai-execution-sinks.d.ts.map +1 -1
  51. package/dist/layer2/ai-execution-sinks.js +462 -12
  52. package/dist/layer2/ai-execution-sinks.js.map +1 -1
  53. package/dist/layer2/ai-fingerprinting.d.ts.map +1 -1
  54. package/dist/layer2/ai-fingerprinting.js +3 -0
  55. package/dist/layer2/ai-fingerprinting.js.map +1 -1
  56. package/dist/layer2/ai-mcp-security.d.ts +17 -0
  57. package/dist/layer2/ai-mcp-security.d.ts.map +1 -0
  58. package/dist/layer2/ai-mcp-security.js +679 -0
  59. package/dist/layer2/ai-mcp-security.js.map +1 -0
  60. package/dist/layer2/ai-package-hallucination.d.ts +19 -0
  61. package/dist/layer2/ai-package-hallucination.d.ts.map +1 -0
  62. package/dist/layer2/ai-package-hallucination.js +696 -0
  63. package/dist/layer2/ai-package-hallucination.js.map +1 -0
  64. package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -1
  65. package/dist/layer2/ai-prompt-hygiene.js +495 -9
  66. package/dist/layer2/ai-prompt-hygiene.js.map +1 -1
  67. package/dist/layer2/ai-rag-safety.d.ts.map +1 -1
  68. package/dist/layer2/ai-rag-safety.js +372 -1
  69. package/dist/layer2/ai-rag-safety.js.map +1 -1
  70. package/dist/layer2/auth-antipatterns.d.ts.map +1 -1
  71. package/dist/layer2/auth-antipatterns.js +4 -0
  72. package/dist/layer2/auth-antipatterns.js.map +1 -1
  73. package/dist/layer2/byok-patterns.d.ts.map +1 -1
  74. package/dist/layer2/byok-patterns.js +3 -0
  75. package/dist/layer2/byok-patterns.js.map +1 -1
  76. package/dist/layer2/dangerous-functions/child-process.d.ts +16 -0
  77. package/dist/layer2/dangerous-functions/child-process.d.ts.map +1 -0
  78. package/dist/layer2/dangerous-functions/child-process.js +74 -0
  79. package/dist/layer2/dangerous-functions/child-process.js.map +1 -0
  80. package/dist/layer2/dangerous-functions/dom-xss.d.ts +29 -0
  81. package/dist/layer2/dangerous-functions/dom-xss.d.ts.map +1 -0
  82. package/dist/layer2/dangerous-functions/dom-xss.js +179 -0
  83. package/dist/layer2/dangerous-functions/dom-xss.js.map +1 -0
  84. package/dist/layer2/dangerous-functions/index.d.ts +13 -0
  85. package/dist/layer2/dangerous-functions/index.d.ts.map +1 -0
  86. package/dist/layer2/dangerous-functions/index.js +621 -0
  87. package/dist/layer2/dangerous-functions/index.js.map +1 -0
  88. package/dist/layer2/dangerous-functions/json-parse.d.ts +31 -0
  89. package/dist/layer2/dangerous-functions/json-parse.d.ts.map +1 -0
  90. package/dist/layer2/dangerous-functions/json-parse.js +319 -0
  91. package/dist/layer2/dangerous-functions/json-parse.js.map +1 -0
  92. package/dist/layer2/dangerous-functions/math-random.d.ts +61 -0
  93. package/dist/layer2/dangerous-functions/math-random.d.ts.map +1 -0
  94. package/dist/layer2/dangerous-functions/math-random.js +459 -0
  95. package/dist/layer2/dangerous-functions/math-random.js.map +1 -0
  96. package/dist/layer2/dangerous-functions/patterns.d.ts +21 -0
  97. package/dist/layer2/dangerous-functions/patterns.d.ts.map +1 -0
  98. package/dist/layer2/dangerous-functions/patterns.js +161 -0
  99. package/dist/layer2/dangerous-functions/patterns.js.map +1 -0
  100. package/dist/layer2/dangerous-functions/request-validation.d.ts +13 -0
  101. package/dist/layer2/dangerous-functions/request-validation.d.ts.map +1 -0
  102. package/dist/layer2/dangerous-functions/request-validation.js +119 -0
  103. package/dist/layer2/dangerous-functions/request-validation.js.map +1 -0
  104. package/dist/layer2/dangerous-functions/utils/control-flow.d.ts +23 -0
  105. package/dist/layer2/dangerous-functions/utils/control-flow.d.ts.map +1 -0
  106. package/dist/layer2/dangerous-functions/utils/control-flow.js +149 -0
  107. package/dist/layer2/dangerous-functions/utils/control-flow.js.map +1 -0
  108. package/dist/layer2/dangerous-functions/utils/helpers.d.ts +31 -0
  109. package/dist/layer2/dangerous-functions/utils/helpers.d.ts.map +1 -0
  110. package/dist/layer2/dangerous-functions/utils/helpers.js +124 -0
  111. package/dist/layer2/dangerous-functions/utils/helpers.js.map +1 -0
  112. package/dist/layer2/dangerous-functions/utils/index.d.ts +9 -0
  113. package/dist/layer2/dangerous-functions/utils/index.d.ts.map +1 -0
  114. package/dist/layer2/dangerous-functions/utils/index.js +23 -0
  115. package/dist/layer2/dangerous-functions/utils/index.js.map +1 -0
  116. package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts +22 -0
  117. package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts.map +1 -0
  118. package/dist/layer2/dangerous-functions/utils/schema-validation.js +89 -0
  119. package/dist/layer2/dangerous-functions/utils/schema-validation.js.map +1 -0
  120. package/dist/layer2/data-exposure.d.ts.map +1 -1
  121. package/dist/layer2/data-exposure.js +3 -0
  122. package/dist/layer2/data-exposure.js.map +1 -1
  123. package/dist/layer2/framework-checks.d.ts.map +1 -1
  124. package/dist/layer2/framework-checks.js +3 -0
  125. package/dist/layer2/framework-checks.js.map +1 -1
  126. package/dist/layer2/index.d.ts +3 -0
  127. package/dist/layer2/index.d.ts.map +1 -1
  128. package/dist/layer2/index.js +61 -2
  129. package/dist/layer2/index.js.map +1 -1
  130. package/dist/layer2/logic-gates.d.ts.map +1 -1
  131. package/dist/layer2/logic-gates.js +4 -0
  132. package/dist/layer2/logic-gates.js.map +1 -1
  133. package/dist/layer2/model-supply-chain.d.ts +20 -0
  134. package/dist/layer2/model-supply-chain.d.ts.map +1 -0
  135. package/dist/layer2/model-supply-chain.js +376 -0
  136. package/dist/layer2/model-supply-chain.js.map +1 -0
  137. package/dist/layer2/risky-imports.d.ts.map +1 -1
  138. package/dist/layer2/risky-imports.js +4 -0
  139. package/dist/layer2/risky-imports.js.map +1 -1
  140. package/dist/layer2/variables.d.ts.map +1 -1
  141. package/dist/layer2/variables.js +4 -0
  142. package/dist/layer2/variables.js.map +1 -1
  143. package/dist/layer3/anthropic/auto-dismiss.d.ts +24 -0
  144. package/dist/layer3/anthropic/auto-dismiss.d.ts.map +1 -0
  145. package/dist/layer3/anthropic/auto-dismiss.js +188 -0
  146. package/dist/layer3/anthropic/auto-dismiss.js.map +1 -0
  147. package/dist/layer3/anthropic/clients.d.ts +44 -0
  148. package/dist/layer3/anthropic/clients.d.ts.map +1 -0
  149. package/dist/layer3/anthropic/clients.js +81 -0
  150. package/dist/layer3/anthropic/clients.js.map +1 -0
  151. package/dist/layer3/anthropic/index.d.ts +41 -0
  152. package/dist/layer3/anthropic/index.d.ts.map +1 -0
  153. package/dist/layer3/anthropic/index.js +141 -0
  154. package/dist/layer3/anthropic/index.js.map +1 -0
  155. package/dist/layer3/anthropic/prompts/index.d.ts +8 -0
  156. package/dist/layer3/anthropic/prompts/index.d.ts.map +1 -0
  157. package/dist/layer3/anthropic/prompts/index.js +14 -0
  158. package/dist/layer3/anthropic/prompts/index.js.map +1 -0
  159. package/dist/layer3/anthropic/prompts/semantic-analysis.d.ts +15 -0
  160. package/dist/layer3/anthropic/prompts/semantic-analysis.d.ts.map +1 -0
  161. package/dist/layer3/anthropic/prompts/semantic-analysis.js +169 -0
  162. package/dist/layer3/anthropic/prompts/semantic-analysis.js.map +1 -0
  163. package/dist/layer3/anthropic/prompts/validation.d.ts +12 -0
  164. package/dist/layer3/anthropic/prompts/validation.d.ts.map +1 -0
  165. package/dist/layer3/anthropic/prompts/validation.js +421 -0
  166. package/dist/layer3/anthropic/prompts/validation.js.map +1 -0
  167. package/dist/layer3/anthropic/providers/anthropic.d.ts +21 -0
  168. package/dist/layer3/anthropic/providers/anthropic.d.ts.map +1 -0
  169. package/dist/layer3/anthropic/providers/anthropic.js +266 -0
  170. package/dist/layer3/anthropic/providers/anthropic.js.map +1 -0
  171. package/dist/layer3/anthropic/providers/index.d.ts +8 -0
  172. package/dist/layer3/anthropic/providers/index.d.ts.map +1 -0
  173. package/dist/layer3/anthropic/providers/index.js +15 -0
  174. package/dist/layer3/anthropic/providers/index.js.map +1 -0
  175. package/dist/layer3/anthropic/providers/openai.d.ts +18 -0
  176. package/dist/layer3/anthropic/providers/openai.d.ts.map +1 -0
  177. package/dist/layer3/anthropic/providers/openai.js +340 -0
  178. package/dist/layer3/anthropic/providers/openai.js.map +1 -0
  179. package/dist/layer3/anthropic/request-builder.d.ts +20 -0
  180. package/dist/layer3/anthropic/request-builder.d.ts.map +1 -0
  181. package/dist/layer3/anthropic/request-builder.js +134 -0
  182. package/dist/layer3/anthropic/request-builder.js.map +1 -0
  183. package/dist/layer3/anthropic/types.d.ts +88 -0
  184. package/dist/layer3/anthropic/types.d.ts.map +1 -0
  185. package/dist/layer3/anthropic/types.js +38 -0
  186. package/dist/layer3/anthropic/types.js.map +1 -0
  187. package/dist/layer3/anthropic/utils/index.d.ts +9 -0
  188. package/dist/layer3/anthropic/utils/index.d.ts.map +1 -0
  189. package/dist/layer3/anthropic/utils/index.js +24 -0
  190. package/dist/layer3/anthropic/utils/index.js.map +1 -0
  191. package/dist/layer3/anthropic/utils/path-helpers.d.ts +21 -0
  192. package/dist/layer3/anthropic/utils/path-helpers.d.ts.map +1 -0
  193. package/dist/layer3/anthropic/utils/path-helpers.js +69 -0
  194. package/dist/layer3/anthropic/utils/path-helpers.js.map +1 -0
  195. package/dist/layer3/anthropic/utils/response-parser.d.ts +40 -0
  196. package/dist/layer3/anthropic/utils/response-parser.d.ts.map +1 -0
  197. package/dist/layer3/anthropic/utils/response-parser.js +285 -0
  198. package/dist/layer3/anthropic/utils/response-parser.js.map +1 -0
  199. package/dist/layer3/anthropic/utils/retry.d.ts +15 -0
  200. package/dist/layer3/anthropic/utils/retry.d.ts.map +1 -0
  201. package/dist/layer3/anthropic/utils/retry.js +62 -0
  202. package/dist/layer3/anthropic/utils/retry.js.map +1 -0
  203. package/dist/layer3/index.d.ts +1 -0
  204. package/dist/layer3/index.d.ts.map +1 -1
  205. package/dist/layer3/index.js +16 -6
  206. package/dist/layer3/index.js.map +1 -1
  207. package/dist/layer3/osv-check.d.ts +75 -0
  208. package/dist/layer3/osv-check.d.ts.map +1 -0
  209. package/dist/layer3/osv-check.js +308 -0
  210. package/dist/layer3/osv-check.js.map +1 -0
  211. package/dist/rules/framework-fixes.d.ts +48 -0
  212. package/dist/rules/framework-fixes.d.ts.map +1 -0
  213. package/dist/rules/framework-fixes.js +439 -0
  214. package/dist/rules/framework-fixes.js.map +1 -0
  215. package/dist/rules/index.d.ts +8 -0
  216. package/dist/rules/index.d.ts.map +1 -0
  217. package/dist/rules/index.js +18 -0
  218. package/dist/rules/index.js.map +1 -0
  219. package/dist/rules/metadata.d.ts +43 -0
  220. package/dist/rules/metadata.d.ts.map +1 -0
  221. package/dist/rules/metadata.js +734 -0
  222. package/dist/rules/metadata.js.map +1 -0
  223. package/dist/suppression/config-loader.d.ts +74 -0
  224. package/dist/suppression/config-loader.d.ts.map +1 -0
  225. package/dist/suppression/config-loader.js +424 -0
  226. package/dist/suppression/config-loader.js.map +1 -0
  227. package/dist/suppression/hash.d.ts +48 -0
  228. package/dist/suppression/hash.d.ts.map +1 -0
  229. package/dist/suppression/hash.js +88 -0
  230. package/dist/suppression/hash.js.map +1 -0
  231. package/dist/suppression/index.d.ts +11 -0
  232. package/dist/suppression/index.d.ts.map +1 -0
  233. package/dist/suppression/index.js +39 -0
  234. package/dist/suppression/index.js.map +1 -0
  235. package/dist/suppression/inline-parser.d.ts +39 -0
  236. package/dist/suppression/inline-parser.d.ts.map +1 -0
  237. package/dist/suppression/inline-parser.js +218 -0
  238. package/dist/suppression/inline-parser.js.map +1 -0
  239. package/dist/suppression/manager.d.ts +94 -0
  240. package/dist/suppression/manager.d.ts.map +1 -0
  241. package/dist/suppression/manager.js +292 -0
  242. package/dist/suppression/manager.js.map +1 -0
  243. package/dist/suppression/types.d.ts +151 -0
  244. package/dist/suppression/types.d.ts.map +1 -0
  245. package/dist/suppression/types.js +28 -0
  246. package/dist/suppression/types.js.map +1 -0
  247. package/dist/tiers.d.ts +1 -1
  248. package/dist/tiers.d.ts.map +1 -1
  249. package/dist/tiers.js +27 -0
  250. package/dist/tiers.js.map +1 -1
  251. package/dist/types.d.ts +62 -1
  252. package/dist/types.d.ts.map +1 -1
  253. package/dist/types.js.map +1 -1
  254. package/dist/utils/context-helpers.d.ts +4 -0
  255. package/dist/utils/context-helpers.d.ts.map +1 -1
  256. package/dist/utils/context-helpers.js +13 -9
  257. package/dist/utils/context-helpers.js.map +1 -1
  258. package/package.json +4 -2
  259. package/src/__tests__/benchmark/fixtures/layer1/mcp-config-audit.json +31 -0
  260. package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +1489 -82
  261. package/src/__tests__/benchmark/fixtures/layer2/ai-mcp-security.ts +495 -0
  262. package/src/__tests__/benchmark/fixtures/layer2/ai-package-hallucination.ts +255 -0
  263. package/src/__tests__/benchmark/fixtures/layer2/ai-prompt-hygiene.ts +300 -1
  264. package/src/__tests__/benchmark/fixtures/layer2/ai-rag-safety.ts +139 -0
  265. package/src/__tests__/benchmark/fixtures/layer2/byok-patterns.ts +7 -0
  266. package/src/__tests__/benchmark/fixtures/layer2/data-exposure.ts +63 -0
  267. package/src/__tests__/benchmark/fixtures/layer2/excessive-agency.ts +221 -0
  268. package/src/__tests__/benchmark/fixtures/layer2/index.ts +18 -0
  269. package/src/__tests__/benchmark/fixtures/layer2/model-supply-chain.ts +204 -0
  270. package/src/__tests__/benchmark/fixtures/layer2/phase1-enhancements.ts +157 -0
  271. package/src/__tests__/snapshots/__snapshots__/anthropic-validation-refactor.test.ts.snap +758 -0
  272. package/src/__tests__/snapshots/__snapshots__/dangerous-functions-refactor.test.ts.snap +503 -0
  273. package/src/__tests__/snapshots/anthropic-validation-refactor.test.ts +321 -0
  274. package/src/__tests__/snapshots/dangerous-functions-refactor.test.ts +439 -0
  275. package/src/baseline/__tests__/diff.test.ts +261 -0
  276. package/src/baseline/__tests__/manager.test.ts +225 -0
  277. package/src/baseline/diff.ts +135 -0
  278. package/src/baseline/index.ts +29 -0
  279. package/src/baseline/manager.ts +230 -0
  280. package/src/baseline/types.ts +97 -0
  281. package/src/formatters/cli-terminal.ts +444 -41
  282. package/src/formatters/github-comment.ts +79 -11
  283. package/src/formatters/index.ts +4 -0
  284. package/src/index.ts +197 -14
  285. package/src/layer1/config-audit.ts +24 -3
  286. package/src/layer1/config-mcp-audit.ts +276 -0
  287. package/src/layer1/index.ts +16 -6
  288. package/src/layer2/ai-agent-tools.ts +336 -0
  289. package/src/layer2/ai-endpoint-protection.ts +16 -3
  290. package/src/layer2/ai-execution-sinks.ts +516 -12
  291. package/src/layer2/ai-fingerprinting.ts +5 -1
  292. package/src/layer2/ai-mcp-security.ts +730 -0
  293. package/src/layer2/ai-package-hallucination.ts +791 -0
  294. package/src/layer2/ai-prompt-hygiene.ts +547 -9
  295. package/src/layer2/ai-rag-safety.ts +382 -3
  296. package/src/layer2/auth-antipatterns.ts +5 -0
  297. package/src/layer2/byok-patterns.ts +5 -1
  298. package/src/layer2/dangerous-functions/child-process.ts +98 -0
  299. package/src/layer2/dangerous-functions/dom-xss.ts +220 -0
  300. package/src/layer2/dangerous-functions/index.ts +949 -0
  301. package/src/layer2/dangerous-functions/json-parse.ts +385 -0
  302. package/src/layer2/dangerous-functions/math-random.ts +537 -0
  303. package/src/layer2/dangerous-functions/patterns.ts +174 -0
  304. package/src/layer2/dangerous-functions/request-validation.ts +145 -0
  305. package/src/layer2/dangerous-functions/utils/control-flow.ts +162 -0
  306. package/src/layer2/dangerous-functions/utils/helpers.ts +170 -0
  307. package/src/layer2/dangerous-functions/utils/index.ts +25 -0
  308. package/src/layer2/dangerous-functions/utils/schema-validation.ts +91 -0
  309. package/src/layer2/data-exposure.ts +5 -1
  310. package/src/layer2/framework-checks.ts +5 -0
  311. package/src/layer2/index.ts +63 -1
  312. package/src/layer2/logic-gates.ts +5 -0
  313. package/src/layer2/model-supply-chain.ts +456 -0
  314. package/src/layer2/risky-imports.ts +5 -0
  315. package/src/layer2/variables.ts +5 -0
  316. package/src/layer3/__tests__/osv-check.test.ts +384 -0
  317. package/src/layer3/anthropic/auto-dismiss.ts +212 -0
  318. package/src/layer3/anthropic/clients.ts +84 -0
  319. package/src/layer3/anthropic/index.ts +170 -0
  320. package/src/layer3/anthropic/prompts/index.ts +14 -0
  321. package/src/layer3/anthropic/prompts/semantic-analysis.ts +173 -0
  322. package/src/layer3/anthropic/prompts/validation.ts +419 -0
  323. package/src/layer3/anthropic/providers/anthropic.ts +310 -0
  324. package/src/layer3/anthropic/providers/index.ts +8 -0
  325. package/src/layer3/anthropic/providers/openai.ts +384 -0
  326. package/src/layer3/anthropic/request-builder.ts +150 -0
  327. package/src/layer3/anthropic/types.ts +148 -0
  328. package/src/layer3/anthropic/utils/index.ts +26 -0
  329. package/src/layer3/anthropic/utils/path-helpers.ts +68 -0
  330. package/src/layer3/anthropic/utils/response-parser.ts +322 -0
  331. package/src/layer3/anthropic/utils/retry.ts +75 -0
  332. package/src/layer3/index.ts +18 -5
  333. package/src/layer3/osv-check.ts +420 -0
  334. package/src/rules/__tests__/framework-fixes.test.ts +689 -0
  335. package/src/rules/__tests__/metadata.test.ts +218 -0
  336. package/src/rules/framework-fixes.ts +470 -0
  337. package/src/rules/index.ts +21 -0
  338. package/src/rules/metadata.ts +831 -0
  339. package/src/suppression/__tests__/config-loader.test.ts +382 -0
  340. package/src/suppression/__tests__/hash.test.ts +166 -0
  341. package/src/suppression/__tests__/inline-parser.test.ts +212 -0
  342. package/src/suppression/__tests__/manager.test.ts +415 -0
  343. package/src/suppression/config-loader.ts +462 -0
  344. package/src/suppression/hash.ts +95 -0
  345. package/src/suppression/index.ts +51 -0
  346. package/src/suppression/inline-parser.ts +273 -0
  347. package/src/suppression/manager.ts +379 -0
  348. package/src/suppression/types.ts +174 -0
  349. package/src/tiers.ts +36 -0
  350. package/src/types.ts +90 -0
  351. package/src/utils/context-helpers.ts +13 -9
  352. package/dist/layer2/dangerous-functions.d.ts +0 -7
  353. package/dist/layer2/dangerous-functions.d.ts.map +0 -1
  354. package/dist/layer2/dangerous-functions.js +0 -1701
  355. package/dist/layer2/dangerous-functions.js.map +0 -1
  356. package/dist/layer3/anthropic.d.ts +0 -87
  357. package/dist/layer3/anthropic.d.ts.map +0 -1
  358. package/dist/layer3/anthropic.js +0 -1948
  359. package/dist/layer3/anthropic.js.map +0 -1
  360. package/dist/layer3/openai.d.ts +0 -25
  361. package/dist/layer3/openai.d.ts.map +0 -1
  362. package/dist/layer3/openai.js +0 -238
  363. package/dist/layer3/openai.js.map +0 -1
  364. package/src/layer2/dangerous-functions.ts +0 -1940
  365. package/src/layer3/anthropic.ts +0 -2257
@@ -0,0 +1,730 @@
1
+ /**
2
+ * Layer 2: MCP (Model Context Protocol) Security Detection
3
+ * Detects security issues in MCP tool implementations
4
+ *
5
+ * Background: MCP enables AI agents to call external tools. Security risks include:
6
+ * - Tool Poisoning: External content returned without validation (CVE-2025-6514)
7
+ * - Credential Issues: Credentials in tool parameters/responses
8
+ * - Confused Deputy: Operations without proper user context
9
+ *
10
+ * Reference: https://modelcontextprotocol.io, 13,000+ MCP servers deployed
11
+ */
12
+
13
+ import type { Vulnerability, VulnerabilitySeverity, VulnerabilityCategory } from '../types'
14
+ import {
15
+ isComment,
16
+ isTestOrMockFile,
17
+ isDocumentationFile,
18
+ isScannerOrFixtureFile,
19
+ isExampleDirectory,
20
+ isLibraryCode,
21
+ } from '../utils/context-helpers'
22
+
23
+ // ============================================================================
24
+ // Context Detection
25
+ // ============================================================================
26
+
27
+ /**
28
+ * Check if file is an MCP server/tool file based on imports and patterns
29
+ */
30
+ function isMCPFile(content: string, filePath: string): boolean {
31
+ // Import patterns for MCP SDK
32
+ const mcpImportPatterns = [
33
+ /@modelcontextprotocol\/sdk/i,
34
+ /from\s+['"]mcp['"]/i,
35
+ /from\s+['"]@mcp\//i,
36
+ /McpServer/i,
37
+ /mcp\.server/i,
38
+ /server\.tool\s*\(/i,
39
+ /@server\.tool/i,
40
+ ]
41
+
42
+ if (mcpImportPatterns.some(p => p.test(content))) {
43
+ return true
44
+ }
45
+
46
+ // Path patterns
47
+ const mcpPathPatterns = [
48
+ /\/mcp\//i,
49
+ /mcp[-_]?server/i,
50
+ /mcp[-_]?tools?/i,
51
+ ]
52
+
53
+ return mcpPathPatterns.some(p => p.test(filePath))
54
+ }
55
+
56
+ /**
57
+ * Check if line/context has content sanitization
58
+ */
59
+ function hasContentSanitization(context: string): boolean {
60
+ const sanitizationPatterns = [
61
+ /sanitize|DOMPurify|purify/i,
62
+ /escapeHtml|escape_html|html\.escape/i,
63
+ /strip(?:Tags|Html|Scripts)/i,
64
+ /validate(?:Content|Input|Schema)/i,
65
+ /zod\.parse|schema\.parse|safeParse/i,
66
+ /filterHtml|cleanHtml/i,
67
+ /ALLOWED_TAGS/i,
68
+ // Safe return patterns - returning only safe fields
69
+ /\.map\s*\([^)]*\{\s*id|title|name|summary\s*:/i,
70
+ // Static content patterns
71
+ /loadStaticDocs|staticContent|publicData/i,
72
+ // Pure computation
73
+ /mathjs\.evaluate|calculate/i,
74
+ ]
75
+ return sanitizationPatterns.some(p => p.test(context))
76
+ }
77
+
78
+ /**
79
+ * Check if the return is for a safe/static data source
80
+ */
81
+ function isSafeDataSource(context: string): boolean {
82
+ const safePatterns = [
83
+ // Static/public data
84
+ /(?:static|public)(?:Data|Docs|Content)/i,
85
+ // Mathematical operations
86
+ /mathjs|calculate|compute/i,
87
+ // Internal API with server-side auth
88
+ /process\.env\.INTERNAL|SERVER_SIDE/i,
89
+ // User's own data explicitly
90
+ /findByUser|getByUser|user\.(?:files|documents|records)/i,
91
+ // Returns only safe fields like id, name, title
92
+ /return\s*\{[^}]*:\s*\{[^}]*(?:only|safe|id|title|name)[^}]*\}/i,
93
+ ]
94
+ return safePatterns.some(p => p.test(context))
95
+ }
96
+
97
+ /**
98
+ * Check if tool has user context access
99
+ */
100
+ function hasUserContext(context: string): boolean {
101
+ const userContextPatterns = [
102
+ /context\.user/i,
103
+ /context\.userId/i,
104
+ /context\.session/i,
105
+ /context\.auth/i,
106
+ /getCurrentUser/i,
107
+ /request\.user/i,
108
+ /req\.user/i,
109
+ /user\.id/i,
110
+ /userId/i,
111
+ /session\.user/i,
112
+ /auth\(\)/i,
113
+ /tenantId/i,
114
+ /tenant\.id/i,
115
+ /orgId/i,
116
+ ]
117
+ return userContextPatterns.some(p => p.test(context))
118
+ }
119
+
120
+ /**
121
+ * Check if there's an authorization check in context
122
+ */
123
+ function hasAuthorizationCheck(context: string): boolean {
124
+ const authCheckPatterns = [
125
+ /if\s*\([^)]*\.ownerId\s*[!=]==?\s*/i,
126
+ /if\s*\([^)]*userId\s*[!=]==?\s*/i,
127
+ /if\s*\([^)]*tenantId\s*[!=]==?\s*/i,
128
+ /Not\s*authorized/i,
129
+ /Forbidden/i,
130
+ /checkPermission/i,
131
+ /checkAccess/i,
132
+ /canAccess/i,
133
+ /hasPermission/i,
134
+ /isAuthorized/i,
135
+ /throw.*Error.*auth/i,
136
+ ]
137
+ return authCheckPatterns.some(p => p.test(context))
138
+ }
139
+
140
+ /**
141
+ * Get surrounding context for analysis
142
+ */
143
+ function getSurroundingContext(content: string, lineIndex: number, windowSize: number = 30): string {
144
+ const lines = content.split('\n')
145
+ const start = Math.max(0, lineIndex - windowSize)
146
+ const end = Math.min(lines.length, lineIndex + windowSize)
147
+ return lines.slice(start, end).join('\n')
148
+ }
149
+
150
+ // ============================================================================
151
+ // Pattern Definitions
152
+ // ============================================================================
153
+
154
+ interface MCPSecurityPattern {
155
+ name: string
156
+ pattern: RegExp
157
+ category: 'tool_poisoning' | 'credential_issue' | 'confused_deputy' | 'description_injection' | 'server_shadowing'
158
+ baseSeverity: VulnerabilitySeverity
159
+ description: string
160
+ suggestedFix: string
161
+ }
162
+
163
+ /**
164
+ * Tool Poisoning Patterns
165
+ * Detect tools that return external content without validation
166
+ */
167
+ const TOOL_POISONING_PATTERNS: MCPSecurityPattern[] = [
168
+ // Raw HTTP response content (JS and Python)
169
+ {
170
+ name: 'Raw HTTP response in tool',
171
+ pattern: /(?:return|=>)\s*[{(]\s*[{"]?[^}]*(?:content|body|text|html)['"]\s*[:=]\s*(?:await\s+)?(?:response|res)\.(?:text|json|body)/gi,
172
+ category: 'tool_poisoning',
173
+ baseSeverity: 'high',
174
+ description: 'MCP tool returns raw HTTP response content without sanitization. External content could contain prompt injection payloads.',
175
+ suggestedFix: 'Sanitize external content before returning: return { content: sanitize(response.text()) }',
176
+ },
177
+ // Raw fetch result
178
+ {
179
+ name: 'Fetch result returned directly',
180
+ pattern: /return\s*[{(]\s*[{"]?[^}]*[:=]\s*await\s+fetch\([^)]+\)\.(?:text|json)\(\)/gi,
181
+ category: 'tool_poisoning',
182
+ baseSeverity: 'high',
183
+ description: 'Fetch result returned directly in tool response. Content may contain malicious instructions.',
184
+ suggestedFix: 'Validate and sanitize fetch results before including in response.',
185
+ },
186
+ // Database query results (JS)
187
+ {
188
+ name: 'Raw database content in response',
189
+ pattern: /return\s*\{[^}]*(?:data|results?|rows|documents?|items?)\s*:\s*(?:await\s+)?(?:db|database|client|collection|query)\.(?:query|find|search|execute)/gi,
190
+ category: 'tool_poisoning',
191
+ baseSeverity: 'medium',
192
+ description: 'Database query results returned without filtering. Stored content could be poisoned.',
193
+ suggestedFix: 'Validate and sanitize database content. Consider returning only safe fields.',
194
+ },
195
+ // Database query results (Python)
196
+ {
197
+ name: 'Raw database content in Python response',
198
+ pattern: /return\s*\{[^}]*["'](?:data|results?|documents?)["']\s*:\s*(?:await\s+)?(?:db|database|results)[\.\[]/gi,
199
+ category: 'tool_poisoning',
200
+ baseSeverity: 'medium',
201
+ description: 'Database query results returned without filtering in Python MCP tool.',
202
+ suggestedFix: 'Validate and sanitize database content. Consider returning only safe fields.',
203
+ },
204
+ // File content
205
+ {
206
+ name: 'File content returned without validation',
207
+ pattern: /return\s*[{(]\s*[{"]?[^}]*content['"]\s*[:=]\s*(?:await\s+)?(?:fs|file|readFile|readFileSync)/gi,
208
+ category: 'tool_poisoning',
209
+ baseSeverity: 'high',
210
+ description: 'File content returned without validation. Files could contain malicious instructions.',
211
+ suggestedFix: 'Validate file content and type. Sanitize before returning to the model.',
212
+ },
213
+ // Email content
214
+ {
215
+ name: 'Email content in response',
216
+ pattern: /return\s*[{(]\s*[{"]?[^}]*(?:body|content|text)['"]\s*[:=]\s*(?:email|message|mail)\.(?:body|content|text|html)/gi,
217
+ category: 'tool_poisoning',
218
+ baseSeverity: 'high',
219
+ description: 'Email content returned to model. Emails are common vectors for prompt injection.',
220
+ suggestedFix: 'Sanitize email content. Strip HTML, scripts, and instruction-like patterns.',
221
+ },
222
+ // RSS/feed content
223
+ {
224
+ name: 'RSS/feed content in response',
225
+ pattern: /return\s*[{(]\s*[{"]?[^}]*(?:items?|entries?|feed)['"]\s*[:=]\s*(?:feed|rss|parser)\.(?:items?|entries?|parse)/gi,
226
+ category: 'tool_poisoning',
227
+ baseSeverity: 'medium',
228
+ description: 'RSS/feed content returned without filtering. Feed titles and descriptions could be poisoned.',
229
+ suggestedFix: 'Sanitize feed content. Filter to safe fields only (id, title summary).',
230
+ },
231
+ // Generic raw content return (JS)
232
+ {
233
+ name: 'Raw content in tool response',
234
+ pattern: /server\.tool\s*\([^)]+,\s*async[^{]+\{[^}]*return\s*\{[^}]*:\s*(?:await\s+)?response\.text\(\)/gi,
235
+ category: 'tool_poisoning',
236
+ baseSeverity: 'high',
237
+ description: 'MCP tool returns raw text content from external source.',
238
+ suggestedFix: 'Add content sanitization layer before returning external content.',
239
+ },
240
+ // Python httpx response text
241
+ {
242
+ name: 'Raw HTTP response in Python tool',
243
+ pattern: /return\s*\{[^}]*["']content["']\s*:\s*(?:await\s+)?response\.text/gi,
244
+ category: 'tool_poisoning',
245
+ baseSeverity: 'high',
246
+ description: 'Python MCP tool returns raw HTTP response content.',
247
+ suggestedFix: 'Sanitize external content before returning to the model.',
248
+ },
249
+ // Variable-based: HTTP response assigned then returned
250
+ {
251
+ name: 'HTTP response variable in MCP tool',
252
+ pattern: /(?:const|let|var)\s+\w+\s*=\s*(?:await\s+)?response\.text\(\)[^}]+return\s*\{[^}]*content/gis,
253
+ category: 'tool_poisoning',
254
+ baseSeverity: 'high',
255
+ description: 'HTTP response text stored in variable and returned. External content could be poisoned.',
256
+ suggestedFix: 'Sanitize the content before returning: const sanitized = sanitize(html)',
257
+ },
258
+ // Variable-based: File read assigned then returned
259
+ {
260
+ name: 'File read variable in MCP tool',
261
+ pattern: /(?:const|let|var)\s+\w+\s*=\s*(?:await\s+)?(?:fs\.readFile|readFile)[^}]+return\s*\{[^}]*content/gis,
262
+ category: 'tool_poisoning',
263
+ baseSeverity: 'high',
264
+ description: 'File content stored in variable and returned. File content could contain malicious instructions.',
265
+ suggestedFix: 'Validate and sanitize file content before returning.',
266
+ },
267
+ // Database query result in return (shorthand property)
268
+ {
269
+ name: 'Database query in MCP return',
270
+ pattern: /(?:const|let|var)\s+(?:results?|data|rows)\s*=\s*(?:await\s+)?(?:db|database|client)\.(?:query|find|search)[^}]+return\s*\{[^}]*(?:data|results?|rows)/gis,
271
+ category: 'tool_poisoning',
272
+ baseSeverity: 'medium',
273
+ description: 'Database query results returned in MCP tool. Stored content could be poisoned.',
274
+ suggestedFix: 'Validate and sanitize database content before returning.',
275
+ },
276
+ // Email body returned
277
+ {
278
+ name: 'Email body in MCP return',
279
+ pattern: /(?:email|message|mail)\s*=\s*(?:await)?[^}]+return\s*\{[^}]*body\s*:\s*(?:email|message|mail)\.body/gis,
280
+ category: 'tool_poisoning',
281
+ baseSeverity: 'high',
282
+ description: 'Email body content returned in MCP tool. Emails are common prompt injection vectors.',
283
+ suggestedFix: 'Sanitize email content. Strip HTML and instruction-like patterns.',
284
+ },
285
+ // Feed/RSS items returned
286
+ {
287
+ name: 'RSS/feed items in MCP return',
288
+ pattern: /(?:feed|rss)\s*=\s*(?:await)?[^}]+return\s*\{[^}]*items?\s*:\s*(?:feed|rss)\.items?/gis,
289
+ category: 'tool_poisoning',
290
+ baseSeverity: 'medium',
291
+ description: 'RSS/feed items returned in MCP tool. Feed content could be poisoned.',
292
+ suggestedFix: 'Sanitize feed content. Filter to safe fields only.',
293
+ },
294
+ ]
295
+
296
+ /**
297
+ * Credential Issue Patterns
298
+ * Detect credentials in tool parameters or responses
299
+ */
300
+ const CREDENTIAL_PATTERNS: MCPSecurityPattern[] = [
301
+ // API key in parameter
302
+ {
303
+ name: 'API key in tool parameter',
304
+ pattern: /server\.tool\s*\([^)]+,\s*async\s*\(\s*\{[^}]*(?:apiKey|api_key|token|secret|password|privateKey|private_key|accessToken|access_token|authToken|auth_token)/gi,
305
+ category: 'credential_issue',
306
+ baseSeverity: 'high',
307
+ description: 'Tool accepts credentials as parameter. Credentials should not flow through the model.',
308
+ suggestedFix: 'Use server-side credential storage. Remove credential parameter and use environment variables or secret manager.',
309
+ },
310
+ // Python decorator with credentials
311
+ {
312
+ name: 'Python tool with credential parameter',
313
+ pattern: /@server\.tool[^)]*\)\s*(?:async\s+)?def\s+\w+\s*\([^)]*(?:api_key|token|secret|password|private_key|access_token|auth_token)/gi,
314
+ category: 'credential_issue',
315
+ baseSeverity: 'high',
316
+ description: 'Python MCP tool accepts credentials as parameter.',
317
+ suggestedFix: 'Use server-side credential management. Do not pass secrets through tool parameters.',
318
+ },
319
+ // Returning credentials in response
320
+ {
321
+ name: 'Credentials in tool response',
322
+ pattern: /return\s*\{[^}]*(?:apiKey|api_key|token|password|secret|privateKey|private_key|accessToken|access_token|refreshToken|refresh_token|jwt)\s*:/gi,
323
+ category: 'credential_issue',
324
+ baseSeverity: 'critical',
325
+ description: 'Tool response includes credentials. Exposing secrets to the model is dangerous.',
326
+ suggestedFix: 'Never return credentials in tool responses. Return success status or user-safe identifiers only.',
327
+ },
328
+ // Connection string in parameter
329
+ {
330
+ name: 'Connection string in tool parameter',
331
+ pattern: /server\.tool\s*\([^)]+,\s*async\s*\(\s*\{[^}]*(?:connectionString|connection_string|dsn|dbUrl|db_url|databaseUrl|database_url)/gi,
332
+ category: 'credential_issue',
333
+ baseSeverity: 'high',
334
+ description: 'Database connection string passed as tool parameter. Connection strings contain credentials.',
335
+ suggestedFix: 'Use server-side database configuration. Do not accept connection strings as parameters.',
336
+ },
337
+ // Environment secrets in response
338
+ {
339
+ name: 'Environment secrets in response',
340
+ pattern: /return\s*\{[^}]*:\s*process\.env\.(?:.*(?:KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL))/gi,
341
+ category: 'credential_issue',
342
+ baseSeverity: 'critical',
343
+ description: 'Environment secrets returned in tool response.',
344
+ suggestedFix: 'Never return environment secrets. Use them server-side only.',
345
+ },
346
+ ]
347
+
348
+ /**
349
+ * Confused Deputy Patterns
350
+ * Detect operations without proper user context
351
+ */
352
+ const CONFUSED_DEPUTY_PATTERNS: MCPSecurityPattern[] = [
353
+ // Data operation without user context
354
+ {
355
+ name: 'Data deletion without user context',
356
+ pattern: /server\.tool\s*\([^)]+,\s*async\s*\(\s*\{[^}]*(?:Id|id)\s*\}[^)]*\)\s*(?:=>|:)[^{]*\{[^}]*(?:\.delete|\.remove|\.destroy)\s*\(/gi,
357
+ category: 'confused_deputy',
358
+ baseSeverity: 'high',
359
+ description: 'Tool deletes data using only an ID parameter without user context verification.',
360
+ suggestedFix: 'Add user context parameter and verify ownership: if (record.ownerId !== context.user.id) throw new Error("Unauthorized")',
361
+ },
362
+ // Update operation without auth check
363
+ {
364
+ name: 'Data update without authorization',
365
+ pattern: /server\.tool\s*\([^)]+,\s*async\s*\(\s*\{[^}]*(?:Id|id)[^}]*data[^}]*\}[^)]*\)[^{]*\{[^}]*(?:\.update|\.set|\.save)\s*\(/gi,
366
+ category: 'confused_deputy',
367
+ baseSeverity: 'high',
368
+ description: 'Tool updates data without verifying the user owns the record.',
369
+ suggestedFix: 'Validate user ownership before update. Add authorization check.',
370
+ },
371
+ // Reading user-specific data without context
372
+ {
373
+ name: 'User data access without context',
374
+ pattern: /server\.tool\s*\([^)]+(?:user|file|record|document|message)[^)]+,\s*async\s*\(\s*\{[^}]*(?:Id|id)\s*\}/gi,
375
+ category: 'confused_deputy',
376
+ baseSeverity: 'medium',
377
+ description: 'Tool accesses user-specific data with only an ID. Missing user context verification.',
378
+ suggestedFix: 'Add user context and verify access permissions for the requested resource.',
379
+ },
380
+ // Admin/privileged operation without auth
381
+ {
382
+ name: 'Privileged operation without authorization',
383
+ pattern: /server\.tool\s*\([^)]+(?:admin|grant|revoke|elevate|promote)[^)]*,\s*async/gi,
384
+ category: 'confused_deputy',
385
+ baseSeverity: 'critical',
386
+ description: 'Privileged/admin tool without visible authorization check.',
387
+ suggestedFix: 'Add strict authorization check. Verify caller has admin privileges before executing.',
388
+ },
389
+ // Send email/message as user
390
+ {
391
+ name: 'Send message without identity verification',
392
+ pattern: /server\.tool\s*\([^)]+(?:send|email|message)[^)]+,\s*async\s*\(\s*\{[^}]*(?:from|sender)[^}]*\}/gi,
393
+ category: 'confused_deputy',
394
+ baseSeverity: 'high',
395
+ description: 'Tool sends messages with a \'from\' parameter. Should use authenticated user identity.',
396
+ suggestedFix: 'Use context.user for sender identity. Do not allow arbitrary \'from\' values.',
397
+ },
398
+ // Cross-tenant data access
399
+ {
400
+ name: 'Organization/tenant data without scope',
401
+ pattern: /server\.tool\s*\([^)]+(?:org|organization|tenant|workspace)[^)]+,\s*async\s*\(\s*\{[^}]*(?:Id|id)\s*\}/gi,
402
+ category: 'confused_deputy',
403
+ baseSeverity: 'high',
404
+ description: 'Tool accesses organization data by ID without tenant context verification.',
405
+ suggestedFix: 'Verify tenant membership: if (org.id !== context.user.tenantId) throw new Error("Unauthorized")',
406
+ },
407
+ // Python tool without context
408
+ {
409
+ name: 'Python tool data operation without user',
410
+ pattern: /@server\.tool[^)]*\)\s*(?:async\s+)?def\s+(?:delete|update|remove|create)_\w+\s*\(\s*(?:\w+_)?id\s*:/gi,
411
+ category: 'confused_deputy',
412
+ baseSeverity: 'medium',
413
+ description: 'Python MCP tool performs data operation with only an ID parameter.',
414
+ suggestedFix: 'Add user context parameter and validate authorization.',
415
+ },
416
+ ]
417
+
418
+ /**
419
+ * Tool Description Injection Patterns
420
+ * Detect prompt injection risks in MCP tool descriptions/metadata
421
+ */
422
+ const DESCRIPTION_INJECTION_PATTERNS: MCPSecurityPattern[] = [
423
+ // Dynamic description from variable/input (JS template literals)
424
+ {
425
+ name: 'Dynamic tool description from variable',
426
+ pattern: /description\s*:\s*[`'"].*\$\{.*(?:user|req|input|param|config).*\}.*[`'"]/gi,
427
+ category: 'description_injection',
428
+ baseSeverity: 'high',
429
+ description: 'Tool description constructed from user input or external variables. Malicious content could manipulate AI behavior.',
430
+ suggestedFix: 'Use static descriptions only. Never include user input in tool descriptions.',
431
+ },
432
+ // Description concatenated with user input
433
+ {
434
+ name: 'Tool description with user input concatenation',
435
+ pattern: /description\s*:\s*(?:["'][^"']*["']\s*\+\s*)?(?:user|req|input|param|options)\./gi,
436
+ category: 'description_injection',
437
+ baseSeverity: 'high',
438
+ description: 'Tool description concatenated with user-controlled values. Could inject prompt manipulation instructions.',
439
+ suggestedFix: 'Use static descriptions. If dynamic content is needed, sanitize and validate strictly.',
440
+ },
441
+ // Injection keywords in tool descriptions
442
+ {
443
+ name: 'Injection keywords in tool description',
444
+ pattern: /description\s*:\s*["'`][^"'`]*(?:ignore\s*(?:previous|above|all)|bypass|override|system\s*prompt|disregard|forget)[^"'`]*["'`]/gi,
445
+ category: 'description_injection',
446
+ baseSeverity: 'critical',
447
+ description: 'Tool description contains prompt injection keywords. This could manipulate AI behavior.',
448
+ suggestedFix: 'Remove manipulation keywords from description. Use neutral, factual descriptions.',
449
+ },
450
+ // Tool name from untrusted source
451
+ {
452
+ name: 'Dynamic tool name from config/options',
453
+ pattern: /(?:registerTool|server\.tool|addTool)\s*\(\s*(?:config|options|params|settings)\s*\[?\s*['".]?\s*(?:name|tool)/gi,
454
+ category: 'description_injection',
455
+ baseSeverity: 'high',
456
+ description: 'Tool name derived from configuration or options. Attackers could shadow legitimate tools.',
457
+ suggestedFix: 'Use hardcoded tool names. Validate against an allowlist if dynamic names are required.',
458
+ },
459
+ // Python dynamic description
460
+ {
461
+ name: 'Python tool with dynamic description',
462
+ pattern: /@server\.tool\s*\(\s*name\s*=\s*(?:f["']|["'].*\{)/gi,
463
+ category: 'description_injection',
464
+ baseSeverity: 'high',
465
+ description: 'Python MCP tool with f-string or formatted description. Could include injected content.',
466
+ suggestedFix: 'Use static string literals for tool names and descriptions.',
467
+ },
468
+ // Description from database/storage
469
+ {
470
+ name: 'Tool description from storage',
471
+ pattern: /description\s*:\s*(?:await\s+)?(?:db|database|storage|cache|redis)\.(?:get|read|fetch)/gi,
472
+ category: 'description_injection',
473
+ baseSeverity: 'medium',
474
+ description: 'Tool description loaded from storage. Stored content could be poisoned.',
475
+ suggestedFix: 'Use static descriptions. If dynamic descriptions are required, validate and sanitize thoroughly.',
476
+ },
477
+ ]
478
+
479
+ /**
480
+ * Cross-Server Tool Shadowing Patterns
481
+ * Detect malicious MCP servers overriding legitimate tools
482
+ */
483
+ const SERVER_SHADOWING_PATTERNS: MCPSecurityPattern[] = [
484
+ // Server config from environment/user input
485
+ {
486
+ name: 'MCP server config from environment',
487
+ pattern: /(?:MCP_SERVERS?|mcpServers?)\s*[=:]\s*(?:JSON\.parse\s*\(\s*)?process\.env/gi,
488
+ category: 'server_shadowing',
489
+ baseSeverity: 'medium',
490
+ description: 'MCP server configuration loaded from environment variables. Ensure proper validation.',
491
+ suggestedFix: 'Validate server URLs against an allowlist. Use explicit server configuration in code.',
492
+ },
493
+ // Server URLs from user input
494
+ {
495
+ name: 'MCP server URL from user input',
496
+ pattern: /(?:server(?:Url|URL|Uri)|endpoint)\s*:\s*(?:req\.|user\.|input\.|params\.|body\.)/gi,
497
+ category: 'server_shadowing',
498
+ baseSeverity: 'high',
499
+ description: 'MCP server URL derived from user input. Attackers could point to malicious servers.',
500
+ suggestedFix: 'Use hardcoded server URLs or validate against a strict allowlist.',
501
+ },
502
+ // Dynamic server registration from config
503
+ {
504
+ name: 'Dynamic MCP server registration',
505
+ pattern: /(?:for|forEach)\s*\([^)]*\)\s*(?:=>|\{)\s*[^}]*(?:register|connect|add)(?:Server|MCP)/gi,
506
+ category: 'server_shadowing',
507
+ baseSeverity: 'medium',
508
+ description: 'MCP servers registered dynamically from configuration. Tool shadowing risk.',
509
+ suggestedFix: 'Register servers explicitly. Implement tool name conflict detection.',
510
+ },
511
+ // Server list from JSON parse
512
+ {
513
+ name: 'MCP servers from parsed JSON',
514
+ pattern: /servers\s*=\s*JSON\.parse\s*\(\s*(?:req\.|user|input|localStorage|sessionStorage)/gi,
515
+ category: 'server_shadowing',
516
+ baseSeverity: 'high',
517
+ description: 'MCP server list parsed from user-controlled data. Could inject malicious servers.',
518
+ suggestedFix: 'Define servers in code. If dynamic loading is needed, validate against an allowlist.',
519
+ },
520
+ // Server config override
521
+ {
522
+ name: 'MCP server config override pattern',
523
+ pattern: /Object\.assign\s*\([^)]*(?:server|mcp)Config[^)]*,\s*(?:req\.|user\.|options\.)/gi,
524
+ category: 'server_shadowing',
525
+ baseSeverity: 'medium',
526
+ description: 'MCP server configuration being overridden with user-provided values.',
527
+ suggestedFix: 'Validate and sanitize configuration overrides. Use allowlist for permitted settings.',
528
+ },
529
+ ]
530
+
531
+ // ============================================================================
532
+ // Main Detection Function
533
+ // ============================================================================
534
+
535
+ /**
536
+ * Map internal category to vulnerability category
537
+ */
538
+ function mapCategory(internal: MCPSecurityPattern['category']): VulnerabilityCategory {
539
+ switch (internal) {
540
+ case 'tool_poisoning':
541
+ return 'ai_mcp_tool_poisoning'
542
+ case 'credential_issue':
543
+ return 'ai_mcp_credential_issue'
544
+ case 'confused_deputy':
545
+ return 'ai_mcp_confused_deputy'
546
+ case 'description_injection':
547
+ return 'ai_mcp_description_injection'
548
+ case 'server_shadowing':
549
+ return 'ai_mcp_server_shadowing'
550
+ }
551
+ }
552
+
553
+ /**
554
+ * Main detection function for MCP security issues
555
+ */
556
+ export function detectMCPSecurity(
557
+ content: string,
558
+ filePath: string
559
+ ): Vulnerability[] {
560
+ const vulnerabilities: Vulnerability[] = []
561
+
562
+ // Skip non-applicable files
563
+ if (isScannerOrFixtureFile(filePath)) return vulnerabilities
564
+ if (isDocumentationFile(filePath)) return vulnerabilities
565
+
566
+ // Only scan MCP-related files
567
+ if (!isMCPFile(content, filePath)) {
568
+ return vulnerabilities
569
+ }
570
+
571
+ const lines = content.split('\n')
572
+ const isTestFile = isTestOrMockFile(filePath)
573
+ const isExample = isExampleDirectory(filePath)
574
+ const isLibrary = isLibraryCode(filePath)
575
+
576
+ // Process all pattern categories
577
+ const allPatterns: MCPSecurityPattern[] = [
578
+ ...TOOL_POISONING_PATTERNS,
579
+ ...CREDENTIAL_PATTERNS,
580
+ ...CONFUSED_DEPUTY_PATTERNS,
581
+ ...DESCRIPTION_INJECTION_PATTERNS,
582
+ ...SERVER_SHADOWING_PATTERNS,
583
+ ]
584
+
585
+ // Track findings to avoid duplicates
586
+ const seenFindings = new Set<string>()
587
+
588
+ for (const pattern of allPatterns) {
589
+ const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
590
+ let match
591
+
592
+ while ((match = regex.exec(content)) !== null) {
593
+ const lineNumber = content.substring(0, match.index).split('\n').length
594
+ const lineContent = lines[lineNumber - 1]?.trim() || ''
595
+
596
+ // Skip comments
597
+ if (isComment(lineContent)) continue
598
+
599
+ // Create dedup key
600
+ const dedupKey = `${filePath}:${lineNumber}:${pattern.category}`
601
+ if (seenFindings.has(dedupKey)) continue
602
+ seenFindings.add(dedupKey)
603
+
604
+ // Get surrounding context for analysis
605
+ const context = getSurroundingContext(content, lineNumber - 1, 30)
606
+
607
+ // Calculate severity based on context
608
+ let severity = pattern.baseSeverity
609
+ let description = pattern.description
610
+ const notes: string[] = []
611
+
612
+ // Apply context-aware severity adjustments
613
+ if (pattern.category === 'tool_poisoning') {
614
+ // Check for content sanitization
615
+ if (hasContentSanitization(context)) {
616
+ severity = 'info'
617
+ notes.push('Content sanitization detected')
618
+ }
619
+ // Check for safe data source
620
+ else if (isSafeDataSource(context)) {
621
+ severity = 'info'
622
+ notes.push('Safe/static data source detected')
623
+ }
624
+ // Check for user context (their own data)
625
+ else if (hasUserContext(context)) {
626
+ // Has user context - might be returning user's own data
627
+ if (severity === 'high') severity = 'medium'
628
+ notes.push('User context present - may be returning user\'s own data')
629
+ }
630
+ }
631
+
632
+ if (pattern.category === 'confused_deputy') {
633
+ // Check for user context
634
+ if (hasUserContext(context)) {
635
+ // User context present - check for auth
636
+ if (hasAuthorizationCheck(context)) {
637
+ severity = 'info'
638
+ notes.push('Authorization check detected')
639
+ } else {
640
+ // Has user but no auth check - lower severity
641
+ if (severity === 'high') severity = 'medium'
642
+ if (severity === 'critical') severity = 'high'
643
+ notes.push('User context present but no authorization check')
644
+ }
645
+ }
646
+ }
647
+
648
+ // Credential issues are always serious, but check context
649
+ if (pattern.category === 'credential_issue') {
650
+ // Check if it's returning the credential
651
+ if (pattern.name.includes('response') || pattern.name.includes('return')) {
652
+ // Returning credentials is always critical/high
653
+ } else if (hasUserContext(context)) {
654
+ // Parameter with user context - still bad but slightly less severe
655
+ if (severity === 'high') severity = 'medium'
656
+ notes.push('User context present but credentials still in parameters')
657
+ }
658
+ }
659
+
660
+ // Description injection - check for input sanitization
661
+ if (pattern.category === 'description_injection') {
662
+ // Check for sanitization or validation before description
663
+ if (/sanitize|validate|escape|filter|strip/i.test(context)) {
664
+ severity = 'low'
665
+ notes.push('Input sanitization detected nearby')
666
+ }
667
+ // Check for static/constant descriptions
668
+ if (/const\s+\w+\s*=\s*["'`][^"'`]+["'`]\s*;?\s*$/m.test(context)) {
669
+ // Likely a constant being used
670
+ severity = 'info'
671
+ notes.push('May be using constant description')
672
+ }
673
+ }
674
+
675
+ // Server shadowing - check for allowlist validation
676
+ if (pattern.category === 'server_shadowing') {
677
+ // Check for allowlist/whitelist validation
678
+ if (/allowlist|whitelist|ALLOWED_SERVERS|validServers|trustedServers/i.test(context)) {
679
+ severity = 'info'
680
+ notes.push('Server allowlist detected')
681
+ }
682
+ // Check for URL validation
683
+ if (/validate.*url|url.*validate|isValidUrl|checkUrl/i.test(context)) {
684
+ severity = 'low'
685
+ notes.push('URL validation detected')
686
+ }
687
+ }
688
+
689
+ // Downgrade test files
690
+ if (isTestFile) {
691
+ severity = 'info'
692
+ notes.push('in test file')
693
+ }
694
+
695
+ // Downgrade example/demo directories
696
+ if (isExample && severity !== 'info') {
697
+ severity = 'info'
698
+ notes.push('in example/demo directory')
699
+ }
700
+
701
+ // Downgrade library code
702
+ if (isLibrary && severity !== 'info') {
703
+ severity = 'info'
704
+ notes.push('library code')
705
+ }
706
+
707
+ // Build final description
708
+ if (notes.length > 0) {
709
+ description += ` (${notes.join('; ')})`
710
+ }
711
+
712
+ vulnerabilities.push({
713
+ id: `ai-mcp-${filePath}-${lineNumber}-${pattern.name.replace(/\s+/g, '-')}`,
714
+ filePath,
715
+ lineNumber,
716
+ lineContent,
717
+ severity,
718
+ category: mapCategory(pattern.category),
719
+ title: pattern.name,
720
+ description,
721
+ suggestedFix: pattern.suggestedFix,
722
+ confidence: severity === 'info' ? 'low' : 'medium',
723
+ layer: 2,
724
+ requiresAIValidation: severity !== 'info' && severity !== 'low',
725
+ })
726
+ }
727
+ }
728
+
729
+ return vulnerabilities
730
+ }