@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,384 @@
1
+ /**
2
+ * OSV Check Unit Tests
3
+ *
4
+ * Tests for the OSV.dev security advisory integration.
5
+ * Mocks fetch to avoid live API calls during tests.
6
+ *
7
+ * Run: npx jest src/layer3/__tests__/osv-check.test.ts
8
+ */
9
+
10
+ import {
11
+ checkPackageAdvisories,
12
+ queryOSV,
13
+ queryOSVBatch,
14
+ mapOSVSeverity,
15
+ isMaliciousPackage,
16
+ getCacheKey,
17
+ advisoryCache,
18
+ } from '../osv-check'
19
+
20
+ // Mock fetch globally
21
+ const mockFetch = jest.fn()
22
+ global.fetch = mockFetch
23
+
24
+ // Helper to create OSV vulnerability response
25
+ function createOSVVuln(options: {
26
+ id: string
27
+ summary?: string
28
+ cvssScore?: number
29
+ malicious?: boolean
30
+ dbSeverity?: string
31
+ }) {
32
+ return {
33
+ id: options.id,
34
+ summary: options.summary || 'Test vulnerability',
35
+ severity: options.cvssScore
36
+ ? [{ type: 'CVSS_V3', score: options.cvssScore.toString() }]
37
+ : undefined,
38
+ database_specific: {
39
+ malicious: options.malicious,
40
+ severity: options.dbSeverity,
41
+ },
42
+ }
43
+ }
44
+
45
+ describe('OSV Check', () => {
46
+ beforeEach(() => {
47
+ // Clear cache and mocks before each test
48
+ advisoryCache.clear()
49
+ mockFetch.mockReset()
50
+ })
51
+
52
+ describe('mapOSVSeverity()', () => {
53
+ it('returns critical for malicious packages', () => {
54
+ const vuln = createOSVVuln({ id: 'MAL-123', malicious: true })
55
+ expect(mapOSVSeverity(vuln)).toBe('critical')
56
+ })
57
+
58
+ it('maps CVSS scores correctly', () => {
59
+ expect(mapOSVSeverity(createOSVVuln({ id: 'CVE-1', cvssScore: 9.5 }))).toBe('critical')
60
+ expect(mapOSVSeverity(createOSVVuln({ id: 'CVE-2', cvssScore: 9.0 }))).toBe('critical')
61
+ expect(mapOSVSeverity(createOSVVuln({ id: 'CVE-3', cvssScore: 7.5 }))).toBe('high')
62
+ expect(mapOSVSeverity(createOSVVuln({ id: 'CVE-4', cvssScore: 7.0 }))).toBe('high')
63
+ expect(mapOSVSeverity(createOSVVuln({ id: 'CVE-5', cvssScore: 5.0 }))).toBe('medium')
64
+ expect(mapOSVSeverity(createOSVVuln({ id: 'CVE-6', cvssScore: 4.0 }))).toBe('medium')
65
+ expect(mapOSVSeverity(createOSVVuln({ id: 'CVE-7', cvssScore: 2.0 }))).toBe('low')
66
+ })
67
+
68
+ it('maps database_specific severity when no CVSS', () => {
69
+ expect(mapOSVSeverity(createOSVVuln({ id: 'X-1', dbSeverity: 'critical' }))).toBe('critical')
70
+ expect(mapOSVSeverity(createOSVVuln({ id: 'X-2', dbSeverity: 'high' }))).toBe('high')
71
+ expect(mapOSVSeverity(createOSVVuln({ id: 'X-3', dbSeverity: 'moderate' }))).toBe('medium')
72
+ expect(mapOSVSeverity(createOSVVuln({ id: 'X-4', dbSeverity: 'medium' }))).toBe('medium')
73
+ expect(mapOSVSeverity(createOSVVuln({ id: 'X-5', dbSeverity: 'low' }))).toBe('low')
74
+ })
75
+
76
+ it('defaults to high for unknown severity', () => {
77
+ const vuln = createOSVVuln({ id: 'UNKNOWN-1' })
78
+ expect(mapOSVSeverity(vuln)).toBe('high')
79
+ })
80
+ })
81
+
82
+ describe('isMaliciousPackage()', () => {
83
+ it('detects MAL- prefixed IDs', () => {
84
+ const vuln = createOSVVuln({ id: 'MAL-2023-1234' })
85
+ expect(isMaliciousPackage(vuln)).toBe(true)
86
+ })
87
+
88
+ it('detects malicious flag in database_specific', () => {
89
+ const vuln = createOSVVuln({ id: 'GHSA-xxxx', malicious: true })
90
+ expect(isMaliciousPackage(vuln)).toBe(true)
91
+ })
92
+
93
+ it('detects "malicious" in summary', () => {
94
+ const vuln = createOSVVuln({
95
+ id: 'GHSA-xxxx',
96
+ summary: 'This package contains malicious code',
97
+ })
98
+ expect(isMaliciousPackage(vuln)).toBe(true)
99
+ })
100
+
101
+ it('returns false for normal vulnerabilities', () => {
102
+ const vuln = createOSVVuln({
103
+ id: 'CVE-2024-1234',
104
+ summary: 'SQL injection vulnerability',
105
+ })
106
+ expect(isMaliciousPackage(vuln)).toBe(false)
107
+ })
108
+ })
109
+
110
+ describe('getCacheKey()', () => {
111
+ it('creates cache keys with lowercase package names', () => {
112
+ expect(getCacheKey('React', 'npm')).toBe('npm:react')
113
+ expect(getCacheKey('Django', 'PyPI')).toBe('PyPI:django')
114
+ })
115
+ })
116
+
117
+ describe('queryOSV()', () => {
118
+ it('returns cached results on subsequent calls', async () => {
119
+ // First call - API returns vulns
120
+ mockFetch.mockResolvedValueOnce({
121
+ ok: true,
122
+ json: async () => ({ vulns: [createOSVVuln({ id: 'CVE-1' })] }),
123
+ })
124
+
125
+ const first = await queryOSV('test-package', 'npm')
126
+ expect(first).toHaveLength(1)
127
+ expect(mockFetch).toHaveBeenCalledTimes(1)
128
+
129
+ // Second call - should use cache
130
+ const second = await queryOSV('test-package', 'npm')
131
+ expect(second).toHaveLength(1)
132
+ expect(mockFetch).toHaveBeenCalledTimes(1) // Still 1, used cache
133
+ })
134
+
135
+ it('returns empty array when API returns non-ok response', async () => {
136
+ mockFetch.mockResolvedValueOnce({
137
+ ok: false,
138
+ status: 500,
139
+ })
140
+
141
+ const result = await queryOSV('error-package', 'npm')
142
+ expect(result).toEqual([])
143
+ })
144
+
145
+ it('returns empty array on network error', async () => {
146
+ mockFetch.mockRejectedValueOnce(new Error('Network error'))
147
+
148
+ const result = await queryOSV('network-error-package', 'npm')
149
+ expect(result).toEqual([])
150
+ })
151
+ })
152
+
153
+ describe('queryOSVBatch()', () => {
154
+ it('uses cache for previously queried packages', async () => {
155
+ // Pre-populate cache
156
+ advisoryCache.set('npm:cached-pkg', {
157
+ timestamp: Date.now(),
158
+ advisories: [createOSVVuln({ id: 'CACHED-1' })],
159
+ })
160
+
161
+ mockFetch.mockResolvedValueOnce({
162
+ ok: true,
163
+ json: async () => ({
164
+ results: [{ vulns: [createOSVVuln({ id: 'NEW-1' })] }],
165
+ }),
166
+ })
167
+
168
+ const results = await queryOSVBatch([
169
+ { name: 'cached-pkg', ecosystem: 'npm' },
170
+ { name: 'new-pkg', ecosystem: 'npm' },
171
+ ])
172
+
173
+ // Should have results for both
174
+ expect(results.get('npm:cached-pkg')).toHaveLength(1)
175
+ expect(results.get('npm:new-pkg')).toHaveLength(1)
176
+
177
+ // API should only be called once (for new-pkg batch)
178
+ expect(mockFetch).toHaveBeenCalledTimes(1)
179
+ })
180
+
181
+ it('returns empty arrays on API error', async () => {
182
+ mockFetch.mockResolvedValueOnce({
183
+ ok: false,
184
+ status: 503,
185
+ })
186
+
187
+ const results = await queryOSVBatch([
188
+ { name: 'pkg1', ecosystem: 'npm' },
189
+ { name: 'pkg2', ecosystem: 'npm' },
190
+ ])
191
+
192
+ expect(results.get('npm:pkg1')).toEqual([])
193
+ expect(results.get('npm:pkg2')).toEqual([])
194
+ })
195
+ })
196
+
197
+ describe('checkPackageAdvisories()', () => {
198
+ it('returns vulnerabilities for packages with advisories', async () => {
199
+ // Mock OSV batch response
200
+ mockFetch.mockResolvedValueOnce({
201
+ ok: true,
202
+ json: async () => ({
203
+ results: [
204
+ {
205
+ vulns: [
206
+ createOSVVuln({
207
+ id: 'MAL-2023-1234',
208
+ summary: 'Malicious package',
209
+ malicious: true,
210
+ }),
211
+ ],
212
+ },
213
+ ],
214
+ }),
215
+ })
216
+
217
+ const packageJson = JSON.stringify({
218
+ dependencies: {
219
+ 'malicious-pkg': '1.0.0',
220
+ },
221
+ })
222
+
223
+ const findings = await checkPackageAdvisories(packageJson, 'package.json')
224
+
225
+ expect(findings).toHaveLength(1)
226
+ expect(findings[0].category).toBe('ai_package_malicious')
227
+ expect(findings[0].severity).toBe('critical')
228
+ expect(findings[0].title).toContain('Malicious package')
229
+ })
230
+
231
+ it('returns empty array for clean packages', async () => {
232
+ // Mock OSV batch response - no vulns
233
+ mockFetch.mockResolvedValueOnce({
234
+ ok: true,
235
+ json: async () => ({
236
+ results: [{ vulns: [] }],
237
+ }),
238
+ })
239
+
240
+ const packageJson = JSON.stringify({
241
+ dependencies: {
242
+ 'clean-pkg': '1.0.0',
243
+ },
244
+ })
245
+
246
+ const findings = await checkPackageAdvisories(packageJson, 'package.json')
247
+ expect(findings).toHaveLength(0)
248
+ })
249
+
250
+ it('handles requirements.txt files', async () => {
251
+ mockFetch.mockResolvedValueOnce({
252
+ ok: true,
253
+ json: async () => ({
254
+ results: [
255
+ {
256
+ vulns: [
257
+ createOSVVuln({
258
+ id: 'CVE-2024-1234',
259
+ summary: 'Critical vulnerability',
260
+ cvssScore: 9.8,
261
+ }),
262
+ ],
263
+ },
264
+ ],
265
+ }),
266
+ })
267
+
268
+ const requirements = `requests==2.28.0
269
+ flask>=2.0.0`
270
+
271
+ const findings = await checkPackageAdvisories(requirements, 'requirements.txt')
272
+
273
+ expect(findings).toHaveLength(1)
274
+ expect(findings[0].category).toBe('suspicious_package')
275
+ expect(findings[0].severity).toBe('critical')
276
+ })
277
+
278
+ it('returns empty for unsupported file types', async () => {
279
+ const findings = await checkPackageAdvisories('some content', 'random.txt')
280
+ expect(findings).toHaveLength(0)
281
+ })
282
+
283
+ it('categorizes vulnerable (non-malicious) packages correctly', async () => {
284
+ mockFetch.mockResolvedValueOnce({
285
+ ok: true,
286
+ json: async () => ({
287
+ results: [
288
+ {
289
+ vulns: [
290
+ createOSVVuln({
291
+ id: 'CVE-2024-5678',
292
+ summary: 'XSS vulnerability in template rendering',
293
+ cvssScore: 7.5,
294
+ }),
295
+ ],
296
+ },
297
+ ],
298
+ }),
299
+ })
300
+
301
+ const packageJson = JSON.stringify({
302
+ dependencies: {
303
+ 'vuln-pkg': '1.0.0',
304
+ },
305
+ })
306
+
307
+ const findings = await checkPackageAdvisories(packageJson, 'package.json')
308
+
309
+ expect(findings).toHaveLength(1)
310
+ expect(findings[0].category).toBe('suspicious_package')
311
+ expect(findings[0].severity).toBe('high')
312
+ expect(findings[0].title).toContain('Vulnerable package')
313
+ })
314
+
315
+ it('sets requiresAIValidation to false (authoritative data)', async () => {
316
+ mockFetch.mockResolvedValueOnce({
317
+ ok: true,
318
+ json: async () => ({
319
+ results: [
320
+ {
321
+ vulns: [createOSVVuln({ id: 'CVE-1', cvssScore: 5.0 })],
322
+ },
323
+ ],
324
+ }),
325
+ })
326
+
327
+ const packageJson = JSON.stringify({
328
+ dependencies: {
329
+ 'test-pkg': '1.0.0',
330
+ },
331
+ })
332
+
333
+ const findings = await checkPackageAdvisories(packageJson, 'package.json')
334
+
335
+ expect(findings).toHaveLength(1)
336
+ expect(findings[0].requiresAIValidation).toBe(false)
337
+ })
338
+
339
+ it('gracefully handles API failures', async () => {
340
+ mockFetch.mockRejectedValueOnce(new Error('Network error'))
341
+
342
+ const packageJson = JSON.stringify({
343
+ dependencies: {
344
+ 'some-pkg': '1.0.0',
345
+ },
346
+ })
347
+
348
+ const findings = await checkPackageAdvisories(packageJson, 'package.json')
349
+ expect(findings).toHaveLength(0) // Graceful degradation
350
+ })
351
+
352
+ it('aggregates multiple advisories for same package', async () => {
353
+ mockFetch.mockResolvedValueOnce({
354
+ ok: true,
355
+ json: async () => ({
356
+ results: [
357
+ {
358
+ vulns: [
359
+ createOSVVuln({ id: 'CVE-1', cvssScore: 5.0, summary: 'Low risk' }),
360
+ createOSVVuln({ id: 'CVE-2', cvssScore: 9.0, summary: 'High risk' }),
361
+ createOSVVuln({ id: 'CVE-3', cvssScore: 7.0, summary: 'Medium risk' }),
362
+ ],
363
+ },
364
+ ],
365
+ }),
366
+ })
367
+
368
+ const packageJson = JSON.stringify({
369
+ dependencies: {
370
+ 'multi-vuln-pkg': '1.0.0',
371
+ },
372
+ })
373
+
374
+ const findings = await checkPackageAdvisories(packageJson, 'package.json')
375
+
376
+ expect(findings).toHaveLength(1) // Single aggregated finding
377
+ expect(findings[0].severity).toBe('critical') // Uses highest severity
378
+ expect(findings[0].title).toContain('3 advisories')
379
+ expect(findings[0].description).toContain('CVE-1')
380
+ expect(findings[0].description).toContain('CVE-2')
381
+ expect(findings[0].description).toContain('CVE-3')
382
+ })
383
+ })
384
+ })
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Smart Auto-Dismiss Rules
3
+ *
4
+ * Instant filtering rules that don't require AI validation.
5
+ * These rules catch obvious false positives before sending to AI.
6
+ */
7
+
8
+ import type { Vulnerability } from '../../types'
9
+ import type { AutoDismissRule } from './types'
10
+ import {
11
+ isTestOrMockFile,
12
+ isExampleFile,
13
+ isScannerOrFixtureFile,
14
+ isEnvVarReference,
15
+ isPublicEndpoint,
16
+ isComment,
17
+ } from '../../utils/context-helpers'
18
+ import { getTierForCategory } from '../../tiers'
19
+
20
+ // ============================================================================
21
+ // Auto-Dismiss Rules
22
+ // ============================================================================
23
+
24
+ const AUTO_DISMISS_RULES: AutoDismissRule[] = [
25
+ // Test files - often contain intentional "vulnerable" patterns for testing
26
+ {
27
+ name: 'test_file',
28
+ check: (finding) => isTestOrMockFile(finding.filePath),
29
+ reason: 'Finding in test/mock file',
30
+ },
31
+
32
+ // Example/demo code - not production code
33
+ {
34
+ name: 'example_file',
35
+ check: (finding) => isExampleFile(finding.filePath),
36
+ reason: 'Finding in example/demo file',
37
+ },
38
+
39
+ // Documentation files
40
+ {
41
+ name: 'documentation_file',
42
+ check: (finding) => /\.(md|mdx|txt|rst)$/i.test(finding.filePath),
43
+ reason: 'Finding in documentation file',
44
+ },
45
+
46
+ // Scanner/security tool code itself
47
+ {
48
+ name: 'scanner_code',
49
+ check: (finding) => isScannerOrFixtureFile(finding.filePath),
50
+ reason: 'Finding in scanner/fixture code',
51
+ },
52
+
53
+ // Environment variable references (not hardcoded secrets)
54
+ {
55
+ name: 'env_var_reference',
56
+ check: (finding) => {
57
+ if (finding.category !== 'hardcoded_secret' && finding.category !== 'high_entropy_string') {
58
+ return false
59
+ }
60
+ return isEnvVarReference(finding.lineContent)
61
+ },
62
+ reason: 'Uses environment variable (not hardcoded)',
63
+ },
64
+
65
+ // Public health check endpoints don't need auth
66
+ {
67
+ name: 'health_check_endpoint',
68
+ check: (finding) => {
69
+ if (finding.category !== 'missing_auth') return false
70
+ return isPublicEndpoint(finding.lineContent, finding.filePath)
71
+ },
72
+ reason: 'Public health check endpoint (auth not required)',
73
+ },
74
+
75
+ // CSS/Tailwind classes flagged as high entropy
76
+ {
77
+ name: 'css_classes',
78
+ check: (finding) => {
79
+ if (finding.category !== 'high_entropy_string') return false
80
+ const cssIndicators = ['flex', 'grid', 'text-', 'bg-', 'px-', 'py-', 'rounded', 'shadow', 'hover:', 'dark:']
81
+ const lowerLine = finding.lineContent.toLowerCase()
82
+ const matchCount = cssIndicators.filter(ind => lowerLine.includes(ind)).length
83
+ return matchCount >= 2
84
+ },
85
+ reason: 'CSS/Tailwind classes (not a secret)',
86
+ },
87
+
88
+ // Comment lines shouldn't be flagged for most categories
89
+ {
90
+ name: 'comment_line',
91
+ check: (finding) => {
92
+ // Some categories are valid in comments (e.g., TODO security)
93
+ if (finding.category === 'ai_pattern') return false
94
+ return isComment(finding.lineContent)
95
+ },
96
+ reason: 'Code comment (not executable)',
97
+ },
98
+
99
+ // Info severity already - no need to validate
100
+ // BUT: Only auto-dismiss info-severity for Tier A (core) findings
101
+ // Tier B (ai_assisted) findings MUST go through AI validation even at info severity
102
+ // because detectors may have pre-downgraded them based on partial context
103
+ {
104
+ name: 'info_severity_core_only',
105
+ check: (finding) => {
106
+ if (finding.severity !== 'info') return false
107
+ // Only auto-dismiss info-severity for Tier A (core) findings
108
+ // Tier B should always go through AI for proper validation
109
+ const tier = getTierForCategory(finding.category, finding.layer)
110
+ return tier === 'core'
111
+ },
112
+ reason: 'Already info severity for core detector (low priority)',
113
+ },
114
+
115
+ // Generic success/error messages in ai_pattern
116
+ {
117
+ name: 'generic_message',
118
+ check: (finding) => {
119
+ if (finding.category !== 'ai_pattern') return false
120
+ const genericPatterns = [
121
+ /['"`](success|done|ok|completed|finished|saved|updated|deleted|created)['"`]/i,
122
+ /['"`]something went wrong['"`]/i,
123
+ /['"`]an error occurred['"`]/i,
124
+ /console\.(log|info|debug)\s*\(\s*['"`][^'"]+['"`]\s*\)/i,
125
+ ]
126
+ return genericPatterns.some(p => p.test(finding.lineContent))
127
+ },
128
+ reason: 'Generic UI message (not security-relevant)',
129
+ },
130
+
131
+ // Type definitions with 'any' - often necessary for third-party libs
132
+ {
133
+ name: 'type_definition_any',
134
+ check: (finding) => {
135
+ if (finding.category !== 'ai_pattern') return false
136
+ if (!finding.title.toLowerCase().includes('any')) return false
137
+ // Check if it's in a .d.ts file or type definition context
138
+ if (finding.filePath.includes('.d.ts')) return true
139
+ const typeDefPatterns = [/^type\s+\w+\s*=/, /^interface\s+\w+/, /declare\s+(const|let|var|function|class)/]
140
+ return typeDefPatterns.some(p => p.test(finding.lineContent.trim()))
141
+ },
142
+ reason: 'Type definition (not runtime code)',
143
+ },
144
+
145
+ // setTimeout/setInterval magic numbers - code style, not security
146
+ {
147
+ name: 'timeout_magic_number',
148
+ check: (finding) => {
149
+ if (finding.category !== 'ai_pattern') return false
150
+ return /set(Timeout|Interval)\s*\([^,]+,\s*\d+\s*\)/.test(finding.lineContent)
151
+ },
152
+ reason: 'Timeout value (code style, not security)',
153
+ },
154
+ ]
155
+
156
+ // ============================================================================
157
+ // Apply Auto-Dismiss Rules
158
+ // ============================================================================
159
+
160
+ /**
161
+ * Rules that should NOT be applied to direct-surface findings.
162
+ * These rules are designed to reduce AI validation costs, not to suppress findings entirely.
163
+ */
164
+ const VALIDATION_ONLY_RULES = new Set([
165
+ 'info_severity_core_only', // Info findings should surface, just not go through expensive AI validation
166
+ ])
167
+
168
+ /**
169
+ * Apply smart auto-dismiss rules to filter obvious false positives
170
+ * Returns findings that should be sent to AI validation
171
+ *
172
+ * @param findings - Array of vulnerabilities to filter
173
+ * @param mode - 'validation' applies all rules (for AI validation candidates),
174
+ * 'surface' excludes cost-saving rules (for direct-surface findings)
175
+ */
176
+ export function applyAutoDismissRules(
177
+ findings: Vulnerability[],
178
+ mode: 'validation' | 'surface' = 'validation'
179
+ ): {
180
+ toValidate: Vulnerability[]
181
+ dismissed: Array<{ finding: Vulnerability; rule: string; reason: string }>
182
+ } {
183
+ const toValidate: Vulnerability[] = []
184
+ const dismissed: Array<{ finding: Vulnerability; rule: string; reason: string }> = []
185
+
186
+ // Filter rules based on mode
187
+ const applicableRules = mode === 'surface'
188
+ ? AUTO_DISMISS_RULES.filter(rule => !VALIDATION_ONLY_RULES.has(rule.name))
189
+ : AUTO_DISMISS_RULES
190
+
191
+ for (const finding of findings) {
192
+ let wasDismissed = false
193
+
194
+ for (const rule of applicableRules) {
195
+ if (rule.check(finding)) {
196
+ dismissed.push({
197
+ finding,
198
+ rule: rule.name,
199
+ reason: rule.reason,
200
+ })
201
+ wasDismissed = true
202
+ break
203
+ }
204
+ }
205
+
206
+ if (!wasDismissed) {
207
+ toValidate.push(finding)
208
+ }
209
+ }
210
+
211
+ return { toValidate, dismissed }
212
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * AI Provider Client Factories
3
+ *
4
+ * Provides lazy-initialized clients for OpenAI and Anthropic APIs.
5
+ */
6
+
7
+ import Anthropic from '@anthropic-ai/sdk'
8
+ import OpenAI from 'openai'
9
+
10
+ // ============================================================================
11
+ // Anthropic Client
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Initialize Anthropic client
16
+ */
17
+ export function getAnthropicClient(): Anthropic {
18
+ const apiKey = process.env.ANTHROPIC_API_KEY
19
+ if (!apiKey) {
20
+ throw new Error('ANTHROPIC_API_KEY environment variable is not set')
21
+ }
22
+ return new Anthropic({ apiKey })
23
+ }
24
+
25
+ // ============================================================================
26
+ // OpenAI Client
27
+ // ============================================================================
28
+
29
+ // Singleton instance for connection reuse
30
+ let openaiClient: OpenAI | null = null
31
+
32
+ /**
33
+ * Initialize OpenAI client (singleton)
34
+ */
35
+ export function getOpenAIClient(): OpenAI {
36
+ if (!openaiClient) {
37
+ const apiKey = process.env.OPENAI_API_KEY
38
+ if (!apiKey) {
39
+ throw new Error('OPENAI_API_KEY environment variable is not set')
40
+ }
41
+ openaiClient = new OpenAI({ apiKey })
42
+ }
43
+ return openaiClient
44
+ }
45
+
46
+ // ============================================================================
47
+ // Pricing Constants
48
+ // ============================================================================
49
+
50
+ /**
51
+ * GPT-5-mini pricing constants (per 1M tokens)
52
+ */
53
+ export const GPT5_MINI_PRICING = {
54
+ input: 0.25, // $0.25 per 1M tokens
55
+ cached: 0.025, // $0.025 per 1M tokens (10% of input)
56
+ output: 2.00, // $2.00 per 1M tokens
57
+ }
58
+
59
+ /**
60
+ * Claude 3.5 Haiku pricing constants (per 1M tokens)
61
+ */
62
+ export const HAIKU_PRICING = {
63
+ input: 0.80, // $0.80 per 1M tokens
64
+ cacheWrite: 1.00, // $1.00 per 1M tokens (5m cache)
65
+ cacheRead: 0.08, // $0.08 per 1M tokens
66
+ output: 4.00, // $4.00 per 1M tokens
67
+ }
68
+
69
+ // ============================================================================
70
+ // Batching Configuration
71
+ // ============================================================================
72
+
73
+ /**
74
+ * Number of files to include in each API call (Phase 2 optimization)
75
+ * Batching multiple files reduces API overhead and leverages prompt caching better
76
+ */
77
+ export const FILES_PER_API_BATCH = 8
78
+
79
+ /**
80
+ * Number of API batches to process in parallel (Phase 3 optimization)
81
+ * Higher values = faster scans but more API load
82
+ * OpenAI/GPT-5-mini handles this well
83
+ */
84
+ export const PARALLEL_API_BATCHES = 6