@cdxgen/cdxgen 13.3.0 → 13.5.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 (372) hide show
  1. package/README.md +12 -9
  2. package/bin/cdxgen-secure.js +15 -0
  3. package/bin/cdxgen.js +115 -31
  4. package/bin/evinse.js +23 -1
  5. package/bin/hbom.js +15 -7
  6. package/bin/repl.js +115 -0
  7. package/bin/sign.js +45 -15
  8. package/bin/tracebom.js +5 -1
  9. package/bin/validate.js +84 -18
  10. package/bin/verify.js +155 -86
  11. package/data/cbomosdb-queries.json +13 -1
  12. package/data/component-tags.json +8 -0
  13. package/data/helpers/init.gradle +46 -21
  14. package/data/queries-darwin.json +25 -1
  15. package/data/queries-win.json +19 -1
  16. package/data/queries.json +41 -0
  17. package/data/remediations.json +344 -1
  18. package/data/rules/cbom-security.yaml +235 -4
  19. package/data/rules/dependency-sources.yaml +67 -0
  20. package/data/rules/go-binaries.yaml +46 -0
  21. package/data/rules/golem-go.yaml +80 -0
  22. package/data/rules/hbom-compliance.yaml +40 -22
  23. package/data/rules/hbom-performance.yaml +7 -1
  24. package/data/rules/host-topology.yaml +1 -1
  25. package/data/rules/obom-runtime.yaml +31 -2
  26. package/data/rules/php-runtime.yaml +143 -0
  27. package/data/rules/scala-evidence.yaml +115 -0
  28. package/lib/audit/index.js +52 -6
  29. package/lib/cli/bomAssembly.js +28 -3
  30. package/lib/cli/cliOptions.js +110 -3
  31. package/lib/cli/index.js +186 -58
  32. package/lib/cli/jsBom.js +50 -29
  33. package/lib/cli/jvmBom.js +779 -146
  34. package/lib/cli/managedBom.js +1021 -261
  35. package/lib/cli/nativeBom.js +364 -58
  36. package/lib/core/activity.js +12 -0
  37. package/lib/core/env.js +47 -17
  38. package/lib/core/fs.js +31 -21
  39. package/lib/core/httpClient.js +233 -69
  40. package/lib/core/pluginRun.js +184 -0
  41. package/lib/core/processTree.js +100 -0
  42. package/lib/core/projectConfig.js +1 -0
  43. package/lib/core/projectTooling.js +97 -0
  44. package/lib/core/propertySanitizer.js +72 -0
  45. package/lib/core/runState.js +39 -0
  46. package/lib/core/toml.js +72 -0
  47. package/lib/ecosystems/asarutils.js +4 -2
  48. package/lib/ecosystems/bunutils.js +5 -7
  49. package/lib/ecosystems/cargoCache.js +231 -8
  50. package/lib/ecosystems/cmakeResolver.js +10 -21
  51. package/lib/ecosystems/cppBuildContext.js +631 -0
  52. package/lib/ecosystems/cppEvidence.js +337 -22
  53. package/lib/ecosystems/cppIncludeResolver.js +294 -0
  54. package/lib/ecosystems/denoutils.js +93 -66
  55. package/lib/ecosystems/ecosystems.js +1967 -788
  56. package/lib/ecosystems/golem.js +578 -18
  57. package/lib/ecosystems/gradleutils.js +340 -46
  58. package/lib/ecosystems/jsEvidence.js +85 -24
  59. package/lib/ecosystems/kosi.js +21 -4
  60. package/lib/ecosystems/npmutils.js +64 -0
  61. package/lib/ecosystems/parsers-dotnet.js +668 -153
  62. package/lib/ecosystems/parsers-elm.js +46 -28
  63. package/lib/ecosystems/parsers-gleam.js +3 -4
  64. package/lib/ecosystems/parsers-go.js +1283 -297
  65. package/lib/ecosystems/parsers-js.js +92 -26
  66. package/lib/ecosystems/parsers-julia.js +2 -3
  67. package/lib/ecosystems/parsers-jvm.js +463 -29
  68. package/lib/ecosystems/parsers-misc.js +405 -93
  69. package/lib/ecosystems/parsers-python.js +152 -20
  70. package/lib/ecosystems/parsers-rust.js +4 -4
  71. package/lib/ecosystems/parsers-terraform.js +1573 -19
  72. package/lib/ecosystems/pythonutils.js +102 -6
  73. package/lib/ecosystems/remote/huggingface.js +3 -0
  74. package/lib/ecosystems/rubyutils.js +73 -20
  75. package/lib/ecosystems/rusi.js +32 -9
  76. package/lib/ecosystems/sbtutils.js +243 -187
  77. package/lib/ecosystems/scalacliutils.js +343 -0
  78. package/lib/ecosystems/scalasem.js +2620 -0
  79. package/lib/ecosystems/terraformRegistry.js +288 -0
  80. package/lib/ecosystems/utils.js +2 -0
  81. package/lib/ecosystems/vendoredCode.js +156 -0
  82. package/lib/evinser/evinser.js +695 -78
  83. package/lib/evinser/swiftsem.js +68 -5
  84. package/lib/helpers/agenticToolutils.js +2 -2
  85. package/lib/helpers/bomSigner.js +576 -128
  86. package/lib/inventory/agentSkillSpec.js +17 -0
  87. package/lib/inventory/aiCollector.js +4 -1
  88. package/lib/inventory/analyzer.js +110 -127
  89. package/lib/inventory/apkInstalledDb.js +243 -0
  90. package/lib/inventory/atomUtils.js +204 -48
  91. package/lib/inventory/auditCategories.js +10 -3
  92. package/lib/inventory/buildToolRateLimit.js +144 -0
  93. package/lib/inventory/cacheCommands.js +2 -1
  94. package/lib/inventory/cacheDir.js +52 -9
  95. package/lib/inventory/certificates.js +2371 -0
  96. package/lib/inventory/cmakeBuildDirs.js +254 -0
  97. package/lib/inventory/communityAiConfigParser.js +4 -9
  98. package/lib/inventory/deps.js +507 -128
  99. package/lib/inventory/display.js +200 -3
  100. package/lib/inventory/dosai.js +747 -101
  101. package/lib/inventory/dosaiParsers.js +310 -15
  102. package/lib/inventory/dynamic.js +96 -8
  103. package/lib/inventory/envcontext.js +168 -5
  104. package/lib/inventory/evidenceUtils.js +590 -132
  105. package/lib/inventory/fetchBatch.js +574 -38
  106. package/lib/inventory/fetchDiskCache.js +597 -0
  107. package/lib/inventory/forgeEnricher.js +14 -5
  108. package/lib/inventory/formulationParsers.js +3 -2
  109. package/lib/inventory/goBuildInfo.js +140 -0
  110. package/lib/inventory/hbom.js +67 -7
  111. package/lib/inventory/hbomAnalysis.js +16 -0
  112. package/lib/inventory/hostTopology.js +21 -0
  113. package/lib/inventory/jvmLocalRepos.js +917 -0
  114. package/lib/inventory/mcpConfigParser.js +2 -3
  115. package/lib/inventory/osPackagePurl.js +244 -0
  116. package/lib/inventory/osPackageResolver.js +89 -87
  117. package/lib/inventory/osinfo.js +79 -31
  118. package/lib/inventory/osqueryTransform.js +114 -5
  119. package/lib/inventory/parentSelection.js +146 -0
  120. package/lib/inventory/phpBuildInfo.js +487 -0
  121. package/lib/inventory/phpHostProbe.js +641 -0
  122. package/lib/inventory/rootfsCerts.js +426 -0
  123. package/lib/inventory/rustFormulationParser.js +3 -4
  124. package/lib/inventory/scalaCoords.js +153 -0
  125. package/lib/inventory/source.js +215 -11
  126. package/lib/inventory/spdx.js +245 -0
  127. package/lib/inventory/traceRunner.js +93 -8
  128. package/lib/inventory/tracedCrypto.js +423 -0
  129. package/lib/managers/binary.js +529 -183
  130. package/lib/managers/docker.js +199 -44
  131. package/lib/managers/oci.js +24 -2
  132. package/lib/parsers/cmakeCache.js +69 -0
  133. package/lib/parsers/cmakeLists.js +16 -2
  134. package/lib/parsers/cmakePresets.js +400 -0
  135. package/lib/parsers/compileCommands.js +557 -0
  136. package/lib/parsers/email.js +277 -0
  137. package/lib/parsers/largeJson.js +34 -2
  138. package/lib/parsers/strictJson.js +322 -0
  139. package/lib/parsers/terraformConfig.js +1073 -0
  140. package/lib/parsers/terraformSource.js +652 -0
  141. package/lib/server/server.js +209 -1
  142. package/lib/stages/postgen/annotator.js +17 -1
  143. package/lib/stages/postgen/introspection/markers.js +1 -1
  144. package/lib/stages/postgen/introspection/reflect.js +4 -1
  145. package/lib/stages/postgen/postgen.js +20 -6
  146. package/lib/stages/postgen/ruleEngine.js +58 -1
  147. package/lib/stages/postgen/specVersionCompat.js +62 -0
  148. package/lib/stages/pregen/pregen.js +117 -5
  149. package/lib/validator/bomValidator.js +26 -5
  150. package/lib/validator/generated/runtime.mjs +13 -0
  151. package/lib/validator/generated/validate-1.6.mjs +26 -7
  152. package/lib/validator/generated/validate-1.7.mjs +31 -12
  153. package/lib/validator/generated/validate-2.0.mjs +30 -11
  154. package/lib/validator/index.js +25 -10
  155. package/lib/validator/reporters/json.js +1 -0
  156. package/package.json +23 -23
  157. package/types/bin/cdxgen-secure.d.ts +3 -0
  158. package/types/bin/cdxgen-secure.d.ts.map +1 -0
  159. package/types/bin/repl.d.ts.map +1 -1
  160. package/types/lib/audit/index.d.ts.map +1 -1
  161. package/types/lib/cli/bomAssembly.d.ts +9 -2
  162. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  163. package/types/lib/cli/cliOptions.d.ts +43 -0
  164. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  165. package/types/lib/cli/index.d.ts.map +1 -1
  166. package/types/lib/cli/jsBom.d.ts.map +1 -1
  167. package/types/lib/cli/jvmBom.d.ts +44 -1
  168. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  169. package/types/lib/cli/managedBom.d.ts +7 -4
  170. package/types/lib/cli/managedBom.d.ts.map +1 -1
  171. package/types/lib/cli/nativeBom.d.ts +3 -1
  172. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  173. package/types/lib/core/activity.d.ts.map +1 -1
  174. package/types/lib/core/env.d.ts +27 -2
  175. package/types/lib/core/env.d.ts.map +1 -1
  176. package/types/lib/core/fs.d.ts.map +1 -1
  177. package/types/lib/core/httpClient.d.ts +13 -8
  178. package/types/lib/core/httpClient.d.ts.map +1 -1
  179. package/types/lib/core/pluginRun.d.ts +97 -0
  180. package/types/lib/core/pluginRun.d.ts.map +1 -0
  181. package/types/lib/core/processTree.d.ts +36 -0
  182. package/types/lib/core/processTree.d.ts.map +1 -0
  183. package/types/lib/core/projectConfig.d.ts.map +1 -1
  184. package/types/lib/core/projectTooling.d.ts +41 -0
  185. package/types/lib/core/projectTooling.d.ts.map +1 -0
  186. package/types/lib/core/propertySanitizer.d.ts +26 -0
  187. package/types/lib/core/propertySanitizer.d.ts.map +1 -1
  188. package/types/lib/core/runState.d.ts +29 -0
  189. package/types/lib/core/runState.d.ts.map +1 -0
  190. package/types/lib/core/toml.d.ts +20 -0
  191. package/types/lib/core/toml.d.ts.map +1 -0
  192. package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
  193. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  194. package/types/lib/ecosystems/cargoCache.d.ts +12 -0
  195. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
  196. package/types/lib/ecosystems/cmakeResolver.d.ts +5 -3
  197. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  198. package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
  199. package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
  200. package/types/lib/ecosystems/cppEvidence.d.ts +19 -3
  201. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  202. package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
  203. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
  204. package/types/lib/ecosystems/denoutils.d.ts +9 -3
  205. package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
  206. package/types/lib/ecosystems/ecosystems.d.ts +62 -13
  207. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  208. package/types/lib/ecosystems/golem.d.ts +81 -1
  209. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  210. package/types/lib/ecosystems/gradleutils.d.ts +59 -7
  211. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  212. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  213. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  214. package/types/lib/ecosystems/npmutils.d.ts +15 -0
  215. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  216. package/types/lib/ecosystems/parsers-dotnet.d.ts +155 -5
  217. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  218. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
  219. package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
  220. package/types/lib/ecosystems/parsers-go.d.ts +119 -5
  221. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  222. package/types/lib/ecosystems/parsers-js.d.ts +6 -1
  223. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  224. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
  225. package/types/lib/ecosystems/parsers-jvm.d.ts +74 -3
  226. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  227. package/types/lib/ecosystems/parsers-misc.d.ts +21 -59
  228. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  229. package/types/lib/ecosystems/parsers-python.d.ts +57 -6
  230. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  231. package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
  232. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
  233. package/types/lib/ecosystems/pythonutils.d.ts +26 -1
  234. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  235. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  236. package/types/lib/ecosystems/rubyutils.d.ts +21 -1
  237. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  238. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  239. package/types/lib/ecosystems/sbtutils.d.ts +88 -2
  240. package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
  241. package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
  242. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
  243. package/types/lib/ecosystems/scalasem.d.ts +311 -0
  244. package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
  245. package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
  246. package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
  247. package/types/lib/ecosystems/utils.d.ts +2 -2
  248. package/types/lib/ecosystems/utils.d.ts.map +1 -1
  249. package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
  250. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
  251. package/types/lib/evinser/evinser.d.ts +100 -8
  252. package/types/lib/evinser/evinser.d.ts.map +1 -1
  253. package/types/lib/evinser/swiftsem.d.ts +10 -0
  254. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  255. package/types/lib/helpers/bomSigner.d.ts +80 -4
  256. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  257. package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
  258. package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
  259. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  260. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  261. package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
  262. package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
  263. package/types/lib/inventory/atomUtils.d.ts +96 -10
  264. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  265. package/types/lib/inventory/auditCategories.d.ts +3 -0
  266. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  267. package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
  268. package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
  269. package/types/lib/inventory/cacheCommands.d.ts +2 -1
  270. package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
  271. package/types/lib/inventory/cacheDir.d.ts +12 -4
  272. package/types/lib/inventory/cacheDir.d.ts.map +1 -1
  273. package/types/lib/inventory/certificates.d.ts +205 -0
  274. package/types/lib/inventory/certificates.d.ts.map +1 -0
  275. package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
  276. package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
  277. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  278. package/types/lib/inventory/deps.d.ts +61 -2
  279. package/types/lib/inventory/deps.d.ts.map +1 -1
  280. package/types/lib/inventory/display.d.ts +18 -0
  281. package/types/lib/inventory/display.d.ts.map +1 -1
  282. package/types/lib/inventory/dosai.d.ts +202 -14
  283. package/types/lib/inventory/dosai.d.ts.map +1 -1
  284. package/types/lib/inventory/dosaiParsers.d.ts +53 -7
  285. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  286. package/types/lib/inventory/dynamic.d.ts +16 -1
  287. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  288. package/types/lib/inventory/envcontext.d.ts +40 -2
  289. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  290. package/types/lib/inventory/evidenceUtils.d.ts +48 -14
  291. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  292. package/types/lib/inventory/fetchBatch.d.ts +83 -14
  293. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  294. package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
  295. package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
  296. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  297. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  298. package/types/lib/inventory/goBuildInfo.d.ts +10 -0
  299. package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
  300. package/types/lib/inventory/hbom.d.ts.map +1 -1
  301. package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
  302. package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
  303. package/types/lib/inventory/hostTopology.d.ts.map +1 -1
  304. package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
  305. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
  306. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  307. package/types/lib/inventory/osPackagePurl.d.ts +99 -0
  308. package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
  309. package/types/lib/inventory/osPackageResolver.d.ts +6 -2
  310. package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
  311. package/types/lib/inventory/osinfo.d.ts +31 -6
  312. package/types/lib/inventory/osinfo.d.ts.map +1 -1
  313. package/types/lib/inventory/osqueryTransform.d.ts +51 -0
  314. package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
  315. package/types/lib/inventory/parentSelection.d.ts +34 -0
  316. package/types/lib/inventory/parentSelection.d.ts.map +1 -0
  317. package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
  318. package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
  319. package/types/lib/inventory/phpHostProbe.d.ts +35 -0
  320. package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
  321. package/types/lib/inventory/rootfsCerts.d.ts +27 -0
  322. package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
  323. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  324. package/types/lib/inventory/scalaCoords.d.ts +80 -0
  325. package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
  326. package/types/lib/inventory/source.d.ts +28 -0
  327. package/types/lib/inventory/source.d.ts.map +1 -1
  328. package/types/lib/inventory/spdx.d.ts +24 -0
  329. package/types/lib/inventory/spdx.d.ts.map +1 -1
  330. package/types/lib/inventory/traceRunner.d.ts +16 -1
  331. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  332. package/types/lib/inventory/tracedCrypto.d.ts +64 -0
  333. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
  334. package/types/lib/managers/binary.d.ts +10 -0
  335. package/types/lib/managers/binary.d.ts.map +1 -1
  336. package/types/lib/managers/docker.d.ts +12 -0
  337. package/types/lib/managers/docker.d.ts.map +1 -1
  338. package/types/lib/managers/oci.d.ts +3 -1
  339. package/types/lib/managers/oci.d.ts.map +1 -1
  340. package/types/lib/parsers/cmakeCache.d.ts +16 -0
  341. package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
  342. package/types/lib/parsers/cmakeLists.d.ts +2 -1
  343. package/types/lib/parsers/cmakeLists.d.ts.map +1 -1
  344. package/types/lib/parsers/cmakePresets.d.ts +78 -0
  345. package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
  346. package/types/lib/parsers/compileCommands.d.ts +115 -0
  347. package/types/lib/parsers/compileCommands.d.ts.map +1 -0
  348. package/types/lib/parsers/email.d.ts +19 -0
  349. package/types/lib/parsers/email.d.ts.map +1 -0
  350. package/types/lib/parsers/largeJson.d.ts +13 -0
  351. package/types/lib/parsers/largeJson.d.ts.map +1 -1
  352. package/types/lib/parsers/strictJson.d.ts +46 -0
  353. package/types/lib/parsers/strictJson.d.ts.map +1 -0
  354. package/types/lib/parsers/terraformConfig.d.ts +70 -0
  355. package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
  356. package/types/lib/parsers/terraformSource.d.ts +66 -0
  357. package/types/lib/parsers/terraformSource.d.ts.map +1 -0
  358. package/types/lib/server/server.d.ts +8 -0
  359. package/types/lib/server/server.d.ts.map +1 -1
  360. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  361. package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
  362. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  363. package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
  364. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  365. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  366. package/types/lib/stages/pregen/pregen.d.ts +14 -1
  367. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  368. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  369. package/types/lib/validator/index.d.ts +8 -0
  370. package/types/lib/validator/index.d.ts.map +1 -1
  371. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  372. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
@@ -1,10 +1,12 @@
1
1
  import { platform } from "node:os";
2
- import { extname } from "node:path";
2
+ import { basename, dirname, extname } from "node:path";
3
3
 
4
4
  import { build } from "@cdxgen/cdx-purl";
5
5
 
6
6
  import { safeExistsSync } from "../core/fs.js";
7
7
  import { thoughtLog } from "../core/logger.js";
8
+ import { sanitizeBomUrl } from "../core/propertySanitizer.js";
9
+ import { isAgentSkillSpecValid } from "./agentSkillSpec.js";
8
10
  import {
9
11
  addDosaiReachabilityProperties,
10
12
  readDosaiMethodsReport,
@@ -12,22 +14,34 @@ import {
12
14
  import {
13
15
  addDosaiSetValue,
14
16
  buildDosaiPurlAliasMap,
17
+ dosaiCallSiteLocation,
15
18
  dosaiSourceLocation,
16
19
  dosaiSourceLocationFromNode,
20
+ pickDosaiComponentByLocation,
17
21
  resolveDosaiComponentPurl,
18
22
  } from "./dosaiParsers.js";
19
23
  import { createGtfoBinsPropertiesFromRow } from "./gtfobins.js";
20
24
  import { createLolbasProperties } from "./lolbas.js";
25
+ import { isOsPackagePurlType } from "./osPackagePurl.js";
21
26
  import {
22
27
  createOsQueryFallbackBomRef,
28
+ createOsQueryPackageIdentity,
23
29
  createOsQueryPurl,
24
30
  deriveOsQueryDescription,
25
31
  deriveOsQueryName,
26
32
  deriveOsQueryPublisher,
27
33
  deriveOsQueryVersion,
34
+ OS_PACKAGE_LICENSE_COLUMNS,
35
+ osPackagePropertyName,
28
36
  sanitizeOsQueryIdentity,
29
37
  shouldCreateOsQueryPurl,
30
38
  } from "./osqueryTransform.js";
39
+ import {
40
+ adjustLicenseInformation,
41
+ findLicenseId,
42
+ isSpdxLicenseExpression,
43
+ spdxLicenses,
44
+ } from "./spdx.js";
31
45
 
32
46
  /**
33
47
  * Build a CycloneDX occurrence evidence object from a location string.
@@ -240,27 +254,47 @@ function addDotnetIdentityMethod(apkg, value) {
240
254
  * Enrich .NET package components with occurrence evidence and imported module/method
241
255
  * information from a dosai dependency slices file.
242
256
  *
243
- * Builds a mapping of DLL filenames to purls using the `internal:PackageFiles` property of each
244
- * package, then reads the slices file to add occurrence locations, imported modules,
245
- * called methods, and assembly version information where available.
257
+ * Each dosai record names its package by purl, resolved in the project of the
258
+ * record's file, and is attached to the component of that version (see
259
+ * {@link resolveDosaiComponentPurl}). The DLL files of each package
260
+ * (`internal:PackageFiles`) only fill in for a record whose purl names no
261
+ * component, and only when the DLL belongs to one component or the record's
262
+ * location says which: two versions of a package ship the same DLL names.
263
+ *
264
+ * The fallback is needed because dosai names a `System.*` assembly it cannot
265
+ * map to a package with a framework purl (`pkg:nuget/System.Runtime`), as it
266
+ * does whenever the tree has lock files but no restore output: the DLL of
267
+ * System.Data.SQLite.Core is still the package's. A DLL that only a package's
268
+ * build-time folders carry is not listed in `internal:PackageFiles`, so it
269
+ * credits no package with the project's framework calls (issue 4441).
246
270
  *
247
271
  * @param {Object[]} pkgList Array of .NET package component objects to enrich
248
272
  * @param {string} slicesFile Path to the dosai dependency slices JSON file
273
+ * @param {Object} [options] Options
274
+ * @param {string} [options.srcPath] Directory dosai analyzed, which its relative locations
275
+ * and the components' relative manifest paths are relative to
249
276
  * @returns {Object[]} The enriched package list (same array, mutated in place)
250
277
  */
251
- export function addEvidenceForDotnet(pkgList, slicesFile) {
278
+ export function addEvidenceForDotnet(pkgList, slicesFile, options = {}) {
252
279
  // We need two datastructures.
253
280
  // dll to purl mapping from the pkgList
254
281
  // purl to occurrences list using the slicesFile
255
282
  if (!slicesFile || !safeExistsSync(slicesFile)) {
256
283
  return pkgList;
257
284
  }
258
- const pkgFilePurlMap = {};
285
+ const pkgFilePurls = {};
259
286
  const purlLocationMap = {};
260
287
  const purlModulesMap = {};
261
288
  const purlMethodsMap = {};
262
289
  const purlReachabilityMap = {};
263
- const purlAliasMap = buildDosaiPurlAliasMap(pkgList);
290
+ const purlAliasMap = buildDosaiPurlAliasMap(pkgList, options);
291
+ const componentPurls = new Set(pkgList.map((apkg) => apkg.purl));
292
+ // A dosai purl that names a component of this list, resolved in the
293
+ // record's project.
294
+ const componentPurlFor = (purl, location) => {
295
+ const resolved = resolveDosaiComponentPurl(purl, purlAliasMap, location);
296
+ return componentPurls.has(resolved) ? resolved : undefined;
297
+ };
264
298
  for (const apkg of pkgList) {
265
299
  if (apkg.properties && Array.isArray(apkg.properties)) {
266
300
  apkg.properties
@@ -270,20 +304,32 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
270
304
  const tmpA = aprop.value.split(", ");
271
305
  if (tmpA?.length) {
272
306
  tmpA.forEach((dllFile) => {
273
- pkgFilePurlMap[dllFile] = apkg.purl;
307
+ pkgFilePurls[dllFile] ??= new Set();
308
+ pkgFilePurls[dllFile].add(apkg.purl);
274
309
  });
275
310
  }
276
311
  }
277
312
  });
278
313
  }
279
314
  }
315
+ // The component a DLL belongs to: its only package, else the candidate of the
316
+ // record's project.
317
+ const purlForDll = (dllFile, location) =>
318
+ pkgFilePurls[dllFile]
319
+ ? pickDosaiComponentByLocation(
320
+ pkgFilePurls[dllFile],
321
+ purlAliasMap,
322
+ location,
323
+ )
324
+ : undefined;
280
325
  // Only MethodCalls into a package .dll produce evidence below, so a report
281
326
  // too large to parse whole keeps just those.
282
327
  const slicesData = readDosaiMethodsReport(slicesFile, {
283
328
  keepMethodCall: (methodCall) =>
284
329
  Boolean(
285
- methodCall?.Module?.endsWith(".dll") &&
286
- pkgFilePurlMap[methodCall.Module],
330
+ methodCall?.Purl ||
331
+ (methodCall?.Module?.endsWith(".dll") &&
332
+ pkgFilePurls[methodCall.Module]),
287
333
  ),
288
334
  });
289
335
  if (!slicesData) {
@@ -295,43 +341,36 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
295
341
  );
296
342
  if (slicesData.Dependencies) {
297
343
  for (const adep of slicesData.Dependencies) {
298
- if (adep.Purl) {
299
- const modPurl = resolveDosaiComponentPurl(adep.Purl, purlAliasMap);
300
- if (modPurl) {
301
- addDosaiSetValue(
302
- purlLocationMap,
303
- modPurl,
304
- dosaiSourceLocation(adep),
305
- );
306
- addDosaiSetValue(
307
- purlModulesMap,
308
- modPurl,
309
- adep.Name || adep.Namespace,
310
- );
311
- }
344
+ const location = dosaiSourceLocation(adep);
345
+ const resolvedPurl = adep.Purl
346
+ ? componentPurlFor(adep.Purl, adep.Path)
347
+ : undefined;
348
+ if (resolvedPurl) {
349
+ addDosaiSetValue(purlLocationMap, resolvedPurl, location);
350
+ addDosaiSetValue(
351
+ purlModulesMap,
352
+ resolvedPurl,
353
+ adep.Name || adep.Namespace,
354
+ );
355
+ continue;
312
356
  }
313
357
  // Case 1: Dependencies slice has the .dll file
314
- if (adep.Module?.endsWith(".dll") && pkgFilePurlMap[adep.Module]) {
315
- const modPurl = pkgFilePurlMap[adep.Module];
316
- if (!purlLocationMap[modPurl]) {
317
- purlLocationMap[modPurl] = new Set();
318
- }
319
- purlLocationMap[modPurl].add(`${adep.Path}#${adep.LineNumber}`);
320
- } else if (
321
- adep?.Name &&
322
- (adep?.Namespace?.startsWith("System") ||
323
- adep?.Namespace?.startsWith("Microsoft"))
324
- ) {
325
- // Case 2: System packages where the .dll information is missing
326
- // In this case, the dll file name is the name followed by dll.
327
- const moduleDll = `${adep.Name}.dll`;
328
- if (pkgFilePurlMap[moduleDll]) {
329
- const modPurl = pkgFilePurlMap[moduleDll];
330
- if (!purlLocationMap[modPurl]) {
331
- purlLocationMap[modPurl] = new Set();
332
- }
333
- purlLocationMap[modPurl].add(`${adep.Path}#${adep.LineNumber}`);
334
- }
358
+ // Case 2: System packages where the .dll information is missing. In
359
+ // this case, the dll file name is the name followed by dll.
360
+ const moduleDll = adep.Module?.endsWith(".dll")
361
+ ? adep.Module
362
+ : adep?.Name &&
363
+ (adep?.Namespace?.startsWith("System") ||
364
+ adep?.Namespace?.startsWith("Microsoft"))
365
+ ? `${adep.Name}.dll`
366
+ : undefined;
367
+ const modPurl = moduleDll && purlForDll(moduleDll, adep.Path);
368
+ if (modPurl) {
369
+ addDosaiSetValue(
370
+ purlLocationMap,
371
+ modPurl,
372
+ `${adep.Path}#${adep.LineNumber}`,
373
+ );
335
374
  }
336
375
  }
337
376
  }
@@ -342,95 +381,113 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
342
381
  const graphNodes = Object.fromEntries(
343
382
  (slicesData.CallGraph?.Nodes || []).map((node) => [node.Id, node]),
344
383
  );
345
- for (const reachability of slicesData.PackageReachability) {
346
- const modPurl = resolveDosaiComponentPurl(
347
- reachability.Purl,
348
- purlAliasMap,
349
- );
350
- if (!modPurl) {
351
- continue;
352
- }
353
- // Confidence/evidence facts for this purl, applied to the component below so an
354
- // unbuilt tree stays distinguishable from an only-imported package.
355
- purlReachabilityMap[modPurl] ??= reachability;
384
+ // The first fact for a component wins, so a fact that names the
385
+ // component's exact purl goes before one that reached it by name.
386
+ const facts = [...slicesData.PackageReachability].sort(
387
+ (a, b) =>
388
+ Number(!componentPurls.has(a.Purl)) -
389
+ Number(!componentPurls.has(b.Purl)),
390
+ );
391
+ for (const reachability of facts) {
392
+ // A fact can span projects (a versionless purl from unrestored
393
+ // projects), so each location is resolved in its own project, and the
394
+ // fact applies to every component one of its locations reached.
395
+ const factPurls = new Set();
396
+ // Modules and methods of call-graph items that name no source file
397
+ // (frames inside the package), held for the fact's own version.
398
+ const unplaced = [];
399
+ const resolveAt = (location) =>
400
+ resolveDosaiComponentPurl(reachability.Purl, purlAliasMap, location);
356
401
  let hasExplicitSourceLocations = false;
357
402
  for (const sourceLocation of reachability.SourceLocations || []) {
358
403
  const location = dosaiSourceLocation(sourceLocation);
404
+ const modPurl = resolveAt(location);
359
405
  addDosaiSetValue(purlLocationMap, modPurl, location);
406
+ if (modPurl) {
407
+ factPurls.add(modPurl);
408
+ }
360
409
  hasExplicitSourceLocations ||= Boolean(location);
361
410
  }
362
411
  for (const edgeId of reachability.EdgeIds || []) {
363
412
  const edge = graphEdges[edgeId];
413
+ const location = dosaiSourceLocation(edge);
414
+ const modPurl = resolveAt(location ?? edge?.Path);
415
+ const method = edge?.CalledMethodName || edge?.TargetName;
416
+ if (!modPurl) {
417
+ unplaced.push([purlMethodsMap, method]);
418
+ continue;
419
+ }
420
+ factPurls.add(modPurl);
364
421
  if (!hasExplicitSourceLocations) {
365
- addDosaiSetValue(
366
- purlLocationMap,
367
- modPurl,
368
- dosaiSourceLocation(edge),
369
- );
422
+ addDosaiSetValue(purlLocationMap, modPurl, location);
370
423
  }
371
- addDosaiSetValue(
372
- purlMethodsMap,
373
- modPurl,
374
- edge?.CalledMethodName || edge?.TargetName,
375
- );
424
+ addDosaiSetValue(purlMethodsMap, modPurl, method);
376
425
  }
377
426
  for (const nodeId of reachability.NodeIds || []) {
378
427
  const node = graphNodes[nodeId];
428
+ const location = dosaiSourceLocationFromNode(node);
429
+ const modPurl = resolveAt(location);
430
+ const module = node?.ClassName || node?.Module;
431
+ const method = node?.Name || node?.Identity?.MethodName;
432
+ if (!modPurl) {
433
+ unplaced.push([purlModulesMap, module], [purlMethodsMap, method]);
434
+ continue;
435
+ }
436
+ factPurls.add(modPurl);
379
437
  if (!hasExplicitSourceLocations) {
380
- addDosaiSetValue(
381
- purlLocationMap,
382
- modPurl,
383
- dosaiSourceLocationFromNode(node),
384
- );
438
+ addDosaiSetValue(purlLocationMap, modPurl, location);
385
439
  }
386
- addDosaiSetValue(
387
- purlModulesMap,
388
- modPurl,
389
- node?.ClassName || node?.Module,
390
- );
391
- addDosaiSetValue(
392
- purlMethodsMap,
393
- modPurl,
394
- node?.Name || node?.Identity?.MethodName,
395
- );
440
+ addDosaiSetValue(purlModulesMap, modPurl, module);
441
+ addDosaiSetValue(purlMethodsMap, modPurl, method);
396
442
  }
397
- }
398
- }
399
- if (slicesData.MethodCalls) {
400
- for (const amethodCall of slicesData.MethodCalls) {
401
- if (
402
- amethodCall.Module?.endsWith(".dll") &&
403
- pkgFilePurlMap[amethodCall.Module]
404
- ) {
405
- const modPurl = pkgFilePurlMap[amethodCall.Module];
406
- if (!purlLocationMap[modPurl]) {
407
- purlLocationMap[modPurl] = new Set();
408
- }
409
- if (!purlModulesMap[modPurl]) {
410
- purlModulesMap[modPurl] = new Set();
443
+ if (!factPurls.size) {
444
+ const modPurl = resolveAt(undefined);
445
+ if (modPurl) {
446
+ factPurls.add(modPurl);
411
447
  }
412
- if (!purlMethodsMap[modPurl]) {
413
- purlMethodsMap[modPurl] = new Set();
448
+ }
449
+ if (factPurls.size === 1) {
450
+ const [modPurl] = factPurls;
451
+ for (const [map, value] of unplaced) {
452
+ addDosaiSetValue(map, modPurl, value);
414
453
  }
415
- purlLocationMap[modPurl].add(
416
- `${amethodCall.Path}#${amethodCall.LineNumber}`,
417
- );
418
- purlModulesMap[modPurl].add(amethodCall.ClassName);
419
- purlMethodsMap[modPurl].add(amethodCall.CalledMethod);
454
+ }
455
+ // Confidence/evidence facts for this purl, applied to the component below so an
456
+ // unbuilt tree stays distinguishable from an only-imported package.
457
+ for (const modPurl of factPurls) {
458
+ purlReachabilityMap[modPurl] ??= reachability;
420
459
  }
421
460
  }
422
461
  }
423
- if (slicesData.AssemblyInformation) {
424
- for (const apkg of pkgList) {
425
- if (!apkg.version) {
426
- for (const assemblyInfo of slicesData.AssemblyInformation) {
427
- if (apkg.name === assemblyInfo.Name) {
428
- apkg.version = assemblyInfo.Version;
429
- }
430
- }
462
+ if (slicesData.MethodCalls) {
463
+ for (const amethodCall of slicesData.MethodCalls) {
464
+ // dosai's purl is resolved in the caller's project; the DLL name is the
465
+ // same for every version of the package, and fills in for a purl that
466
+ // names no component, such as the framework purl dosai gives a
467
+ // System.* assembly it cannot map to a package.
468
+ const modPurl =
469
+ (amethodCall.Purl
470
+ ? componentPurlFor(amethodCall.Purl, amethodCall.Path)
471
+ : undefined) ||
472
+ (amethodCall.Module?.endsWith(".dll")
473
+ ? purlForDll(amethodCall.Module, amethodCall.Path)
474
+ : undefined);
475
+ if (!modPurl) {
476
+ continue;
431
477
  }
478
+ addDosaiSetValue(
479
+ purlLocationMap,
480
+ modPurl,
481
+ dosaiCallSiteLocation(amethodCall, options.srcPath),
482
+ );
483
+ addDosaiSetValue(purlModulesMap, modPurl, amethodCall.ClassName);
484
+ addDosaiSetValue(purlMethodsMap, modPurl, amethodCall.CalledMethod);
432
485
  }
433
486
  }
487
+ // AssemblyInformation used to fill in a missing version by assembly name.
488
+ // An assembly version is not the package version (Newtonsoft.Json 13.0.3
489
+ // ships assembly 13.0.0.0), the purl and bom-ref kept no version, and the
490
+ // last same-named assembly won, so the component described two packages.
434
491
  }
435
492
  if (Object.keys(purlLocationMap).length) {
436
493
  for (const apkg of pkgList) {
@@ -495,20 +552,55 @@ export function convertOSQueryResults(
495
552
  enhance = false,
496
553
  osPackageListers = undefined,
497
554
  ) {
555
+ if (queryCategory === "ai_agent_skills") {
556
+ return convertAgentSkillRows(results);
557
+ }
498
558
  const pkgList = [];
559
+ // deb, rpm, apk, pacman, Homebrew, MacPorts, snap and flatpak rows describe a
560
+ // package, not a file or an app: the package manager names them, and nothing
561
+ // in such a row is a path inside the package.
562
+ const isOsPackage = isOsPackagePurlType(queryObj.purlType);
563
+ const isLocatedPackage =
564
+ LOCATED_PACKAGE_PURL_TYPES.has(queryObj.purlType) ||
565
+ LOCATED_PACKAGE_CATEGORIES.has(queryCategory);
499
566
  if (results?.length) {
500
567
  for (const res of results) {
501
- const version = deriveOsQueryVersion(res);
568
+ let version = deriveOsQueryVersion(res);
502
569
  let name = deriveOsQueryName(res, results.length === 1, queryObj.name);
503
570
  if (queryObj.purlType === "chrome-extension") {
504
- name = (res.identifier || res.extension_id || name || "").toLowerCase();
571
+ // osquery computes identifier from the manifest key, which an
572
+ // unpacked extension may lack; the profile still knows its ID.
573
+ name = (
574
+ res.identifier ||
575
+ res.referenced_identifier ||
576
+ res.extension_id ||
577
+ name ||
578
+ ""
579
+ ).toLowerCase();
580
+ }
581
+ let npmScope;
582
+ if (queryObj.purlType === "npm") {
583
+ const scoped = /^@([^/]+)\/(.+)$/.exec(`${name || ""}`);
584
+ if (scoped) {
585
+ [, npmScope, name] = scoped;
586
+ }
505
587
  }
506
588
  let group = "";
507
- const subpath = res.path || res.admindir || res.source;
589
+ const subpath =
590
+ isOsPackage || isLocatedPackage
591
+ ? undefined
592
+ : res.path || res.admindir || res.source;
508
593
  const publisher = deriveOsQueryPublisher(res);
509
- // For vscode-extension purl type, the publisher is used as the namespace
510
- if (queryObj.purlType === "vscode-extension" && publisher) {
511
- group = publisher.toLowerCase();
594
+ if (queryObj.purlType === "vscode-extension") {
595
+ // osquery names a VS Code extension by its ID, <publisher>.<name>,
596
+ // which the vscode-extension type splits into namespace and name. Its
597
+ // publisher column is the publisher's display name.
598
+ const extensionId = /^([^.]+)\.(.+)$/u.exec(`${name || ""}`);
599
+ if (extensionId) {
600
+ [, group, name] = extensionId;
601
+ } else if (publisher) {
602
+ group = publisher.toLowerCase();
603
+ }
512
604
  }
513
605
  let scope;
514
606
  const compScope = res.priority;
@@ -523,25 +615,53 @@ export function convertOSQueryResults(
523
615
  qualifiers = {
524
616
  tag_id: res.identifying_number.replace("{", "").replace("}", ""),
525
617
  };
618
+ } else if (queryCategory === "firefox_addons" && res.identifier) {
619
+ // The profile an add-on was found in is not part of its purl, so the
620
+ // add-on ID is what tells apart two add-ons that share a name.
621
+ qualifiers = { tag_id: firefoxAddonTagId(res.identifier) };
526
622
  }
527
623
  if (name) {
528
624
  name = sanitizeOsQueryIdentity(name);
529
- group = sanitizeOsQueryIdentity(group);
625
+ // sanitizeOsQueryIdentity drops a leading @, which an npm scope keeps.
626
+ group = npmScope
627
+ ? `@${sanitizeOsQueryIdentity(npmScope)}`
628
+ : sanitizeOsQueryIdentity(group);
530
629
  const isCryptoAsset = queryObj.componentType === "cryptographic-asset";
531
- const purl = shouldCreateOsQueryPurl(queryObj.componentType)
532
- ? createOsQueryPurl(
533
- queryObj.purlType,
534
- group,
535
- name,
536
- version,
537
- qualifiers,
538
- subpath,
539
- )
540
- : undefined;
630
+ let purl;
631
+ if (isOsPackage) {
632
+ // The version of such a row is part of its identity: an rpm's is its
633
+ // version and release, behind its epoch.
634
+ const identity = createOsQueryPackageIdentity(
635
+ queryObj.purlType,
636
+ res,
637
+ name,
638
+ );
639
+ version = identity.version;
640
+ purl = shouldCreateOsQueryPurl(queryObj.componentType)
641
+ ? identity.purl
642
+ : undefined;
643
+ } else if (shouldCreateOsQueryPurl(queryObj.componentType)) {
644
+ purl = createOsQueryPurl(
645
+ queryObj.purlType,
646
+ group,
647
+ name,
648
+ version,
649
+ qualifiers,
650
+ subpath,
651
+ );
652
+ }
541
653
  const props = [{ name: "cdx:osquery:category", value: queryCategory }];
542
654
  props.push(...createLolbasProperties(queryCategory, res));
543
655
  if (platform() === "linux") {
544
- props.push(...createGtfoBinsPropertiesFromRow(queryCategory, res));
656
+ // The GTFOBins matcher reads a description for command names, and
657
+ // the free text of a package's description matches by chance: zlib's
658
+ // "the deflate compression method found in gzip" is no use of gzip.
659
+ props.push(
660
+ ...createGtfoBinsPropertiesFromRow(
661
+ queryCategory,
662
+ isOsPackage ? { ...res, description: undefined } : res,
663
+ ),
664
+ );
545
665
  }
546
666
  let providesList;
547
667
  if (enhance) {
@@ -575,10 +695,12 @@ export function convertOSQueryResults(
575
695
  version: version || "",
576
696
  description,
577
697
  publisher,
698
+ // Without a purl, the bom-ref is built from the row, and the path of
699
+ // a located package is not part of what it is.
578
700
  "bom-ref": createOsQueryBomRef(
579
701
  queryCategory,
580
702
  queryObj.componentType,
581
- res,
703
+ isLocatedPackage ? { ...res, path: undefined } : res,
582
704
  name,
583
705
  version,
584
706
  purl,
@@ -599,25 +721,361 @@ export function convertOSQueryResults(
599
721
  if (cryptoProperties) {
600
722
  apkg.cryptoProperties = cryptoProperties;
601
723
  }
724
+ const licenseColumns = isOsPackage
725
+ ? OS_PACKAGE_LICENSE_COLUMNS[queryObj.purlType]
726
+ : undefined;
727
+ if (licenseColumns) {
728
+ const licenses = osPackageLicenses(
729
+ res[licenseColumns.license],
730
+ licenseColumns.separator,
731
+ );
732
+ if (licenses.length) {
733
+ apkg.licenses = licenses;
734
+ }
735
+ const website = licenseColumns.website
736
+ ? osPackageWebsite(res[licenseColumns.website])
737
+ : undefined;
738
+ if (website) {
739
+ apkg.externalReferences = [{ type: "website", url: website }];
740
+ }
741
+ }
602
742
  for (const k of Object.keys(res).filter((p) => {
603
743
  if (["version", "description", "publisher"].includes(p)) {
604
744
  return false;
605
745
  }
606
746
  return !(queryObj.purlType !== "chrome-extension" && p === "name");
607
747
  })) {
608
- if (res[k] && res[k] !== "null") {
748
+ const propertyName = isOsPackage
749
+ ? osPackagePropertyName(queryObj.purlType, k)
750
+ : k;
751
+ // (none) is what rpm's database says for a tag a record lacks, such
752
+ // as the source rpm of a key record.
753
+ if (
754
+ propertyName &&
755
+ res[k] &&
756
+ res[k] !== "null" &&
757
+ !(isOsPackage && res[k] === "(none)")
758
+ ) {
609
759
  props.push({
610
- name: k,
760
+ name: propertyName,
611
761
  value: res[k],
612
762
  });
613
763
  }
614
764
  }
765
+ if (isLocatedPackage) {
766
+ // osquery writes a JSON null as "null", which is the path Firefox
767
+ // gives its default theme.
768
+ const occurrence = createOccurrenceEvidence(
769
+ [res.path, res.directory].find(
770
+ (value) => value && value !== "null",
771
+ ),
772
+ );
773
+ if (occurrence) {
774
+ apkg.evidence = { occurrences: [occurrence] };
775
+ }
776
+ }
615
777
  apkg.properties = props;
616
778
  pkgList.push(apkg);
617
779
  }
618
780
  }
619
781
  }
620
- return pkgList;
782
+ return isLocatedPackage ? mergeLocatedPackages(pkgList) : pkgList;
783
+ }
784
+
785
+ // npm and pypi rows name a package found at a path on the host, Chromium and
786
+ // VS Code extension rows an extension found in a browser profile or an editor's
787
+ // extensions folder, and a firefox_addons row an add-on found in a Firefox
788
+ // profile. Where it was found is not part of what it is, so the path is an
789
+ // occurrence rather than a subpath: a home directory stays out of the purl, and
790
+ // one package installed in two places is one component with two occurrences.
791
+ const LOCATED_PACKAGE_PURL_TYPES = new Set([
792
+ "npm",
793
+ "pypi",
794
+ "chrome-extension",
795
+ "vscode-extension",
796
+ ]);
797
+ const LOCATED_PACKAGE_CATEGORIES = new Set(["firefox_addons"]);
798
+
799
+ const FIREFOX_ADDON_GUID =
800
+ /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/iu;
801
+
802
+ // A Firefox add-on ID is an email-like string or a GUID in braces. A swid
803
+ // tag_id that is a GUID must be in lower case, and is written without braces.
804
+ function firefoxAddonTagId(identifier) {
805
+ const id = `${identifier}`.trim();
806
+ const guid = FIREFOX_ADDON_GUID.exec(id);
807
+ return guid ? guid[1].toLowerCase() : id;
808
+ }
809
+
810
+ function mergeLocatedPackages(components) {
811
+ const byRef = new Map();
812
+ for (const component of components) {
813
+ const existing = byRef.get(component["bom-ref"]);
814
+ if (!existing) {
815
+ byRef.set(component["bom-ref"], component);
816
+ continue;
817
+ }
818
+ const occurrences = existing.evidence?.occurrences || [];
819
+ for (const occurrence of component.evidence?.occurrences || []) {
820
+ if (
821
+ !occurrences.some((known) => known.location === occurrence.location)
822
+ ) {
823
+ occurrences.push(occurrence);
824
+ }
825
+ }
826
+ if (occurrences.length) {
827
+ existing.evidence = { ...existing.evidence, occurrences };
828
+ }
829
+ for (const property of component.properties) {
830
+ if (
831
+ !existing.properties.some(
832
+ (known) =>
833
+ known.name === property.name && known.value === property.value,
834
+ )
835
+ ) {
836
+ existing.properties.push(property);
837
+ }
838
+ }
839
+ }
840
+ for (const component of byRef.values()) {
841
+ component.evidence?.occurrences?.sort((a, b) =>
842
+ a.location.localeCompare(b.location),
843
+ );
844
+ }
845
+ return [...byRef.values()];
846
+ }
847
+
848
+ // The licence an OS package declares. An apk `L:` field is a list of SPDX ids
849
+ // separated by spaces on older packages and an SPDX expression on newer ones;
850
+ // pacman keeps one licence per entry, each an SPDX id or expression, which
851
+ // osquery joins with commas; a snap or a flatpak declares one expression. Only
852
+ // an explicit operator or a parenthesis makes a text an expression: the SPDX
853
+ // parser's own check takes the `or` inside `LGPL-2.0-or-later` for an operator,
854
+ // and would turn a plain list into one expression. A token is an id only when
855
+ // the SPDX list has what it maps to; any other is kept by name rather than
856
+ // dropped.
857
+ const MAX_OS_PACKAGE_LICENSES = 64;
858
+ const SPDX_OPERATORS = new Set(["AND", "OR", "WITH"]);
859
+
860
+ function isExplicitSpdxExpression(text) {
861
+ // No SPDX expression holds a comma, but the expression parser lets one
862
+ // through, which would take a whole pacman licence list for one expression.
863
+ return (
864
+ !text.includes(",") &&
865
+ (text.split(/\s+/u).some((token) => SPDX_OPERATORS.has(token)) ||
866
+ text.includes("(")) &&
867
+ isSpdxLicenseExpression(text)
868
+ );
869
+ }
870
+
871
+ function osPackageLicenses(text, separator) {
872
+ const license = `${text ?? ""}`.split(/\s+/u).filter(Boolean).join(" ");
873
+ if (!license) {
874
+ return [];
875
+ }
876
+ if (isExplicitSpdxExpression(license)) {
877
+ return [{ expression: license }];
878
+ }
879
+ const entries = [];
880
+ const items = separator ? license.split(separator) : [license];
881
+ for (const item of items.slice(0, MAX_OS_PACKAGE_LICENSES)) {
882
+ const entry = item.trim();
883
+ if (!entry || SPDX_OPERATORS.has(entry)) {
884
+ continue;
885
+ }
886
+ if (isExplicitSpdxExpression(entry)) {
887
+ entries.push({ expression: entry });
888
+ continue;
889
+ }
890
+ const id = findLicenseId(entry);
891
+ entries.push(
892
+ spdxLicenses.includes(id)
893
+ ? { license: { id } }
894
+ : { license: { name: entry } },
895
+ );
896
+ }
897
+ // A list that holds an expression must become one expression, since a
898
+ // component's licenses are either licences or a single expression. Every
899
+ // entry applies, so they are joined with AND.
900
+ if (entries.length > 1 && entries.some((entry) => entry.expression)) {
901
+ const parts = entries.map((entry) =>
902
+ entry.expression ? `(${entry.expression})` : entry.license.id,
903
+ );
904
+ const expression = parts.join(" AND ");
905
+ if (parts.every(Boolean) && isSpdxLicenseExpression(expression)) {
906
+ return [{ expression }];
907
+ }
908
+ }
909
+ return adjustLicenseInformation(entries);
910
+ }
911
+
912
+ // The home page of a package. A URL a package carries is untrusted text: only
913
+ // an http(s) URL is kept, without credentials, query or fragment.
914
+ function osPackageWebsite(value) {
915
+ const cleaned = sanitizeBomUrl(`${value ?? ""}`.trim());
916
+ try {
917
+ const { protocol } = new URL(cleaned);
918
+ return protocol === "http:" || protocol === "https:" ? cleaned : undefined;
919
+ } catch {
920
+ return undefined;
921
+ }
922
+ }
923
+
924
+ /**
925
+ * Convert the packages of an apk installed database into components.
926
+ *
927
+ * The packages are named as the container scan names an Alpine, Wolfi or
928
+ * Chainguard package, and grouped with the other OS package categories under
929
+ * `cdx:osquery:category` = `apk_packages`. Beyond what an osquery package row
930
+ * gives, an apk record knows its licence and home page, which become
931
+ * `licenses` and a `website` external reference.
932
+ *
933
+ * @param {Object[]} packages Packages from readApkInstalledDb()
934
+ * @returns {Object[]} Components
935
+ */
936
+ export function convertApkPackages(packages) {
937
+ const components = [];
938
+ for (const pkg of packages || []) {
939
+ const row = { name: pkg.name, version: pkg.version };
940
+ for (const column of ["arch", "origin", "maintainer"]) {
941
+ if (pkg[column]) {
942
+ row[column] = pkg[column];
943
+ }
944
+ }
945
+ const [component] = convertOSQueryResults(
946
+ "apk_packages",
947
+ { purlType: "apk" },
948
+ [row],
949
+ );
950
+ if (!component) {
951
+ continue;
952
+ }
953
+ // The description is set here and not in the row. The GTFOBins matcher reads
954
+ // a row's description for command names, which a deb or rpm row has no
955
+ // column for, and the free text of an apk description matches by chance:
956
+ // "Alpine base dir structure" is no use of the alpine mail client.
957
+ if (pkg.description) {
958
+ component.description = pkg.description;
959
+ }
960
+ const licenses = osPackageLicenses(pkg.license, /\s+/u);
961
+ if (licenses.length) {
962
+ component.licenses = licenses;
963
+ }
964
+ const website = osPackageWebsite(pkg.url);
965
+ if (website) {
966
+ component.externalReferences = [{ type: "website", url: website }];
967
+ }
968
+ components.push(component);
969
+ }
970
+ return components;
971
+ }
972
+
973
+ // The frontmatter and osquery columns of a skill row that become properties,
974
+ // named as the ai-skill project scan names them where it has a name.
975
+ const AGENT_SKILL_PROPERTY_NAMES = [
976
+ ["name", "cdx:skill:name"],
977
+ ["description", "cdx:skill:description"],
978
+ ["license", "cdx:skill:license"],
979
+ ["compatibility", "cdx:skill:compatibility"],
980
+ ["agent", "cdx:skill:agent"],
981
+ ["scope", "cdx:skill:scope"],
982
+ ["allowed_tools", "cdx:skill:allowedTools"],
983
+ ["resource_count", "cdx:skill:resourceCount"],
984
+ ["script_count", "cdx:skill:scriptCount"],
985
+ ["username", "username"],
986
+ ];
987
+ const SHA256_HEX = /^[0-9a-f]{64}$/u;
988
+
989
+ // A skill's licence is free text ("Complete terms in LICENSE.txt"), so it
990
+ // becomes `licenses` only when it is an SPDX id or expression.
991
+ function agentSkillLicenses(text) {
992
+ const license = `${text ?? ""}`.trim();
993
+ if (!license) {
994
+ return [];
995
+ }
996
+ if (isExplicitSpdxExpression(license)) {
997
+ return [{ expression: license }];
998
+ }
999
+ const id = findLicenseId(license);
1000
+ return spdxLicenses.includes(id) ? [{ license: { id } }] : [];
1001
+ }
1002
+
1003
+ /**
1004
+ * Convert the rows of the `ai_agent_skills` query into components.
1005
+ *
1006
+ * A skill installed on the host is what the `ai-skill` project scan finds in a
1007
+ * repository, so it is a `file` component with the same `cdx:file:kind` and
1008
+ * `cdx:skill:*` properties, and no purl: a skill has no package identity. Its
1009
+ * bom-ref is the SHA-256 of its SKILL.md rather than the path, which is under a
1010
+ * home directory, and where it was found is an occurrence. The same skill
1011
+ * found under several roots, such as a plugin marketplace checkout and the
1012
+ * plugin cache, is one component with one occurrence for each.
1013
+ *
1014
+ * @param {Object[]} results Rows of `ai_agent_skills` joined with `hash`
1015
+ * @returns {Object[]} Components
1016
+ */
1017
+ export function convertAgentSkillRows(results) {
1018
+ const components = [];
1019
+ for (const row of results || []) {
1020
+ const skillFile = `${row.path ?? ""}`;
1021
+ const directory = `${row.directory ?? ""}` || dirname(skillFile);
1022
+ // A SKILL.md whose frontmatter osquery cannot read has no name; the
1023
+ // project scan names such a skill after its directory too.
1024
+ const name = sanitizeOsQueryIdentity(row.name || basename(directory));
1025
+ if (!skillFile || !name) {
1026
+ continue;
1027
+ }
1028
+ const version = row.version || "";
1029
+ const sha256 = SHA256_HEX.test(`${row.sha256 ?? ""}`)
1030
+ ? row.sha256
1031
+ : undefined;
1032
+ const properties = [
1033
+ { name: "cdx:osquery:category", value: "ai_agent_skills" },
1034
+ { name: "cdx:file:kind", value: "skill-file" },
1035
+ { name: "cdx:agent:inventorySource", value: "osquery" },
1036
+ ];
1037
+ for (const [column, propertyName] of AGENT_SKILL_PROPERTY_NAMES) {
1038
+ const value = `${row[column] ?? ""}`.trim();
1039
+ if (value && value !== "null") {
1040
+ properties.push({ name: propertyName, value });
1041
+ }
1042
+ }
1043
+ if (Number(row.script_count) > 0) {
1044
+ properties.push({ name: "cdx:skill:hasScripts", value: "true" });
1045
+ }
1046
+ properties.push({
1047
+ name: "cdx:skill:specValid",
1048
+ value: isAgentSkillSpecValid(row) ? "true" : "false",
1049
+ });
1050
+ const component = {
1051
+ name,
1052
+ version,
1053
+ description: row.description || "",
1054
+ type: "file",
1055
+ "bom-ref": createOsQueryFallbackBomRef(
1056
+ "ai_agent_skills",
1057
+ "file",
1058
+ name,
1059
+ version,
1060
+ sha256 ? "sha256" : "scope",
1061
+ sha256 || `${row.agent || ""}/${row.scope || ""}`,
1062
+ ),
1063
+ properties,
1064
+ };
1065
+ if (sha256) {
1066
+ component.hashes = [{ alg: "SHA-256", content: sha256 }];
1067
+ }
1068
+ const licenses = agentSkillLicenses(row.license);
1069
+ if (licenses.length) {
1070
+ component.licenses = licenses;
1071
+ }
1072
+ const occurrence = createOccurrenceEvidence(skillFile);
1073
+ if (occurrence) {
1074
+ component.evidence = { occurrences: [occurrence] };
1075
+ }
1076
+ components.push(component);
1077
+ }
1078
+ return mergeLocatedPackages(components);
621
1079
  }
622
1080
  function createOsQueryCryptoProperties(queryCategory, res, version) {
623
1081
  switch (queryCategory) {