kirograph 0.16.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +103 -1605
  3. package/dist/architecture/layers/dart.js +112 -0
  4. package/dist/architecture/layers/dart.js.map +7 -0
  5. package/dist/architecture/layers/index.js +3 -1
  6. package/dist/architecture/layers/index.js.map +2 -2
  7. package/dist/bin/banner.js +74 -6
  8. package/dist/bin/banner.js.map +2 -2
  9. package/dist/bin/commands/affected.js +40 -5
  10. package/dist/bin/commands/affected.js.map +2 -2
  11. package/dist/bin/commands/attack-surface.js +157 -0
  12. package/dist/bin/commands/attack-surface.js.map +7 -0
  13. package/dist/bin/commands/benchmark.js +206 -0
  14. package/dist/bin/commands/benchmark.js.map +7 -0
  15. package/dist/bin/commands/budget.js +70 -0
  16. package/dist/bin/commands/budget.js.map +7 -0
  17. package/dist/bin/commands/communities.js +77 -0
  18. package/dist/bin/commands/communities.js.map +7 -0
  19. package/dist/bin/commands/data.js +665 -0
  20. package/dist/bin/commands/data.js.map +7 -0
  21. package/dist/bin/commands/dep-confusion.js +119 -0
  22. package/dist/bin/commands/dep-confusion.js.map +7 -0
  23. package/dist/bin/commands/export.js +304 -10
  24. package/dist/bin/commands/export.js.map +2 -2
  25. package/dist/bin/commands/flows.js +97 -0
  26. package/dist/bin/commands/flows.js.map +7 -0
  27. package/dist/bin/commands/help.js +386 -100
  28. package/dist/bin/commands/help.js.map +2 -2
  29. package/dist/bin/commands/install.js +90 -8
  30. package/dist/bin/commands/install.js.map +2 -2
  31. package/dist/bin/commands/licenses.js +201 -0
  32. package/dist/bin/commands/licenses.js.map +7 -0
  33. package/dist/bin/commands/reachability.js +160 -0
  34. package/dist/bin/commands/reachability.js.map +7 -0
  35. package/dist/bin/commands/read.js +100 -0
  36. package/dist/bin/commands/read.js.map +7 -0
  37. package/dist/bin/commands/refactor.js +116 -0
  38. package/dist/bin/commands/refactor.js.map +7 -0
  39. package/dist/bin/commands/remediation.js +133 -0
  40. package/dist/bin/commands/remediation.js.map +7 -0
  41. package/dist/bin/commands/sbom.js +76 -0
  42. package/dist/bin/commands/sbom.js.map +7 -0
  43. package/dist/bin/commands/security-ci-report.js +267 -0
  44. package/dist/bin/commands/security-ci-report.js.map +7 -0
  45. package/dist/bin/commands/security-export.js +1349 -0
  46. package/dist/bin/commands/security-export.js.map +7 -0
  47. package/dist/bin/commands/security-flows.js +126 -0
  48. package/dist/bin/commands/security-flows.js.map +7 -0
  49. package/dist/bin/commands/security-secrets.js +170 -0
  50. package/dist/bin/commands/security-secrets.js.map +7 -0
  51. package/dist/bin/commands/security.js +193 -0
  52. package/dist/bin/commands/security.js.map +7 -0
  53. package/dist/bin/commands/staleness.js +144 -0
  54. package/dist/bin/commands/staleness.js.map +7 -0
  55. package/dist/bin/commands/status.js +102 -2
  56. package/dist/bin/commands/status.js.map +2 -2
  57. package/dist/bin/commands/supply-chain.js +138 -0
  58. package/dist/bin/commands/supply-chain.js.map +7 -0
  59. package/dist/bin/commands/uninit.js +109 -60
  60. package/dist/bin/commands/uninit.js.map +2 -2
  61. package/dist/bin/commands/vex.js +76 -0
  62. package/dist/bin/commands/vex.js.map +7 -0
  63. package/dist/bin/commands/vuln-suppress.js +95 -0
  64. package/dist/bin/commands/vuln-suppress.js.map +7 -0
  65. package/dist/bin/commands/vulns.js +382 -0
  66. package/dist/bin/commands/vulns.js.map +7 -0
  67. package/dist/bin/installer/auto-detect.js +125 -0
  68. package/dist/bin/installer/auto-detect.js.map +7 -0
  69. package/dist/bin/installer/cli-agent.js +28 -5
  70. package/dist/bin/installer/cli-agent.js.map +2 -2
  71. package/dist/bin/installer/common.js +47 -0
  72. package/dist/bin/installer/common.js.map +2 -2
  73. package/dist/bin/installer/config-prompt.js +39 -1
  74. package/dist/bin/installer/config-prompt.js.map +2 -2
  75. package/dist/bin/installer/detect.js +309 -0
  76. package/dist/bin/installer/detect.js.map +7 -0
  77. package/dist/bin/installer/index.js +36 -1
  78. package/dist/bin/installer/index.js.map +2 -2
  79. package/dist/bin/installer/instructions.js +195 -3
  80. package/dist/bin/installer/instructions.js.map +2 -2
  81. package/dist/bin/installer/steering.js +440 -0
  82. package/dist/bin/installer/steering.js.map +2 -2
  83. package/dist/bin/installer/targets/aider.js +3 -3
  84. package/dist/bin/installer/targets/aider.js.map +2 -2
  85. package/dist/bin/installer/targets/amp.js +3 -3
  86. package/dist/bin/installer/targets/amp.js.map +2 -2
  87. package/dist/bin/installer/targets/antigravity.js +35 -6
  88. package/dist/bin/installer/targets/antigravity.js.map +2 -2
  89. package/dist/bin/installer/targets/augment.js +3 -3
  90. package/dist/bin/installer/targets/augment.js.map +2 -2
  91. package/dist/bin/installer/targets/claude.js +49 -5
  92. package/dist/bin/installer/targets/claude.js.map +2 -2
  93. package/dist/bin/installer/targets/cline.js +32 -6
  94. package/dist/bin/installer/targets/cline.js.map +2 -2
  95. package/dist/bin/installer/targets/codex.js +64 -12
  96. package/dist/bin/installer/targets/codex.js.map +2 -2
  97. package/dist/bin/installer/targets/continue.js +2 -2
  98. package/dist/bin/installer/targets/continue.js.map +2 -2
  99. package/dist/bin/installer/targets/copilot-cli.js +101 -0
  100. package/dist/bin/installer/targets/copilot-cli.js.map +7 -0
  101. package/dist/bin/installer/targets/copilot.js +31 -5
  102. package/dist/bin/installer/targets/copilot.js.map +2 -2
  103. package/dist/bin/installer/targets/cursor.js +4 -4
  104. package/dist/bin/installer/targets/cursor.js.map +2 -2
  105. package/dist/bin/installer/targets/devin.js +2 -2
  106. package/dist/bin/installer/targets/devin.js.map +2 -2
  107. package/dist/bin/installer/targets/gemini-cli.js +2 -2
  108. package/dist/bin/installer/targets/gemini-cli.js.map +2 -2
  109. package/dist/bin/installer/targets/generic.js +2 -14
  110. package/dist/bin/installer/targets/generic.js.map +2 -2
  111. package/dist/bin/installer/targets/goose.js +3 -3
  112. package/dist/bin/installer/targets/goose.js.map +2 -2
  113. package/dist/bin/installer/targets/index.js +250 -0
  114. package/dist/bin/installer/targets/index.js.map +2 -2
  115. package/dist/bin/installer/targets/junie.js +2 -2
  116. package/dist/bin/installer/targets/junie.js.map +2 -2
  117. package/dist/bin/installer/targets/kilo.js +2 -2
  118. package/dist/bin/installer/targets/kilo.js.map +2 -2
  119. package/dist/bin/installer/targets/kiro.js +3 -3
  120. package/dist/bin/installer/targets/kiro.js.map +2 -2
  121. package/dist/bin/installer/targets/opencode.js +2 -2
  122. package/dist/bin/installer/targets/opencode.js.map +2 -2
  123. package/dist/bin/installer/targets/openhands.js +3 -3
  124. package/dist/bin/installer/targets/openhands.js.map +2 -2
  125. package/dist/bin/installer/targets/qoder.js +91 -0
  126. package/dist/bin/installer/targets/qoder.js.map +7 -0
  127. package/dist/bin/installer/targets/qwen.js +96 -0
  128. package/dist/bin/installer/targets/qwen.js.map +7 -0
  129. package/dist/bin/installer/targets/replit.js +3 -3
  130. package/dist/bin/installer/targets/replit.js.map +2 -2
  131. package/dist/bin/installer/targets/roo.js +2 -2
  132. package/dist/bin/installer/targets/roo.js.map +2 -2
  133. package/dist/bin/installer/targets/tabnine.js +3 -3
  134. package/dist/bin/installer/targets/tabnine.js.map +2 -2
  135. package/dist/bin/installer/targets/trae.js +5 -5
  136. package/dist/bin/installer/targets/trae.js.map +2 -2
  137. package/dist/bin/installer/targets/warp.js +2 -2
  138. package/dist/bin/installer/targets/warp.js.map +2 -2
  139. package/dist/bin/installer/targets/windsurf.js +35 -6
  140. package/dist/bin/installer/targets/windsurf.js.map +2 -2
  141. package/dist/bin/kirograph.js +74 -6
  142. package/dist/bin/kirograph.js.map +3 -3
  143. package/dist/bin/progress.js +30 -0
  144. package/dist/bin/progress.js.map +2 -2
  145. package/dist/compression/naive-cost.js +25 -0
  146. package/dist/compression/naive-cost.js.map +2 -2
  147. package/dist/compression/tracker.js +91 -2
  148. package/dist/compression/tracker.js.map +2 -2
  149. package/dist/compression/types.js.map +1 -1
  150. package/dist/config.js +126 -3
  151. package/dist/config.js.map +2 -2
  152. package/dist/core/pipeline.js +92 -4
  153. package/dist/core/pipeline.js.map +2 -2
  154. package/dist/data/filters.js +105 -0
  155. package/dist/data/filters.js.map +7 -0
  156. package/dist/data/indexer.js +225 -0
  157. package/dist/data/indexer.js.map +7 -0
  158. package/dist/data/linker.js +150 -0
  159. package/dist/data/linker.js.map +7 -0
  160. package/dist/data/lint.js +109 -0
  161. package/dist/data/lint.js.map +7 -0
  162. package/dist/data/parsers/csv.js +132 -0
  163. package/dist/data/parsers/csv.js.map +7 -0
  164. package/dist/data/parsers/excel.js +88 -0
  165. package/dist/data/parsers/excel.js.map +7 -0
  166. package/dist/data/parsers/index.js +79 -0
  167. package/dist/data/parsers/index.js.map +7 -0
  168. package/dist/data/parsers/json-array.js +89 -0
  169. package/dist/data/parsers/json-array.js.map +7 -0
  170. package/dist/data/parsers/jsonl.js +84 -0
  171. package/dist/data/parsers/jsonl.js.map +7 -0
  172. package/dist/data/parsers/parquet.js +95 -0
  173. package/dist/data/parsers/parquet.js.map +7 -0
  174. package/dist/data/profiler.js +182 -0
  175. package/dist/data/profiler.js.map +7 -0
  176. package/dist/data/queries.js +512 -0
  177. package/dist/data/queries.js.map +7 -0
  178. package/dist/data/types.js +17 -0
  179. package/dist/data/types.js.map +7 -0
  180. package/dist/db/data-schema.sql +61 -0
  181. package/dist/db/database.js +67 -5
  182. package/dist/db/database.js.map +2 -2
  183. package/dist/db/memory-schema.sql +5 -1
  184. package/dist/db/schema.sql +3 -1
  185. package/dist/db/security-schema.sql +72 -0
  186. package/dist/extraction/extractor.js +81 -0
  187. package/dist/extraction/extractor.js.map +2 -2
  188. package/dist/extraction/grammars.js +32 -8
  189. package/dist/extraction/grammars.js.map +2 -2
  190. package/dist/extraction/languages.js +43 -1
  191. package/dist/extraction/languages.js.map +2 -2
  192. package/dist/extraction/notebook.js +300 -0
  193. package/dist/extraction/notebook.js.map +7 -0
  194. package/dist/extraction/wasm/tree-sitter-astro.wasm +0 -0
  195. package/dist/extraction/wasm/tree-sitter-gdscript.wasm +0 -0
  196. package/dist/extraction/wasm/tree-sitter-julia.wasm +0 -0
  197. package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  198. package/dist/extraction/wasm/tree-sitter-perl.wasm +0 -0
  199. package/dist/extraction/wasm/tree-sitter-powershell.wasm +0 -0
  200. package/dist/extraction/wasm/tree-sitter-r.wasm +0 -0
  201. package/dist/extraction/wasm/tree-sitter-sql.wasm +0 -0
  202. package/dist/extraction/wasm/tree-sitter-verilog.wasm +0 -0
  203. package/dist/frameworks/flutter.js +130 -0
  204. package/dist/frameworks/flutter.js.map +7 -0
  205. package/dist/frameworks/index.js +7 -1
  206. package/dist/frameworks/index.js.map +3 -3
  207. package/dist/graph/communities.js +317 -0
  208. package/dist/graph/communities.js.map +7 -0
  209. package/dist/graph/flows.js +138 -0
  210. package/dist/graph/flows.js.map +7 -0
  211. package/dist/graph/refactor.js +155 -0
  212. package/dist/graph/refactor.js.map +7 -0
  213. package/dist/index.js +1 -1
  214. package/dist/index.js.map +2 -2
  215. package/dist/mcp/cache.js +172 -0
  216. package/dist/mcp/cache.js.map +7 -0
  217. package/dist/mcp/read-modes.js +295 -0
  218. package/dist/mcp/read-modes.js.map +7 -0
  219. package/dist/mcp/server.js +2 -1
  220. package/dist/mcp/server.js.map +2 -2
  221. package/dist/mcp/tool-names.js +29 -1
  222. package/dist/mcp/tool-names.js.map +2 -2
  223. package/dist/mcp/tools.js +1588 -6
  224. package/dist/mcp/tools.js.map +3 -3
  225. package/dist/memory/database.js +32 -2
  226. package/dist/memory/database.js.map +2 -2
  227. package/dist/memory/types.js.map +1 -1
  228. package/dist/resolution/bridges/android-rn.js +298 -0
  229. package/dist/resolution/bridges/android-rn.js.map +7 -0
  230. package/dist/resolution/bridges/expo-modules.js +155 -0
  231. package/dist/resolution/bridges/expo-modules.js.map +7 -0
  232. package/dist/resolution/bridges/flutter-channel.js +385 -0
  233. package/dist/resolution/bridges/flutter-channel.js.map +7 -0
  234. package/dist/resolution/bridges/index.js +80 -0
  235. package/dist/resolution/bridges/index.js.map +7 -0
  236. package/dist/resolution/bridges/native-events.js +188 -0
  237. package/dist/resolution/bridges/native-events.js.map +7 -0
  238. package/dist/resolution/bridges/native-views.js +244 -0
  239. package/dist/resolution/bridges/native-views.js.map +7 -0
  240. package/dist/resolution/bridges/react-native.js +161 -0
  241. package/dist/resolution/bridges/react-native.js.map +7 -0
  242. package/dist/resolution/bridges/swift-objc.js +227 -0
  243. package/dist/resolution/bridges/swift-objc.js.map +7 -0
  244. package/dist/resolution/bridges/turbomodules.js +216 -0
  245. package/dist/resolution/bridges/turbomodules.js.map +7 -0
  246. package/dist/resolution/index.js +61 -2
  247. package/dist/resolution/index.js.map +2 -2
  248. package/dist/security/attack-surface.js +164 -0
  249. package/dist/security/attack-surface.js.map +7 -0
  250. package/dist/security/context-warnings.js +123 -0
  251. package/dist/security/context-warnings.js.map +7 -0
  252. package/dist/security/context-warnings.test.js +300 -0
  253. package/dist/security/context-warnings.test.js.map +7 -0
  254. package/dist/security/data-flows.js +228 -0
  255. package/dist/security/data-flows.js.map +7 -0
  256. package/dist/security/dep-confusion.js +255 -0
  257. package/dist/security/dep-confusion.js.map +7 -0
  258. package/dist/security/errors.js +51 -0
  259. package/dist/security/errors.js.map +7 -0
  260. package/dist/security/export/fix-suggestions.js +58 -0
  261. package/dist/security/export/fix-suggestions.js.map +7 -0
  262. package/dist/security/export/fix-suggestions.test.js +74 -0
  263. package/dist/security/export/fix-suggestions.test.js.map +7 -0
  264. package/dist/security/export/sbom.js +230 -0
  265. package/dist/security/export/sbom.js.map +7 -0
  266. package/dist/security/export/sbom.test.js +288 -0
  267. package/dist/security/export/sbom.test.js.map +7 -0
  268. package/dist/security/export/serialization.js +142 -0
  269. package/dist/security/export/serialization.js.map +7 -0
  270. package/dist/security/export/serialization.test.js +310 -0
  271. package/dist/security/export/serialization.test.js.map +7 -0
  272. package/dist/security/export/vex.js +220 -0
  273. package/dist/security/export/vex.js.map +7 -0
  274. package/dist/security/export/vex.test.js +278 -0
  275. package/dist/security/export/vex.test.js.map +7 -0
  276. package/dist/security/index.js +67 -0
  277. package/dist/security/index.js.map +7 -0
  278. package/dist/security/integrator-transitives.test.js +359 -0
  279. package/dist/security/integrator-transitives.test.js.map +7 -0
  280. package/dist/security/integrator.js +491 -0
  281. package/dist/security/integrator.js.map +7 -0
  282. package/dist/security/integrator.test.js +237 -0
  283. package/dist/security/integrator.test.js.map +7 -0
  284. package/dist/security/license.js +70 -0
  285. package/dist/security/license.js.map +7 -0
  286. package/dist/security/manifest/adapter.js +243 -0
  287. package/dist/security/manifest/adapter.js.map +7 -0
  288. package/dist/security/manifest/adapter.test.js +272 -0
  289. package/dist/security/manifest/adapter.test.js.map +7 -0
  290. package/dist/security/manifest/parser.js +376 -0
  291. package/dist/security/manifest/parser.js.map +7 -0
  292. package/dist/security/manifest/parser.test.js +110 -0
  293. package/dist/security/manifest/parser.test.js.map +7 -0
  294. package/dist/security/manifest/plugins/cargo.js +233 -0
  295. package/dist/security/manifest/plugins/cargo.js.map +7 -0
  296. package/dist/security/manifest/plugins/cargo.test.js +304 -0
  297. package/dist/security/manifest/plugins/cargo.test.js.map +7 -0
  298. package/dist/security/manifest/plugins/composer.js +152 -0
  299. package/dist/security/manifest/plugins/composer.js.map +7 -0
  300. package/dist/security/manifest/plugins/go.js +146 -0
  301. package/dist/security/manifest/plugins/go.js.map +7 -0
  302. package/dist/security/manifest/plugins/go.test.js +341 -0
  303. package/dist/security/manifest/plugins/go.test.js.map +7 -0
  304. package/dist/security/manifest/plugins/gradle.js +159 -0
  305. package/dist/security/manifest/plugins/gradle.js.map +7 -0
  306. package/dist/security/manifest/plugins/hex.js +159 -0
  307. package/dist/security/manifest/plugins/hex.js.map +7 -0
  308. package/dist/security/manifest/plugins/maven.js +128 -0
  309. package/dist/security/manifest/plugins/maven.js.map +7 -0
  310. package/dist/security/manifest/plugins/maven.test.js +389 -0
  311. package/dist/security/manifest/plugins/maven.test.js.map +7 -0
  312. package/dist/security/manifest/plugins/npm.js +284 -0
  313. package/dist/security/manifest/plugins/npm.js.map +7 -0
  314. package/dist/security/manifest/plugins/npm.test.js +298 -0
  315. package/dist/security/manifest/plugins/npm.test.js.map +7 -0
  316. package/dist/security/manifest/plugins/nuget.js +186 -0
  317. package/dist/security/manifest/plugins/nuget.js.map +7 -0
  318. package/dist/security/manifest/plugins/pip.js +123 -0
  319. package/dist/security/manifest/plugins/pip.js.map +7 -0
  320. package/dist/security/manifest/plugins/pip.test.js +208 -0
  321. package/dist/security/manifest/plugins/pip.test.js.map +7 -0
  322. package/dist/security/manifest/plugins/pubspec.js +212 -0
  323. package/dist/security/manifest/plugins/pubspec.js.map +7 -0
  324. package/dist/security/manifest/plugins/pyproject.js +261 -0
  325. package/dist/security/manifest/plugins/pyproject.js.map +7 -0
  326. package/dist/security/manifest/plugins/rubygems.js +185 -0
  327. package/dist/security/manifest/plugins/rubygems.js.map +7 -0
  328. package/dist/security/manifest/plugins/swift.js +200 -0
  329. package/dist/security/manifest/plugins/swift.js.map +7 -0
  330. package/dist/security/owasp.js +104 -0
  331. package/dist/security/owasp.js.map +7 -0
  332. package/dist/security/pipeline.js +125 -0
  333. package/dist/security/pipeline.js.map +7 -0
  334. package/dist/security/reachability.js +276 -0
  335. package/dist/security/reachability.js.map +7 -0
  336. package/dist/security/reachability.test.js +394 -0
  337. package/dist/security/reachability.test.js.map +7 -0
  338. package/dist/security/remediation.js +126 -0
  339. package/dist/security/remediation.js.map +7 -0
  340. package/dist/security/secrets.js +176 -0
  341. package/dist/security/secrets.js.map +7 -0
  342. package/dist/security/staleness.js +231 -0
  343. package/dist/security/staleness.js.map +7 -0
  344. package/dist/security/supply-chain.js +301 -0
  345. package/dist/security/supply-chain.js.map +7 -0
  346. package/dist/security/suppressions.js +106 -0
  347. package/dist/security/suppressions.js.map +7 -0
  348. package/dist/security/types.js +17 -0
  349. package/dist/security/types.js.map +7 -0
  350. package/dist/security/vuln/client.js +355 -0
  351. package/dist/security/vuln/client.js.map +7 -0
  352. package/dist/security/vuln/client.test.js +288 -0
  353. package/dist/security/vuln/client.test.js.map +7 -0
  354. package/dist/security/vuln/epss-client.js +95 -0
  355. package/dist/security/vuln/epss-client.js.map +7 -0
  356. package/dist/security/vuln/index.js +29 -0
  357. package/dist/security/vuln/index.js.map +7 -0
  358. package/dist/security/vuln/osv-adapter.js +321 -0
  359. package/dist/security/vuln/osv-adapter.js.map +7 -0
  360. package/dist/security/vuln/osv-adapter.test.js +391 -0
  361. package/dist/security/vuln/osv-adapter.test.js.map +7 -0
  362. package/dist/security/vuln/types.js +17 -0
  363. package/dist/security/vuln/types.js.map +7 -0
  364. package/dist/types.js.map +2 -2
  365. package/package.json +5 -2
@@ -0,0 +1,355 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var client_exports = {};
20
+ __export(client_exports, {
21
+ VulnerabilityDatabaseClient: () => VulnerabilityDatabaseClient,
22
+ computeRiskScore: () => computeRiskScore
23
+ });
24
+ module.exports = __toCommonJS(client_exports);
25
+ var import_errors = require("../../errors");
26
+ var import_epss_client = require("./epss-client");
27
+ const BATCH_CHUNK_SIZE = 1e3;
28
+ function computeRiskScore(cvss, epss, verdict, staleness) {
29
+ const cvssNormalized = (cvss ?? 5) / 10;
30
+ const epssValue = epss ?? 0;
31
+ let reachabilityFactor;
32
+ if (verdict === "affected") {
33
+ reachabilityFactor = 1;
34
+ } else if (verdict === "under_investigation") {
35
+ reachabilityFactor = 0.5;
36
+ } else if (verdict === "not_affected") {
37
+ reachabilityFactor = 0.1;
38
+ } else {
39
+ reachabilityFactor = 0.3;
40
+ }
41
+ const stalenessBonus = 1 + (staleness ?? 0) * 0.2;
42
+ const raw = reachabilityFactor * (0.4 * cvssNormalized + 0.6 * epssValue) * stalenessBonus;
43
+ return Math.min(raw * 10, 10);
44
+ }
45
+ class VulnerabilityDatabaseClient {
46
+ constructor(adapters, db, timeoutMs = 3e4) {
47
+ this.adapters = adapters;
48
+ this.db = db;
49
+ this.timeoutMs = timeoutMs;
50
+ }
51
+ /**
52
+ * Enrich all Dependency_Nodes with vulnerability data.
53
+ *
54
+ * Uses batch queries (up to 1000 per HTTP request) when the adapter supports
55
+ * `queryBatch`. Falls back to sequential single queries per dependency if batch
56
+ * is unavailable or fails. Results are deduplicated by CVE ID across adapters.
57
+ */
58
+ async enrichAll() {
59
+ const rawDb = this.db.getRawDb();
60
+ const result = {
61
+ vulnerabilitiesFound: 0,
62
+ dependenciesChecked: 0,
63
+ errors: [],
64
+ staleNodes: []
65
+ };
66
+ const depRows = rawDb.all(
67
+ `SELECT node_id, ecosystem, package_name, resolved_version, declared_constraint
68
+ FROM sec_dependencies`
69
+ );
70
+ const queryableDeps = depRows.filter((d) => d.resolved_version || d.declared_constraint);
71
+ for (const adapter of this.adapters) {
72
+ if (adapter.queryBatch) {
73
+ await this.enrichAllBatch(adapter, queryableDeps, result);
74
+ } else {
75
+ await this.enrichAllSequential(adapter, queryableDeps, result);
76
+ }
77
+ }
78
+ const now = Date.now();
79
+ for (const dep of queryableDeps) {
80
+ rawDb.run(
81
+ `UPDATE sec_dependencies SET last_vuln_check = ? WHERE node_id = ?`,
82
+ [now, dep.node_id]
83
+ );
84
+ }
85
+ result.dependenciesChecked = queryableDeps.length;
86
+ await this.enrichEpss(rawDb);
87
+ await this.computeRiskScores(rawDb);
88
+ return result;
89
+ }
90
+ /**
91
+ * Fetch EPSS scores for all stored vulnerabilities and update the database.
92
+ */
93
+ async enrichEpss(rawDb) {
94
+ try {
95
+ const cveRows = rawDb.all(
96
+ `SELECT cve_id FROM sec_vulnerabilities`
97
+ );
98
+ if (cveRows.length === 0) {
99
+ return;
100
+ }
101
+ const cveIds = cveRows.map((r) => r.cve_id);
102
+ const epssClient = new import_epss_client.EpssClient();
103
+ const scores = await epssClient.fetchScores(cveIds);
104
+ if (scores.size === 0) {
105
+ return;
106
+ }
107
+ const fetchedAt = Date.now();
108
+ for (const [cveId, { score, percentile }] of scores) {
109
+ rawDb.run(
110
+ `UPDATE sec_vulnerabilities SET epss_score = ?, epss_percentile = ?, epss_fetched_at = ? WHERE cve_id = ?`,
111
+ [score, percentile, fetchedAt, cveId]
112
+ );
113
+ }
114
+ } catch (error) {
115
+ const msg = error instanceof Error ? error.message : String(error);
116
+ (0, import_errors.logWarn)(`[sec:epss] EPSS enrichment failed (non-critical): ${msg}`);
117
+ }
118
+ }
119
+ /**
120
+ * Compute and persist combined risk scores for all stored vulnerabilities.
121
+ * Called after EPSS enrichment so epss_score values are up to date.
122
+ */
123
+ async computeRiskScores(rawDb) {
124
+ const rows = rawDb.all(`
125
+ SELECT v.node_id, v.severity_score, v.epss_score,
126
+ r.verdict,
127
+ d.staleness_score
128
+ FROM sec_vulnerabilities v
129
+ LEFT JOIN edges e ON e.target = v.node_id AND e.kind = 'has_vulnerability'
130
+ LEFT JOIN sec_dependencies d ON d.node_id = e.source
131
+ LEFT JOIN sec_reachability r ON r.vulnerability_node_id = v.node_id
132
+ `);
133
+ for (const row of rows) {
134
+ const score = computeRiskScore(row.severity_score, row.epss_score, row.verdict, row.staleness_score);
135
+ rawDb.run(`UPDATE sec_vulnerabilities SET risk_score = ? WHERE node_id = ?`, [score, row.node_id]);
136
+ }
137
+ }
138
+ /**
139
+ * Batch enrichment path: groups deps into chunks of BATCH_CHUNK_SIZE and sends
140
+ * one HTTP request per chunk. Falls back to sequential on batch failure.
141
+ */
142
+ async enrichAllBatch(adapter, deps, result) {
143
+ const rawDb = this.db.getRawDb();
144
+ for (let i = 0; i < deps.length; i += BATCH_CHUNK_SIZE) {
145
+ const chunk = deps.slice(i, i + BATCH_CHUNK_SIZE);
146
+ const queries = chunk.map((dep) => ({
147
+ ecosystem: dep.ecosystem,
148
+ packageName: dep.package_name,
149
+ version: dep.resolved_version || dep.declared_constraint
150
+ }));
151
+ let batchResults;
152
+ try {
153
+ const controller = new AbortController();
154
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
155
+ try {
156
+ batchResults = await adapter.queryBatch(queries, controller.signal);
157
+ } finally {
158
+ clearTimeout(timeout);
159
+ }
160
+ } catch (error) {
161
+ const msg = error instanceof Error ? error.message : String(error);
162
+ (0, import_errors.logWarn)(`[sec:vuln] ${adapter.name} batch query failed (${msg}), falling back to sequential for chunk of ${chunk.length}`);
163
+ await this.enrichAllSequential(adapter, chunk, result);
164
+ continue;
165
+ }
166
+ for (let j = 0; j < chunk.length; j++) {
167
+ const dep = chunk[j];
168
+ const cveRecords = batchResults[j] ?? [];
169
+ const uniqueCves = this.deduplicateByCveId(cveRecords);
170
+ for (const cve of uniqueCves) {
171
+ this.upsertVulnerabilityNode(rawDb, cve, dep.node_id);
172
+ result.vulnerabilitiesFound++;
173
+ }
174
+ }
175
+ }
176
+ }
177
+ /**
178
+ * Sequential enrichment path: queries one dependency at a time.
179
+ * Used when the adapter does not support queryBatch, or as a fallback.
180
+ */
181
+ async enrichAllSequential(adapter, deps, result) {
182
+ const rawDb = this.db.getRawDb();
183
+ for (const dep of deps) {
184
+ const version = dep.resolved_version || dep.declared_constraint;
185
+ const cveRecords = await this.queryAdaptersForDependency(dep, version, result, [adapter]);
186
+ const uniqueCves = this.deduplicateByCveId(cveRecords);
187
+ for (const cve of uniqueCves) {
188
+ this.upsertVulnerabilityNode(rawDb, cve, dep.node_id);
189
+ result.vulnerabilitiesFound++;
190
+ }
191
+ }
192
+ }
193
+ /**
194
+ * Enrich a single Dependency_Node with vulnerability data.
195
+ *
196
+ * @param dependencyNodeId - The node ID of the dependency to enrich
197
+ * @returns Array of CVE records found for this dependency
198
+ */
199
+ async enrichOne(dependencyNodeId) {
200
+ const rawDb = this.db.getRawDb();
201
+ const dep = rawDb.get(
202
+ `SELECT node_id, ecosystem, package_name, resolved_version, declared_constraint
203
+ FROM sec_dependencies WHERE node_id = ?`,
204
+ [dependencyNodeId]
205
+ );
206
+ if (!dep) {
207
+ return [];
208
+ }
209
+ const version = dep.resolved_version || dep.declared_constraint;
210
+ if (!version) {
211
+ return [];
212
+ }
213
+ const enrichResult = {
214
+ vulnerabilitiesFound: 0,
215
+ dependenciesChecked: 0,
216
+ errors: [],
217
+ staleNodes: []
218
+ };
219
+ const cveRecords = await this.queryAdaptersForDependency(
220
+ dep,
221
+ version,
222
+ enrichResult
223
+ );
224
+ const uniqueCves = this.deduplicateByCveId(cveRecords);
225
+ for (const cve of uniqueCves) {
226
+ this.upsertVulnerabilityNode(rawDb, cve, dep.node_id);
227
+ }
228
+ const now = Date.now();
229
+ rawDb.run(
230
+ `UPDATE sec_dependencies SET last_vuln_check = ? WHERE node_id = ?`,
231
+ [now, dep.node_id]
232
+ );
233
+ return uniqueCves;
234
+ }
235
+ /**
236
+ * Query a set of adapters for a single dependency.
237
+ * Handles timeouts and unreachable databases gracefully.
238
+ * Defaults to all configured adapters when adapterList is omitted.
239
+ */
240
+ async queryAdaptersForDependency(dep, version, result, adapterList = this.adapters) {
241
+ const allRecords = [];
242
+ for (const adapter of adapterList) {
243
+ try {
244
+ const controller = new AbortController();
245
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
246
+ try {
247
+ const records = await adapter.query(
248
+ dep.ecosystem,
249
+ dep.package_name,
250
+ version,
251
+ controller.signal
252
+ );
253
+ allRecords.push(...records);
254
+ } finally {
255
+ clearTimeout(timeout);
256
+ }
257
+ } catch (error) {
258
+ const errorMessage = error instanceof Error ? error.message : String(error);
259
+ (0, import_errors.logError)(
260
+ `[sec:vuln] Failed to query ${adapter.name} for ${dep.package_name}@${version}: ${errorMessage}`
261
+ );
262
+ result.errors.push({
263
+ dependency: dep.package_name,
264
+ database: adapter.name,
265
+ error: errorMessage
266
+ });
267
+ this.markDependencyStale(dep.node_id, result);
268
+ }
269
+ }
270
+ return allRecords;
271
+ }
272
+ /**
273
+ * Mark a dependency node as having stale vulnerability data.
274
+ * Retains cached data but sets the stale flag with timestamp.
275
+ */
276
+ markDependencyStale(nodeId, result) {
277
+ const rawDb = this.db.getRawDb();
278
+ const now = Date.now();
279
+ rawDb.run(
280
+ `UPDATE sec_dependencies
281
+ SET vuln_data_stale = 1, vuln_data_stale_since = ?
282
+ WHERE node_id = ?`,
283
+ [now, nodeId]
284
+ );
285
+ if (!result.staleNodes.includes(nodeId)) {
286
+ result.staleNodes.push(nodeId);
287
+ }
288
+ (0, import_errors.logWarn)(
289
+ `[sec:vuln] Vulnerability data marked stale for dependency node ${nodeId}`
290
+ );
291
+ }
292
+ /**
293
+ * Deduplicate CVE records by their ID.
294
+ * When the same CVE is reported by multiple databases, keep the first occurrence.
295
+ */
296
+ deduplicateByCveId(records) {
297
+ const seen = /* @__PURE__ */ new Map();
298
+ for (const record of records) {
299
+ if (!seen.has(record.id)) {
300
+ seen.set(record.id, record);
301
+ }
302
+ }
303
+ return [...seen.values()];
304
+ }
305
+ /**
306
+ * Create or update a Vulnerability_Node and link it to the affected dependency.
307
+ *
308
+ * - Creates a node in `nodes` table with kind='vulnerability', ID format `vuln:<cve_id>`
309
+ * - Inserts a row in `sec_vulnerabilities` with CVE metadata
310
+ * - Creates a `has_vulnerability` edge from the Dependency_Node to the Vulnerability_Node
311
+ */
312
+ upsertVulnerabilityNode(rawDb, cve, dependencyNodeId) {
313
+ const vulnNodeId = `vuln:${cve.id}`;
314
+ const now = Date.now();
315
+ const summary = cve.summary.length > 500 ? cve.summary.slice(0, 500) : cve.summary;
316
+ const sourceDatabase = this.adapters.length > 0 ? this.adapters[0].name : "unknown";
317
+ rawDb.run(
318
+ `INSERT OR REPLACE INTO nodes
319
+ (id, kind, name, qualified_name, file_path, language,
320
+ start_line, end_line, start_column, end_column,
321
+ is_exported, is_async, is_static, is_abstract, updated_at)
322
+ VALUES (?, 'vulnerability', ?, ?, '', 'unknown', 0, 0, 0, 0, 0, 0, 0, 0, ?)`,
323
+ [vulnNodeId, cve.id, cve.id, now]
324
+ );
325
+ const affectedRangesJson = JSON.stringify(cve.affectedVersionRanges);
326
+ rawDb.run(
327
+ `INSERT OR REPLACE INTO sec_vulnerabilities
328
+ (node_id, cve_id, severity_score, affected_ranges, fixed_version, summary, source_database)
329
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
330
+ [
331
+ vulnNodeId,
332
+ cve.id,
333
+ cve.severity,
334
+ affectedRangesJson,
335
+ cve.fixedVersion ?? null,
336
+ summary,
337
+ sourceDatabase
338
+ ]
339
+ );
340
+ const edge = {
341
+ source: dependencyNodeId,
342
+ target: vulnNodeId,
343
+ kind: "has_vulnerability",
344
+ confidence: "extracted",
345
+ confidenceScore: 1
346
+ };
347
+ this.db.insertEdge(edge);
348
+ }
349
+ }
350
+ // Annotate the CommonJS export names for ESM import in node:
351
+ 0 && (module.exports = {
352
+ VulnerabilityDatabaseClient,
353
+ computeRiskScore
354
+ });
355
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/security/vuln/client.ts"],
4
+ "sourcesContent": ["/**\n * VulnerabilityDatabaseClient\n *\n * Orchestrates vulnerability enrichment by querying configured databases,\n * merging and deduplicating results by CVE identifier, creating Vulnerability_Nodes\n * linked to affected Dependency_Nodes, and handling unreachable databases gracefully.\n *\n * Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.7\n */\n\nimport type { GraphDatabase } from '../../db/database';\nimport type { Edge } from '../../types';\nimport type { CVERecord, EnrichmentResult } from '../types';\nimport type { VulnDatabaseAdapter, BatchQuery } from './types';\nimport { logError, logWarn } from '../../errors';\nimport { EpssClient } from './epss-client';\n\nconst BATCH_CHUNK_SIZE = 1000;\n\n/**\n * Compute a combined risk score (0.0\u201310.0) for a vulnerability.\n *\n * Formula:\n * risk = reachability_factor \u00D7 (0.4 \u00D7 cvss_normalized + 0.6 \u00D7 epss) \u00D7 staleness_bonus\n * scaled to 0\u201310: min(risk \u00D7 10, 10.0)\n *\n * @param cvss - CVSS base score (0.0\u201310.0), null treated as 5.0\n * @param epss - EPSS exploitation probability (0.0\u20131.0), null treated as 0.0\n * @param verdict - Reachability verdict or null/undefined\n * @param staleness - Dependency staleness score (0.0\u20131.0), null treated as 0.0\n */\nexport function computeRiskScore(\n cvss: number | null | undefined,\n epss: number | null | undefined,\n verdict: string | null | undefined,\n staleness: number | null | undefined,\n): number {\n const cvssNormalized = (cvss ?? 5.0) / 10.0;\n const epssValue = epss ?? 0.0;\n\n let reachabilityFactor: number;\n if (verdict === 'affected') {\n reachabilityFactor = 1.0;\n } else if (verdict === 'under_investigation') {\n reachabilityFactor = 0.5;\n } else if (verdict === 'not_affected') {\n reachabilityFactor = 0.1;\n } else {\n // no verdict\n reachabilityFactor = 0.3;\n }\n\n const stalenessBonus = 1.0 + ((staleness ?? 0) * 0.2);\n\n const raw = reachabilityFactor * (0.4 * cvssNormalized + 0.6 * epssValue) * stalenessBonus;\n return Math.min(raw * 10, 10.0);\n}\n\n/**\n * Dependency row from sec_dependencies table.\n */\ninterface DependencyRow {\n node_id: string;\n ecosystem: string;\n package_name: string;\n resolved_version: string | null;\n declared_constraint: string;\n}\n\nexport class VulnerabilityDatabaseClient {\n private readonly adapters: VulnDatabaseAdapter[];\n private readonly db: GraphDatabase;\n private readonly timeoutMs: number;\n\n constructor(\n adapters: VulnDatabaseAdapter[],\n db: GraphDatabase,\n timeoutMs: number = 30000,\n ) {\n this.adapters = adapters;\n this.db = db;\n this.timeoutMs = timeoutMs;\n }\n\n /**\n * Enrich all Dependency_Nodes with vulnerability data.\n *\n * Uses batch queries (up to 1000 per HTTP request) when the adapter supports\n * `queryBatch`. Falls back to sequential single queries per dependency if batch\n * is unavailable or fails. Results are deduplicated by CVE ID across adapters.\n */\n async enrichAll(): Promise<EnrichmentResult> {\n const rawDb = this.db.getRawDb();\n const result: EnrichmentResult = {\n vulnerabilitiesFound: 0,\n dependenciesChecked: 0,\n errors: [],\n staleNodes: [],\n };\n\n const depRows: DependencyRow[] = rawDb.all(\n `SELECT node_id, ecosystem, package_name, resolved_version, declared_constraint\n FROM sec_dependencies`,\n );\n\n // Filter out deps with no usable version\n const queryableDeps = depRows.filter(d => d.resolved_version || d.declared_constraint);\n\n for (const adapter of this.adapters) {\n if (adapter.queryBatch) {\n await this.enrichAllBatch(adapter, queryableDeps, result);\n } else {\n await this.enrichAllSequential(adapter, queryableDeps, result);\n }\n }\n\n // Record lastVulnCheck for all queried deps (do once, after all adapters)\n const now = Date.now();\n for (const dep of queryableDeps) {\n rawDb.run(\n `UPDATE sec_dependencies SET last_vuln_check = ? WHERE node_id = ?`,\n [now, dep.node_id],\n );\n }\n result.dependenciesChecked = queryableDeps.length;\n\n // Enrich stored vulnerabilities with EPSS scores\n await this.enrichEpss(rawDb);\n\n // Compute combined risk scores after all enrichment is done\n await this.computeRiskScores(rawDb);\n\n return result;\n }\n\n /**\n * Fetch EPSS scores for all stored vulnerabilities and update the database.\n */\n private async enrichEpss(rawDb: any): Promise<void> {\n try {\n const cveRows: Array<{ cve_id: string }> = rawDb.all(\n `SELECT cve_id FROM sec_vulnerabilities`,\n );\n\n if (cveRows.length === 0) {\n return;\n }\n\n const cveIds = cveRows.map(r => r.cve_id);\n const epssClient = new EpssClient();\n const scores = await epssClient.fetchScores(cveIds);\n\n if (scores.size === 0) {\n return;\n }\n\n const fetchedAt = Date.now();\n for (const [cveId, { score, percentile }] of scores) {\n rawDb.run(\n `UPDATE sec_vulnerabilities SET epss_score = ?, epss_percentile = ?, epss_fetched_at = ? WHERE cve_id = ?`,\n [score, percentile, fetchedAt, cveId],\n );\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n logWarn(`[sec:epss] EPSS enrichment failed (non-critical): ${msg}`);\n }\n }\n\n /**\n * Compute and persist combined risk scores for all stored vulnerabilities.\n * Called after EPSS enrichment so epss_score values are up to date.\n */\n private async computeRiskScores(rawDb: any): Promise<void> {\n const rows: Array<{\n node_id: string;\n severity_score: number | null;\n epss_score: number | null;\n verdict: string | null;\n staleness_score: number | null;\n }> = rawDb.all(`\n SELECT v.node_id, v.severity_score, v.epss_score,\n r.verdict,\n d.staleness_score\n FROM sec_vulnerabilities v\n LEFT JOIN edges e ON e.target = v.node_id AND e.kind = 'has_vulnerability'\n LEFT JOIN sec_dependencies d ON d.node_id = e.source\n LEFT JOIN sec_reachability r ON r.vulnerability_node_id = v.node_id\n `);\n for (const row of rows) {\n const score = computeRiskScore(row.severity_score, row.epss_score, row.verdict, row.staleness_score);\n rawDb.run(`UPDATE sec_vulnerabilities SET risk_score = ? WHERE node_id = ?`, [score, row.node_id]);\n }\n }\n\n /**\n * Batch enrichment path: groups deps into chunks of BATCH_CHUNK_SIZE and sends\n * one HTTP request per chunk. Falls back to sequential on batch failure.\n */\n private async enrichAllBatch(\n adapter: VulnDatabaseAdapter & Required<Pick<VulnDatabaseAdapter, 'queryBatch'>>,\n deps: DependencyRow[],\n result: EnrichmentResult,\n ): Promise<void> {\n const rawDb = this.db.getRawDb();\n\n for (let i = 0; i < deps.length; i += BATCH_CHUNK_SIZE) {\n const chunk = deps.slice(i, i + BATCH_CHUNK_SIZE);\n const queries: BatchQuery[] = chunk.map(dep => ({\n ecosystem: dep.ecosystem,\n packageName: dep.package_name,\n version: dep.resolved_version || dep.declared_constraint,\n }));\n\n let batchResults: Array<CVERecord[]>;\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n batchResults = await adapter.queryBatch(queries, controller.signal);\n } finally {\n clearTimeout(timeout);\n }\n } catch (error: unknown) {\n // Batch failed \u2014 fall back to sequential for this chunk\n const msg = error instanceof Error ? error.message : String(error);\n logWarn(`[sec:vuln] ${adapter.name} batch query failed (${msg}), falling back to sequential for chunk of ${chunk.length}`);\n await this.enrichAllSequential(adapter, chunk, result);\n continue;\n }\n\n for (let j = 0; j < chunk.length; j++) {\n const dep = chunk[j]!;\n const cveRecords = batchResults[j] ?? [];\n const uniqueCves = this.deduplicateByCveId(cveRecords);\n for (const cve of uniqueCves) {\n this.upsertVulnerabilityNode(rawDb, cve, dep.node_id);\n result.vulnerabilitiesFound++;\n }\n }\n }\n }\n\n /**\n * Sequential enrichment path: queries one dependency at a time.\n * Used when the adapter does not support queryBatch, or as a fallback.\n */\n private async enrichAllSequential(\n adapter: VulnDatabaseAdapter,\n deps: DependencyRow[],\n result: EnrichmentResult,\n ): Promise<void> {\n const rawDb = this.db.getRawDb();\n\n for (const dep of deps) {\n const version = dep.resolved_version || dep.declared_constraint;\n const cveRecords = await this.queryAdaptersForDependency(dep, version, result, [adapter]);\n const uniqueCves = this.deduplicateByCveId(cveRecords);\n for (const cve of uniqueCves) {\n this.upsertVulnerabilityNode(rawDb, cve, dep.node_id);\n result.vulnerabilitiesFound++;\n }\n }\n }\n\n /**\n * Enrich a single Dependency_Node with vulnerability data.\n *\n * @param dependencyNodeId - The node ID of the dependency to enrich\n * @returns Array of CVE records found for this dependency\n */\n async enrichOne(dependencyNodeId: string): Promise<CVERecord[]> {\n const rawDb = this.db.getRawDb();\n\n const dep: DependencyRow | undefined = rawDb.get(\n `SELECT node_id, ecosystem, package_name, resolved_version, declared_constraint\n FROM sec_dependencies WHERE node_id = ?`,\n [dependencyNodeId],\n );\n\n if (!dep) {\n return [];\n }\n\n const version = dep.resolved_version || dep.declared_constraint;\n if (!version) {\n return [];\n }\n\n const enrichResult: EnrichmentResult = {\n vulnerabilitiesFound: 0,\n dependenciesChecked: 0,\n errors: [],\n staleNodes: [],\n };\n\n const cveRecords = await this.queryAdaptersForDependency(\n dep,\n version,\n enrichResult,\n );\n\n // Deduplicate by CVE ID\n const uniqueCves = this.deduplicateByCveId(cveRecords);\n\n // Create Vulnerability_Nodes and edges\n for (const cve of uniqueCves) {\n this.upsertVulnerabilityNode(rawDb, cve, dep.node_id);\n }\n\n // Record lastVulnCheck timestamp\n const now = Date.now();\n rawDb.run(\n `UPDATE sec_dependencies SET last_vuln_check = ? WHERE node_id = ?`,\n [now, dep.node_id],\n );\n\n return uniqueCves;\n }\n\n /**\n * Query a set of adapters for a single dependency.\n * Handles timeouts and unreachable databases gracefully.\n * Defaults to all configured adapters when adapterList is omitted.\n */\n private async queryAdaptersForDependency(\n dep: DependencyRow,\n version: string,\n result: EnrichmentResult,\n adapterList: VulnDatabaseAdapter[] = this.adapters,\n ): Promise<CVERecord[]> {\n const allRecords: CVERecord[] = [];\n\n for (const adapter of adapterList) {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), this.timeoutMs);\n\n try {\n const records = await adapter.query(\n dep.ecosystem,\n dep.package_name,\n version,\n controller.signal,\n );\n allRecords.push(...records);\n } finally {\n clearTimeout(timeout);\n }\n } catch (error: unknown) {\n // Handle unreachable database (Requirement 3.5)\n const errorMessage = error instanceof Error ? error.message : String(error);\n logError(\n `[sec:vuln] Failed to query ${adapter.name} for ${dep.package_name}@${version}: ${errorMessage}`,\n );\n\n result.errors.push({\n dependency: dep.package_name,\n database: adapter.name,\n error: errorMessage,\n });\n\n // Set vulnDataStale flag on the dependency node\n this.markDependencyStale(dep.node_id, result);\n }\n }\n\n return allRecords;\n }\n\n /**\n * Mark a dependency node as having stale vulnerability data.\n * Retains cached data but sets the stale flag with timestamp.\n */\n private markDependencyStale(nodeId: string, result: EnrichmentResult): void {\n const rawDb = this.db.getRawDb();\n const now = Date.now();\n\n rawDb.run(\n `UPDATE sec_dependencies\n SET vuln_data_stale = 1, vuln_data_stale_since = ?\n WHERE node_id = ?`,\n [now, nodeId],\n );\n\n if (!result.staleNodes.includes(nodeId)) {\n result.staleNodes.push(nodeId);\n }\n\n logWarn(\n `[sec:vuln] Vulnerability data marked stale for dependency node ${nodeId}`,\n );\n }\n\n /**\n * Deduplicate CVE records by their ID.\n * When the same CVE is reported by multiple databases, keep the first occurrence.\n */\n private deduplicateByCveId(records: CVERecord[]): CVERecord[] {\n const seen = new Map<string, CVERecord>();\n for (const record of records) {\n if (!seen.has(record.id)) {\n seen.set(record.id, record);\n }\n }\n return [...seen.values()];\n }\n\n /**\n * Create or update a Vulnerability_Node and link it to the affected dependency.\n *\n * - Creates a node in `nodes` table with kind='vulnerability', ID format `vuln:<cve_id>`\n * - Inserts a row in `sec_vulnerabilities` with CVE metadata\n * - Creates a `has_vulnerability` edge from the Dependency_Node to the Vulnerability_Node\n */\n private upsertVulnerabilityNode(\n rawDb: any,\n cve: CVERecord,\n dependencyNodeId: string,\n ): void {\n const vulnNodeId = `vuln:${cve.id}`;\n const now = Date.now();\n\n // Truncate summary to 500 characters (Requirement 3.3)\n const summary = cve.summary.length > 500\n ? cve.summary.slice(0, 500)\n : cve.summary;\n\n // Determine source database from the first adapter that returned this CVE\n // (in practice, the adapter name is not stored on CVERecord, so we use\n // the first configured adapter's name as the source)\n const sourceDatabase = this.adapters.length > 0 ? this.adapters[0].name : 'unknown';\n\n // Upsert into nodes table\n rawDb.run(\n `INSERT OR REPLACE INTO nodes\n (id, kind, name, qualified_name, file_path, language,\n start_line, end_line, start_column, end_column,\n is_exported, is_async, is_static, is_abstract, updated_at)\n VALUES (?, 'vulnerability', ?, ?, '', 'unknown', 0, 0, 0, 0, 0, 0, 0, 0, ?)`,\n [vulnNodeId, cve.id, cve.id, now],\n );\n\n // Upsert into sec_vulnerabilities table\n const affectedRangesJson = JSON.stringify(cve.affectedVersionRanges);\n rawDb.run(\n `INSERT OR REPLACE INTO sec_vulnerabilities\n (node_id, cve_id, severity_score, affected_ranges, fixed_version, summary, source_database)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [\n vulnNodeId,\n cve.id,\n cve.severity,\n affectedRangesJson,\n cve.fixedVersion ?? null,\n summary,\n sourceDatabase,\n ],\n );\n\n // Create has_vulnerability edge from Dependency_Node to Vulnerability_Node\n const edge: Edge = {\n source: dependencyNodeId,\n target: vulnNodeId,\n kind: 'has_vulnerability',\n confidence: 'extracted',\n confidenceScore: 1.0,\n };\n this.db.insertEdge(edge);\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }