@cdxgen/cdxgen 0.0.1 → 13.0.1

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 (722) hide show
  1. package/README.md +921 -87
  2. package/bin/audit.js +239 -0
  3. package/bin/cdxgen.js +2335 -0
  4. package/bin/convert.js +220 -0
  5. package/bin/evinse.js +353 -0
  6. package/bin/hbom.js +504 -0
  7. package/bin/repl.js +2224 -0
  8. package/bin/sign.js +195 -0
  9. package/bin/tracebom.js +303 -0
  10. package/bin/validate.js +268 -0
  11. package/bin/verify.js +189 -0
  12. package/data/README.md +307 -0
  13. package/data/bom-1.4.schema.json +1605 -0
  14. package/data/bom-1.5.schema.json +3660 -0
  15. package/data/bom-1.6.schema.json +5511 -0
  16. package/data/bom-1.7.schema.json +6700 -0
  17. package/data/cbomosdb-queries.json +68 -0
  18. package/data/component-tags.json +339 -0
  19. package/data/container-knowledge-index.json +125 -0
  20. package/data/cosdb-queries.json +17 -0
  21. package/data/crypto-oid.json +1998 -0
  22. package/data/cryptography-defs.json +3793 -0
  23. package/data/cryptography-defs.schema.json +576 -0
  24. package/data/cyclonedx-2.0-bundled.schema.json +7182 -0
  25. package/data/frameworks-list.json +219 -0
  26. package/data/glibc-stdlib.json +117 -0
  27. package/data/gtfobins-index.json +6296 -0
  28. package/data/helpers/init.gradle +71 -0
  29. package/data/jsf-0.82.schema.json +211 -0
  30. package/data/known-licenses.json +82 -0
  31. package/data/lic-mapping.json +338 -0
  32. package/data/license-aliases.json +6624 -0
  33. package/data/license-db.json +39059 -0
  34. package/data/license-deprecations.json +302 -0
  35. package/data/lolbas-index.json +150 -0
  36. package/data/predictive-audit-allowlist.json +11 -0
  37. package/data/pypi-pkg-aliases.json +1199 -0
  38. package/data/python-stdlib.json +308 -0
  39. package/data/queries-darwin.json +193 -0
  40. package/data/queries-win.json +257 -0
  41. package/data/queries.json +268 -0
  42. package/data/ruby-known-modules.json +281 -0
  43. package/data/rules/ai-agent-governance.yaml +323 -0
  44. package/data/rules/ai-bom.yaml +268 -0
  45. package/data/rules/ai-oversight.yaml +186 -0
  46. package/data/rules/ai-provenance.yaml +92 -0
  47. package/data/rules/asar-archives.yaml +150 -0
  48. package/data/rules/cbom-security.yaml +165 -0
  49. package/data/rules/chrome-extensions.yaml +248 -0
  50. package/data/rules/ci-permissions.yaml +802 -0
  51. package/data/rules/container-risk.yaml +353 -0
  52. package/data/rules/dependency-sources.yaml +236 -0
  53. package/data/rules/golem-go.yaml +229 -0
  54. package/data/rules/hbom-compliance.yaml +325 -0
  55. package/data/rules/hbom-performance.yaml +307 -0
  56. package/data/rules/hbom-security.yaml +248 -0
  57. package/data/rules/host-topology.yaml +165 -0
  58. package/data/rules/mcp-servers.yaml +388 -0
  59. package/data/rules/obom-runtime.yaml +1776 -0
  60. package/data/rules/package-integrity.yaml +364 -0
  61. package/data/rules/rootfs-hardening.yaml +179 -0
  62. package/data/rules/vscode-extensions.yaml +237 -0
  63. package/data/spdx-export.schema.json +6794 -0
  64. package/data/spdx-license-list.json +7449 -0
  65. package/data/spdx-licenses.json +759 -0
  66. package/data/spdx.schema.json +819 -0
  67. package/data/templates/README.md +3 -0
  68. package/data/templates/asvs-4.0.3.cdx.json +2943 -0
  69. package/data/templates/asvs-5.0.cdx.json +3046 -0
  70. package/data/templates/bsimm-v13.cdx.json +1008 -0
  71. package/data/templates/masvs-2.0.0.cdx.json +225 -0
  72. package/data/templates/nist-ssdf-1.1.cdx.json +828 -0
  73. package/data/templates/pcissc-secure-slc-1.1.cdx.json +1027 -0
  74. package/data/templates/scvs-1.0.0.cdx.json +813 -0
  75. package/data/templates/ssaf-DRAFT-2023-11.cdx.json +127 -0
  76. package/data/vendor-alias.json +41967 -0
  77. package/data/wrapdb-releases.json +4227 -0
  78. package/index.cjs +15 -0
  79. package/lib/audit/index.js +2800 -0
  80. package/lib/audit/progress.js +114 -0
  81. package/lib/audit/reporters.js +937 -0
  82. package/lib/audit/scoring.js +560 -0
  83. package/lib/audit/targets.js +798 -0
  84. package/lib/cli/bomAssembly.js +1882 -0
  85. package/lib/cli/cliOptions.js +543 -0
  86. package/lib/cli/index.js +2759 -0
  87. package/lib/cli/jsBom.js +2087 -0
  88. package/lib/cli/jvmBom.js +1858 -0
  89. package/lib/cli/managedBom.js +2083 -0
  90. package/lib/cli/nativeBom.js +2125 -0
  91. package/lib/core/activity.js +1087 -0
  92. package/lib/core/env.js +719 -0
  93. package/lib/core/fs.js +1058 -0
  94. package/lib/core/httpClient.js +730 -0
  95. package/lib/core/logger.js +280 -0
  96. package/lib/core/parallel.js +323 -0
  97. package/lib/core/paths.js +70 -0
  98. package/lib/core/projectConfig.js +122 -0
  99. package/lib/core/propertySanitizer.js +174 -0
  100. package/lib/core/spdx.js +5 -0
  101. package/lib/core/state.js +59 -0
  102. package/lib/core/text.js +207 -0
  103. package/lib/core/ui.js +920 -0
  104. package/lib/core/urls.js +84 -0
  105. package/lib/ecosystems/asarutils.js +1636 -0
  106. package/lib/ecosystems/bunutils.js +402 -0
  107. package/lib/ecosystems/caxa.js +77 -0
  108. package/lib/ecosystems/chromextutils.js +1174 -0
  109. package/lib/ecosystems/cmakeResolver.js +587 -0
  110. package/lib/ecosystems/cppEvidence.js +305 -0
  111. package/lib/ecosystems/denoutils.js +1094 -0
  112. package/lib/ecosystems/dotnetutils.js +132 -0
  113. package/lib/ecosystems/ecosystems.js +2772 -0
  114. package/lib/ecosystems/golem.js +1807 -0
  115. package/lib/ecosystems/gradleutils.js +1048 -0
  116. package/lib/ecosystems/jsEvidence.js +317 -0
  117. package/lib/ecosystems/npmutils.js +586 -0
  118. package/lib/ecosystems/parsers-bazel.js +875 -0
  119. package/lib/ecosystems/parsers-dotnet.js +1430 -0
  120. package/lib/ecosystems/parsers-gleam.js +302 -0
  121. package/lib/ecosystems/parsers-go.js +818 -0
  122. package/lib/ecosystems/parsers-js.js +4222 -0
  123. package/lib/ecosystems/parsers-jvm.js +948 -0
  124. package/lib/ecosystems/parsers-misc.js +3191 -0
  125. package/lib/ecosystems/parsers-python.js +2070 -0
  126. package/lib/ecosystems/parsers-rust.js +1294 -0
  127. package/lib/ecosystems/parsers-zig.js +861 -0
  128. package/lib/ecosystems/pep770.js +341 -0
  129. package/lib/ecosystems/pylockutils.js +427 -0
  130. package/lib/ecosystems/pythonutils.js +1281 -0
  131. package/lib/ecosystems/registryProvenance.js +1003 -0
  132. package/lib/ecosystems/remote/huggingface.js +1343 -0
  133. package/lib/ecosystems/rubyutils.js +1629 -0
  134. package/lib/ecosystems/rusi.js +820 -0
  135. package/lib/ecosystems/sbtutils.js +972 -0
  136. package/lib/ecosystems/tea.js +607 -0
  137. package/lib/ecosystems/utils.js +583 -0
  138. package/lib/ecosystems/zigResolver.js +410 -0
  139. package/lib/evinser/db.js +145 -0
  140. package/lib/evinser/evinser.js +2362 -0
  141. package/lib/evinser/scalasem.js +65 -0
  142. package/lib/evinser/swiftsem.js +767 -0
  143. package/lib/helpers/annotationFormatter.js +84 -0
  144. package/lib/helpers/bomSigner.js +312 -0
  145. package/lib/helpers/exportUtils.js +160 -0
  146. package/lib/helpers/remote/dependency-track.js +117 -0
  147. package/lib/helpers/versutils.js +202 -0
  148. package/lib/helpers/vsixutils.js +1069 -0
  149. package/lib/inventory/agentFormulationParser.js +322 -0
  150. package/lib/inventory/aiCollector.js +2140 -0
  151. package/lib/inventory/aiInventory.js +414 -0
  152. package/lib/inventory/aiModelVariants.js +121 -0
  153. package/lib/inventory/aiOversightCollector.js +661 -0
  154. package/lib/inventory/aiProvenanceCollector.js +822 -0
  155. package/lib/inventory/analyzer.js +6089 -0
  156. package/lib/inventory/analyzerScope.js +807 -0
  157. package/lib/inventory/astWorker.js +20 -0
  158. package/lib/inventory/atomUtils.js +1092 -0
  159. package/lib/inventory/auditCategories.js +192 -0
  160. package/lib/inventory/bomUtils.js +369 -0
  161. package/lib/inventory/cacheCommands.js +182 -0
  162. package/lib/inventory/cacheDir.js +104 -0
  163. package/lib/inventory/cbomutils.js +590 -0
  164. package/lib/inventory/cdxrs.js +434 -0
  165. package/lib/inventory/ciParsers/azurePipelines.js +295 -0
  166. package/lib/inventory/ciParsers/circleCi.js +286 -0
  167. package/lib/inventory/ciParsers/common.js +24 -0
  168. package/lib/inventory/ciParsers/githubActions.js +2434 -0
  169. package/lib/inventory/ciParsers/gitlabCi.js +213 -0
  170. package/lib/inventory/ciParsers/jenkins.js +180 -0
  171. package/lib/inventory/citations.js +234 -0
  172. package/lib/inventory/communityAiConfigParser.js +756 -0
  173. package/lib/inventory/containerRisk.js +204 -0
  174. package/lib/inventory/cryptoAlgorithmFamily.js +258 -0
  175. package/lib/inventory/deps.js +807 -0
  176. package/lib/inventory/depsUtils.js +588 -0
  177. package/lib/inventory/display.js +2128 -0
  178. package/lib/inventory/dosai.js +601 -0
  179. package/lib/inventory/dosaiParsers.js +154 -0
  180. package/lib/inventory/dynamic.js +145 -0
  181. package/lib/inventory/envcontext.js +1793 -0
  182. package/lib/inventory/evidenceUtils.js +774 -0
  183. package/lib/inventory/fetchBatch.js +779 -0
  184. package/lib/inventory/fetchRate.js +255 -0
  185. package/lib/inventory/forgeEnricher.js +300 -0
  186. package/lib/inventory/formulationParsers.js +422 -0
  187. package/lib/inventory/gtfobins.js +339 -0
  188. package/lib/inventory/hbom.js +719 -0
  189. package/lib/inventory/hbomAnalysis.js +310 -0
  190. package/lib/inventory/hbomLoader.js +35 -0
  191. package/lib/inventory/hostTopology.js +835 -0
  192. package/lib/inventory/inventoryStats.js +100 -0
  193. package/lib/inventory/jsonLike.js +121 -0
  194. package/lib/inventory/licenseEnhancer.js +918 -0
  195. package/lib/inventory/lolbas.js +285 -0
  196. package/lib/inventory/mcp.js +253 -0
  197. package/lib/inventory/mcpConfigParser.js +683 -0
  198. package/lib/inventory/mcpDiscovery.js +112 -0
  199. package/lib/inventory/mcpPinning.js +209 -0
  200. package/lib/inventory/osPackageResolver.js +493 -0
  201. package/lib/inventory/osinfo.js +383 -0
  202. package/lib/inventory/osqueryTransform.js +274 -0
  203. package/lib/inventory/plugins.js +466 -0
  204. package/lib/inventory/protobom.js +231 -0
  205. package/lib/inventory/protobomLoader.js +43 -0
  206. package/lib/inventory/provenanceUtils.js +224 -0
  207. package/lib/inventory/purl.js +749 -0
  208. package/lib/inventory/rustFormulationParser.js +339 -0
  209. package/lib/inventory/sbomDocument.js +340 -0
  210. package/lib/inventory/source.js +1488 -0
  211. package/lib/inventory/spdx.js +361 -0
  212. package/lib/inventory/spdxExpression.js +512 -0
  213. package/lib/inventory/spdxUtils.js +107 -0
  214. package/lib/inventory/table.js +267 -0
  215. package/lib/inventory/traceRunner.js +439 -0
  216. package/lib/inventory/unicodeScan.js +147 -0
  217. package/lib/managers/binary.js +3459 -0
  218. package/lib/managers/containerutils.js +125 -0
  219. package/lib/managers/docker.js +2145 -0
  220. package/lib/managers/dockerConnection.js +162 -0
  221. package/lib/managers/oci.js +704 -0
  222. package/lib/managers/piptree.js +267 -0
  223. package/lib/parsers/cmakeCache.js +199 -0
  224. package/lib/parsers/gguf.js +492 -0
  225. package/lib/parsers/gitAiNotes.js +233 -0
  226. package/lib/parsers/gitmodules.js +239 -0
  227. package/lib/parsers/htmlExtract.js +610 -0
  228. package/lib/parsers/huggingfaceManifest.js +551 -0
  229. package/lib/parsers/huggingfaceUtils.js +506 -0
  230. package/lib/parsers/iri.js +618 -0
  231. package/lib/parsers/npmrc.js +99 -0
  232. package/lib/parsers/ollama.js +101 -0
  233. package/lib/parsers/xml.js +841 -0
  234. package/lib/server/openapi.yaml +433 -0
  235. package/lib/server/server.js +616 -0
  236. package/lib/stages/postgen/annotator.js +977 -0
  237. package/lib/stages/postgen/auditBom.js +468 -0
  238. package/lib/stages/postgen/postgen.js +1239 -0
  239. package/lib/stages/postgen/ruleEngine.js +731 -0
  240. package/lib/stages/postgen/sortBom.js +195 -0
  241. package/lib/stages/postgen/spdxConverter.js +796 -0
  242. package/lib/stages/postgen/specVersionCompat.js +703 -0
  243. package/lib/stages/pregen/envAudit.js +388 -0
  244. package/lib/stages/pregen/pregen.js +637 -0
  245. package/lib/third-party/README.md +9 -0
  246. package/lib/third-party/arborist/CHANGELOG.md +1109 -0
  247. package/lib/third-party/arborist/LICENSE.md +20 -0
  248. package/lib/third-party/arborist/README.md +315 -0
  249. package/lib/third-party/arborist/lib/arborist/index.js +258 -0
  250. package/lib/third-party/arborist/lib/arborist/load-actual.js +605 -0
  251. package/lib/third-party/arborist/lib/arborist/load-virtual.js +300 -0
  252. package/lib/third-party/arborist/lib/bin-links.js +120 -0
  253. package/lib/third-party/arborist/lib/calc-dep-flags.js +130 -0
  254. package/lib/third-party/arborist/lib/case-insensitive-map.js +51 -0
  255. package/lib/third-party/arborist/lib/consistent-resolve.js +42 -0
  256. package/lib/third-party/arborist/lib/debug.js +50 -0
  257. package/lib/third-party/arborist/lib/dep-valid.js +190 -0
  258. package/lib/third-party/arborist/lib/edge.js +417 -0
  259. package/lib/third-party/arborist/lib/from-path.js +36 -0
  260. package/lib/third-party/arborist/lib/gather-dep-set.js +43 -0
  261. package/lib/third-party/arborist/lib/index.js +22 -0
  262. package/lib/third-party/arborist/lib/inventory.js +138 -0
  263. package/lib/third-party/arborist/lib/link.js +169 -0
  264. package/lib/third-party/arborist/lib/node.js +1673 -0
  265. package/lib/third-party/arborist/lib/npm-extension.js +45 -0
  266. package/lib/third-party/arborist/lib/override-resolves.js +11 -0
  267. package/lib/third-party/arborist/lib/override-set.js +319 -0
  268. package/lib/third-party/arborist/lib/package-extensions.js +289 -0
  269. package/lib/third-party/arborist/lib/printable.js +210 -0
  270. package/lib/third-party/arborist/lib/proc-log.js +30 -0
  271. package/lib/third-party/arborist/lib/realpath.js +100 -0
  272. package/lib/third-party/arborist/lib/relpath.js +4 -0
  273. package/lib/third-party/arborist/lib/shrinkwrap.js +1291 -0
  274. package/lib/third-party/arborist/lib/spec-from-lock.js +34 -0
  275. package/lib/third-party/arborist/lib/tree-check.js +165 -0
  276. package/lib/third-party/arborist/lib/validate-npm-package-name.js +107 -0
  277. package/lib/third-party/arborist/lib/version-from-tgz.js +51 -0
  278. package/lib/third-party/arborist/lib/yarn-lock.js +397 -0
  279. package/lib/validator/bomValidator.js +985 -0
  280. package/lib/validator/complianceEngine.js +241 -0
  281. package/lib/validator/complianceRules.js +1680 -0
  282. package/lib/validator/generated/runtime.mjs +17 -0
  283. package/lib/validator/generated/validate-1.6.mjs +21258 -0
  284. package/lib/validator/generated/validate-1.7.mjs +24803 -0
  285. package/lib/validator/generated/validate-2.0.mjs +22931 -0
  286. package/lib/validator/index.js +246 -0
  287. package/lib/validator/reporters/annotations.js +142 -0
  288. package/lib/validator/reporters/console.js +160 -0
  289. package/lib/validator/reporters/index.js +41 -0
  290. package/lib/validator/reporters/json.js +37 -0
  291. package/lib/validator/reporters/sarif.js +184 -0
  292. package/package.json +209 -26
  293. package/types/bin/audit.d.ts +3 -0
  294. package/types/bin/audit.d.ts.map +1 -0
  295. package/types/bin/cdxgen.d.ts +3 -0
  296. package/types/bin/cdxgen.d.ts.map +1 -0
  297. package/types/bin/convert.d.ts +3 -0
  298. package/types/bin/convert.d.ts.map +1 -0
  299. package/types/bin/evinse.d.ts +3 -0
  300. package/types/bin/evinse.d.ts.map +1 -0
  301. package/types/bin/hbom.d.ts +3 -0
  302. package/types/bin/hbom.d.ts.map +1 -0
  303. package/types/bin/repl.d.ts +3 -0
  304. package/types/bin/repl.d.ts.map +1 -0
  305. package/types/bin/sign.d.ts +3 -0
  306. package/types/bin/sign.d.ts.map +1 -0
  307. package/types/bin/tracebom.d.ts +3 -0
  308. package/types/bin/tracebom.d.ts.map +1 -0
  309. package/types/bin/validate.d.ts +14 -0
  310. package/types/bin/validate.d.ts.map +1 -0
  311. package/types/bin/verify.d.ts +3 -0
  312. package/types/bin/verify.d.ts.map +1 -0
  313. package/types/lib/audit/index.d.ts +153 -0
  314. package/types/lib/audit/index.d.ts.map +1 -0
  315. package/types/lib/audit/progress.d.ts +31 -0
  316. package/types/lib/audit/progress.d.ts.map +1 -0
  317. package/types/lib/audit/reporters.d.ts +62 -0
  318. package/types/lib/audit/reporters.d.ts.map +1 -0
  319. package/types/lib/audit/scoring.d.ts +36 -0
  320. package/types/lib/audit/scoring.d.ts.map +1 -0
  321. package/types/lib/audit/targets.d.ts +77 -0
  322. package/types/lib/audit/targets.d.ts.map +1 -0
  323. package/types/lib/cli/bomAssembly.d.ts +329 -0
  324. package/types/lib/cli/bomAssembly.d.ts.map +1 -0
  325. package/types/lib/cli/cliOptions.d.ts +156 -0
  326. package/types/lib/cli/cliOptions.d.ts.map +1 -0
  327. package/types/lib/cli/index.d.ts +107 -0
  328. package/types/lib/cli/index.d.ts.map +1 -0
  329. package/types/lib/cli/jsBom.d.ts +94 -0
  330. package/types/lib/cli/jsBom.d.ts.map +1 -0
  331. package/types/lib/cli/jvmBom.d.ts +54 -0
  332. package/types/lib/cli/jvmBom.d.ts.map +1 -0
  333. package/types/lib/cli/managedBom.d.ts +105 -0
  334. package/types/lib/cli/managedBom.d.ts.map +1 -0
  335. package/types/lib/cli/nativeBom.d.ts +143 -0
  336. package/types/lib/cli/nativeBom.d.ts.map +1 -0
  337. package/types/lib/core/activity.d.ts +178 -0
  338. package/types/lib/core/activity.d.ts.map +1 -0
  339. package/types/lib/core/env.d.ts +192 -0
  340. package/types/lib/core/env.d.ts.map +1 -0
  341. package/types/lib/core/fs.d.ts +158 -0
  342. package/types/lib/core/fs.d.ts.map +1 -0
  343. package/types/lib/core/httpClient.d.ts +149 -0
  344. package/types/lib/core/httpClient.d.ts.map +1 -0
  345. package/types/lib/core/logger.d.ts +50 -0
  346. package/types/lib/core/logger.d.ts.map +1 -0
  347. package/types/lib/core/parallel.d.ts +95 -0
  348. package/types/lib/core/parallel.d.ts.map +1 -0
  349. package/types/lib/core/paths.d.ts +36 -0
  350. package/types/lib/core/paths.d.ts.map +1 -0
  351. package/types/lib/core/projectConfig.d.ts +23 -0
  352. package/types/lib/core/projectConfig.d.ts.map +1 -0
  353. package/types/lib/core/propertySanitizer.d.ts +23 -0
  354. package/types/lib/core/propertySanitizer.d.ts.map +1 -0
  355. package/types/lib/core/spdx.d.ts +5 -0
  356. package/types/lib/core/spdx.d.ts.map +1 -0
  357. package/types/lib/core/state.d.ts +20 -0
  358. package/types/lib/core/state.d.ts.map +1 -0
  359. package/types/lib/core/text.d.ts +78 -0
  360. package/types/lib/core/text.d.ts.map +1 -0
  361. package/types/lib/core/ui.d.ts +87 -0
  362. package/types/lib/core/ui.d.ts.map +1 -0
  363. package/types/lib/core/urls.d.ts +43 -0
  364. package/types/lib/core/urls.d.ts.map +1 -0
  365. package/types/lib/ecosystems/asarutils.d.ts +71 -0
  366. package/types/lib/ecosystems/asarutils.d.ts.map +1 -0
  367. package/types/lib/ecosystems/bunutils.d.ts +38 -0
  368. package/types/lib/ecosystems/bunutils.d.ts.map +1 -0
  369. package/types/lib/ecosystems/caxa.d.ts +8 -0
  370. package/types/lib/ecosystems/caxa.d.ts.map +1 -0
  371. package/types/lib/ecosystems/chromextutils.d.ts +97 -0
  372. package/types/lib/ecosystems/chromextutils.d.ts.map +1 -0
  373. package/types/lib/ecosystems/cmakeResolver.d.ts +176 -0
  374. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -0
  375. package/types/lib/ecosystems/cppEvidence.d.ts +35 -0
  376. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -0
  377. package/types/lib/ecosystems/denoutils.d.ts +76 -0
  378. package/types/lib/ecosystems/denoutils.d.ts.map +1 -0
  379. package/types/lib/ecosystems/dotnetutils.d.ts +45 -0
  380. package/types/lib/ecosystems/dotnetutils.d.ts.map +1 -0
  381. package/types/lib/ecosystems/ecosystems.d.ts +279 -0
  382. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -0
  383. package/types/lib/ecosystems/golem.d.ts +60 -0
  384. package/types/lib/ecosystems/golem.d.ts.map +1 -0
  385. package/types/lib/ecosystems/gradleutils.d.ts +139 -0
  386. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -0
  387. package/types/lib/ecosystems/jsEvidence.d.ts +17 -0
  388. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -0
  389. package/types/lib/ecosystems/npmutils.d.ts +183 -0
  390. package/types/lib/ecosystems/npmutils.d.ts.map +1 -0
  391. package/types/lib/ecosystems/parsers-bazel.d.ts +36 -0
  392. package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -0
  393. package/types/lib/ecosystems/parsers-dotnet.d.ts +116 -0
  394. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -0
  395. package/types/lib/ecosystems/parsers-gleam.d.ts +33 -0
  396. package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -0
  397. package/types/lib/ecosystems/parsers-go.d.ts +92 -0
  398. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -0
  399. package/types/lib/ecosystems/parsers-js.d.ts +242 -0
  400. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -0
  401. package/types/lib/ecosystems/parsers-jvm.d.ts +100 -0
  402. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -0
  403. package/types/lib/ecosystems/parsers-misc.d.ts +450 -0
  404. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -0
  405. package/types/lib/ecosystems/parsers-python.d.ts +145 -0
  406. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -0
  407. package/types/lib/ecosystems/parsers-rust.d.ts +56 -0
  408. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -0
  409. package/types/lib/ecosystems/parsers-zig.d.ts +83 -0
  410. package/types/lib/ecosystems/parsers-zig.d.ts.map +1 -0
  411. package/types/lib/ecosystems/pep770.d.ts +38 -0
  412. package/types/lib/ecosystems/pep770.d.ts.map +1 -0
  413. package/types/lib/ecosystems/pylockutils.d.ts +68 -0
  414. package/types/lib/ecosystems/pylockutils.d.ts.map +1 -0
  415. package/types/lib/ecosystems/pythonutils.d.ts +70 -0
  416. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -0
  417. package/types/lib/ecosystems/registryProvenance.d.ts +26 -0
  418. package/types/lib/ecosystems/registryProvenance.d.ts.map +1 -0
  419. package/types/lib/ecosystems/remote/huggingface.d.ts +31 -0
  420. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -0
  421. package/types/lib/ecosystems/rubyutils.d.ts +182 -0
  422. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -0
  423. package/types/lib/ecosystems/rusi.d.ts +40 -0
  424. package/types/lib/ecosystems/rusi.d.ts.map +1 -0
  425. package/types/lib/ecosystems/sbtutils.d.ts +179 -0
  426. package/types/lib/ecosystems/sbtutils.d.ts.map +1 -0
  427. package/types/lib/ecosystems/tea.d.ts +180 -0
  428. package/types/lib/ecosystems/tea.d.ts.map +1 -0
  429. package/types/lib/ecosystems/utils.d.ts +41 -0
  430. package/types/lib/ecosystems/utils.d.ts.map +1 -0
  431. package/types/lib/ecosystems/zigResolver.d.ts +70 -0
  432. package/types/lib/ecosystems/zigResolver.d.ts.map +1 -0
  433. package/types/lib/evinser/db.d.ts +17 -0
  434. package/types/lib/evinser/db.d.ts.map +1 -0
  435. package/types/lib/evinser/evinser.d.ts +354 -0
  436. package/types/lib/evinser/evinser.d.ts.map +1 -0
  437. package/types/lib/evinser/scalasem.d.ts +15 -0
  438. package/types/lib/evinser/scalasem.d.ts.map +1 -0
  439. package/types/lib/evinser/swiftsem.d.ts +103 -0
  440. package/types/lib/evinser/swiftsem.d.ts.map +1 -0
  441. package/types/lib/helpers/annotationFormatter.d.ts +23 -0
  442. package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
  443. package/types/lib/helpers/bomSigner.d.ts +28 -0
  444. package/types/lib/helpers/bomSigner.d.ts.map +1 -0
  445. package/types/lib/helpers/exportUtils.d.ts +52 -0
  446. package/types/lib/helpers/exportUtils.d.ts.map +1 -0
  447. package/types/lib/helpers/remote/dependency-track.d.ts +23 -0
  448. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
  449. package/types/lib/helpers/versutils.d.ts +8 -0
  450. package/types/lib/helpers/versutils.d.ts.map +1 -0
  451. package/types/lib/helpers/vsixutils.d.ts +130 -0
  452. package/types/lib/helpers/vsixutils.d.ts.map +1 -0
  453. package/types/lib/inventory/agentFormulationParser.d.ts +21 -0
  454. package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -0
  455. package/types/lib/inventory/aiCollector.d.ts +61 -0
  456. package/types/lib/inventory/aiCollector.d.ts.map +1 -0
  457. package/types/lib/inventory/aiInventory.d.ts +103 -0
  458. package/types/lib/inventory/aiInventory.d.ts.map +1 -0
  459. package/types/lib/inventory/aiModelVariants.d.ts +31 -0
  460. package/types/lib/inventory/aiModelVariants.d.ts.map +1 -0
  461. package/types/lib/inventory/aiOversightCollector.d.ts +28 -0
  462. package/types/lib/inventory/aiOversightCollector.d.ts.map +1 -0
  463. package/types/lib/inventory/aiProvenanceCollector.d.ts +13 -0
  464. package/types/lib/inventory/aiProvenanceCollector.d.ts.map +1 -0
  465. package/types/lib/inventory/analyzer.d.ts +157 -0
  466. package/types/lib/inventory/analyzer.d.ts.map +1 -0
  467. package/types/lib/inventory/analyzerScope.d.ts +106 -0
  468. package/types/lib/inventory/analyzerScope.d.ts.map +1 -0
  469. package/types/lib/inventory/astWorker.d.ts +9 -0
  470. package/types/lib/inventory/astWorker.d.ts.map +1 -0
  471. package/types/lib/inventory/atomUtils.d.ts +159 -0
  472. package/types/lib/inventory/atomUtils.d.ts.map +1 -0
  473. package/types/lib/inventory/auditCategories.d.ts +80 -0
  474. package/types/lib/inventory/auditCategories.d.ts.map +1 -0
  475. package/types/lib/inventory/bomUtils.d.ts +145 -0
  476. package/types/lib/inventory/bomUtils.d.ts.map +1 -0
  477. package/types/lib/inventory/cacheCommands.d.ts +28 -0
  478. package/types/lib/inventory/cacheCommands.d.ts.map +1 -0
  479. package/types/lib/inventory/cacheDir.d.ts +55 -0
  480. package/types/lib/inventory/cacheDir.d.ts.map +1 -0
  481. package/types/lib/inventory/cbomutils.d.ts +37 -0
  482. package/types/lib/inventory/cbomutils.d.ts.map +1 -0
  483. package/types/lib/inventory/cdxrs.d.ts +90 -0
  484. package/types/lib/inventory/cdxrs.d.ts.map +1 -0
  485. package/types/lib/inventory/ciParsers/azurePipelines.d.ts +27 -0
  486. package/types/lib/inventory/ciParsers/azurePipelines.d.ts.map +1 -0
  487. package/types/lib/inventory/ciParsers/circleCi.d.ts +27 -0
  488. package/types/lib/inventory/ciParsers/circleCi.d.ts.map +1 -0
  489. package/types/lib/inventory/ciParsers/common.d.ts +11 -0
  490. package/types/lib/inventory/ciParsers/common.d.ts.map +1 -0
  491. package/types/lib/inventory/ciParsers/githubActions.d.ts +38 -0
  492. package/types/lib/inventory/ciParsers/githubActions.d.ts.map +1 -0
  493. package/types/lib/inventory/ciParsers/gitlabCi.d.ts +26 -0
  494. package/types/lib/inventory/ciParsers/gitlabCi.d.ts.map +1 -0
  495. package/types/lib/inventory/ciParsers/jenkins.d.ts +26 -0
  496. package/types/lib/inventory/ciParsers/jenkins.d.ts.map +1 -0
  497. package/types/lib/inventory/citations.d.ts +90 -0
  498. package/types/lib/inventory/citations.d.ts.map +1 -0
  499. package/types/lib/inventory/communityAiConfigParser.d.ts +14 -0
  500. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -0
  501. package/types/lib/inventory/containerRisk.d.ts +20 -0
  502. package/types/lib/inventory/containerRisk.d.ts.map +1 -0
  503. package/types/lib/inventory/cryptoAlgorithmFamily.d.ts +37 -0
  504. package/types/lib/inventory/cryptoAlgorithmFamily.d.ts.map +1 -0
  505. package/types/lib/inventory/deps.d.ts +140 -0
  506. package/types/lib/inventory/deps.d.ts.map +1 -0
  507. package/types/lib/inventory/depsUtils.d.ts +74 -0
  508. package/types/lib/inventory/depsUtils.d.ts.map +1 -0
  509. package/types/lib/inventory/display.d.ts +227 -0
  510. package/types/lib/inventory/display.d.ts.map +1 -0
  511. package/types/lib/inventory/dosai.d.ts +130 -0
  512. package/types/lib/inventory/dosai.d.ts.map +1 -0
  513. package/types/lib/inventory/dosaiParsers.d.ts +59 -0
  514. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -0
  515. package/types/lib/inventory/dynamic.d.ts +25 -0
  516. package/types/lib/inventory/dynamic.d.ts.map +1 -0
  517. package/types/lib/inventory/envcontext.d.ts +377 -0
  518. package/types/lib/inventory/envcontext.d.ts.map +1 -0
  519. package/types/lib/inventory/evidenceUtils.d.ts +74 -0
  520. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -0
  521. package/types/lib/inventory/fetchBatch.d.ts +127 -0
  522. package/types/lib/inventory/fetchBatch.d.ts.map +1 -0
  523. package/types/lib/inventory/fetchRate.d.ts +118 -0
  524. package/types/lib/inventory/fetchRate.d.ts.map +1 -0
  525. package/types/lib/inventory/forgeEnricher.d.ts +31 -0
  526. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -0
  527. package/types/lib/inventory/formulationParsers.d.ts +50 -0
  528. package/types/lib/inventory/formulationParsers.d.ts.map +1 -0
  529. package/types/lib/inventory/gtfobins.d.ts +27 -0
  530. package/types/lib/inventory/gtfobins.d.ts.map +1 -0
  531. package/types/lib/inventory/hbom.d.ts +58 -0
  532. package/types/lib/inventory/hbom.d.ts.map +1 -0
  533. package/types/lib/inventory/hbomAnalysis.d.ts +56 -0
  534. package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -0
  535. package/types/lib/inventory/hbomLoader.d.ts +7 -0
  536. package/types/lib/inventory/hbomLoader.d.ts.map +1 -0
  537. package/types/lib/inventory/hostTopology.d.ts +47 -0
  538. package/types/lib/inventory/hostTopology.d.ts.map +1 -0
  539. package/types/lib/inventory/inventoryStats.d.ts +42 -0
  540. package/types/lib/inventory/inventoryStats.d.ts.map +1 -0
  541. package/types/lib/inventory/jsonLike.d.ts +23 -0
  542. package/types/lib/inventory/jsonLike.d.ts.map +1 -0
  543. package/types/lib/inventory/licenseEnhancer.d.ts +70 -0
  544. package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -0
  545. package/types/lib/inventory/lolbas.d.ts +16 -0
  546. package/types/lib/inventory/lolbas.d.ts.map +1 -0
  547. package/types/lib/inventory/mcp.d.ts +29 -0
  548. package/types/lib/inventory/mcp.d.ts.map +1 -0
  549. package/types/lib/inventory/mcpConfigParser.d.ts +20 -0
  550. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -0
  551. package/types/lib/inventory/mcpDiscovery.d.ts +33 -0
  552. package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -0
  553. package/types/lib/inventory/mcpPinning.d.ts +11 -0
  554. package/types/lib/inventory/mcpPinning.d.ts.map +1 -0
  555. package/types/lib/inventory/osPackageResolver.d.ts +49 -0
  556. package/types/lib/inventory/osPackageResolver.d.ts.map +1 -0
  557. package/types/lib/inventory/osinfo.d.ts +201 -0
  558. package/types/lib/inventory/osinfo.d.ts.map +1 -0
  559. package/types/lib/inventory/osqueryTransform.d.ts +84 -0
  560. package/types/lib/inventory/osqueryTransform.d.ts.map +1 -0
  561. package/types/lib/inventory/plugins.d.ts +58 -0
  562. package/types/lib/inventory/plugins.d.ts.map +1 -0
  563. package/types/lib/inventory/protobom.d.ts +71 -0
  564. package/types/lib/inventory/protobom.d.ts.map +1 -0
  565. package/types/lib/inventory/protobomLoader.d.ts +17 -0
  566. package/types/lib/inventory/protobomLoader.d.ts.map +1 -0
  567. package/types/lib/inventory/provenanceUtils.d.ts +98 -0
  568. package/types/lib/inventory/provenanceUtils.d.ts.map +1 -0
  569. package/types/lib/inventory/purl.d.ts +313 -0
  570. package/types/lib/inventory/purl.d.ts.map +1 -0
  571. package/types/lib/inventory/rustFormulationParser.d.ts +17 -0
  572. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -0
  573. package/types/lib/inventory/sbomDocument.d.ts +59 -0
  574. package/types/lib/inventory/sbomDocument.d.ts.map +1 -0
  575. package/types/lib/inventory/source.d.ts +163 -0
  576. package/types/lib/inventory/source.d.ts.map +1 -0
  577. package/types/lib/inventory/spdx.d.ts +66 -0
  578. package/types/lib/inventory/spdx.d.ts.map +1 -0
  579. package/types/lib/inventory/spdxExpression.d.ts +58 -0
  580. package/types/lib/inventory/spdxExpression.d.ts.map +1 -0
  581. package/types/lib/inventory/spdxUtils.d.ts +12 -0
  582. package/types/lib/inventory/spdxUtils.d.ts.map +1 -0
  583. package/types/lib/inventory/table.d.ts +22 -0
  584. package/types/lib/inventory/table.d.ts.map +1 -0
  585. package/types/lib/inventory/traceRunner.d.ts +80 -0
  586. package/types/lib/inventory/traceRunner.d.ts.map +1 -0
  587. package/types/lib/inventory/unicodeScan.d.ts +46 -0
  588. package/types/lib/inventory/unicodeScan.d.ts.map +1 -0
  589. package/types/lib/managers/binary.d.ts +147 -0
  590. package/types/lib/managers/binary.d.ts.map +1 -0
  591. package/types/lib/managers/containerutils.d.ts +30 -0
  592. package/types/lib/managers/containerutils.d.ts.map +1 -0
  593. package/types/lib/managers/docker.d.ts +157 -0
  594. package/types/lib/managers/docker.d.ts.map +1 -0
  595. package/types/lib/managers/dockerConnection.d.ts +42 -0
  596. package/types/lib/managers/dockerConnection.d.ts.map +1 -0
  597. package/types/lib/managers/oci.d.ts +21 -0
  598. package/types/lib/managers/oci.d.ts.map +1 -0
  599. package/types/lib/managers/piptree.d.ts +13 -0
  600. package/types/lib/managers/piptree.d.ts.map +1 -0
  601. package/types/lib/parsers/cmakeCache.d.ts +70 -0
  602. package/types/lib/parsers/cmakeCache.d.ts.map +1 -0
  603. package/types/lib/parsers/gguf.d.ts +39 -0
  604. package/types/lib/parsers/gguf.d.ts.map +1 -0
  605. package/types/lib/parsers/gitAiNotes.d.ts +15 -0
  606. package/types/lib/parsers/gitAiNotes.d.ts.map +1 -0
  607. package/types/lib/parsers/gitmodules.d.ts +88 -0
  608. package/types/lib/parsers/gitmodules.d.ts.map +1 -0
  609. package/types/lib/parsers/htmlExtract.d.ts +34 -0
  610. package/types/lib/parsers/htmlExtract.d.ts.map +1 -0
  611. package/types/lib/parsers/huggingfaceManifest.d.ts +153 -0
  612. package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -0
  613. package/types/lib/parsers/huggingfaceUtils.d.ts +155 -0
  614. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -0
  615. package/types/lib/parsers/iri.d.ts +68 -0
  616. package/types/lib/parsers/iri.d.ts.map +1 -0
  617. package/types/lib/parsers/npmrc.d.ts +33 -0
  618. package/types/lib/parsers/npmrc.d.ts.map +1 -0
  619. package/types/lib/parsers/ollama.d.ts +22 -0
  620. package/types/lib/parsers/ollama.d.ts.map +1 -0
  621. package/types/lib/parsers/xml.d.ts +47 -0
  622. package/types/lib/parsers/xml.d.ts.map +1 -0
  623. package/types/lib/server/server.d.ts +51 -0
  624. package/types/lib/server/server.d.ts.map +1 -0
  625. package/types/lib/stages/postgen/annotator.d.ts +36 -0
  626. package/types/lib/stages/postgen/annotator.d.ts.map +1 -0
  627. package/types/lib/stages/postgen/auditBom.d.ts +92 -0
  628. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
  629. package/types/lib/stages/postgen/postgen.d.ts +59 -0
  630. package/types/lib/stages/postgen/postgen.d.ts.map +1 -0
  631. package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
  632. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
  633. package/types/lib/stages/postgen/sortBom.d.ts +34 -0
  634. package/types/lib/stages/postgen/sortBom.d.ts.map +1 -0
  635. package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
  636. package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
  637. package/types/lib/stages/postgen/specVersionCompat.d.ts +71 -0
  638. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -0
  639. package/types/lib/stages/pregen/envAudit.d.ts +14 -0
  640. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
  641. package/types/lib/stages/pregen/pregen.d.ts +59 -0
  642. package/types/lib/stages/pregen/pregen.d.ts.map +1 -0
  643. package/types/lib/third-party/arborist/lib/arborist/index.d.ts +47 -0
  644. package/types/lib/third-party/arborist/lib/arborist/index.d.ts.map +1 -0
  645. package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts +4 -0
  646. package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts.map +1 -0
  647. package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts +4 -0
  648. package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts.map +1 -0
  649. package/types/lib/third-party/arborist/lib/bin-links.d.ts +18 -0
  650. package/types/lib/third-party/arborist/lib/bin-links.d.ts.map +1 -0
  651. package/types/lib/third-party/arborist/lib/calc-dep-flags.d.ts +3 -0
  652. package/types/lib/third-party/arborist/lib/calc-dep-flags.d.ts.map +1 -0
  653. package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts +9 -0
  654. package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts.map +1 -0
  655. package/types/lib/third-party/arborist/lib/consistent-resolve.d.ts +3 -0
  656. package/types/lib/third-party/arborist/lib/consistent-resolve.d.ts.map +1 -0
  657. package/types/lib/third-party/arborist/lib/debug.d.ts +3 -0
  658. package/types/lib/third-party/arborist/lib/debug.d.ts.map +1 -0
  659. package/types/lib/third-party/arborist/lib/dep-valid.d.ts +3 -0
  660. package/types/lib/third-party/arborist/lib/dep-valid.d.ts.map +1 -0
  661. package/types/lib/third-party/arborist/lib/edge.d.ts +46 -0
  662. package/types/lib/third-party/arborist/lib/edge.d.ts.map +1 -0
  663. package/types/lib/third-party/arborist/lib/from-path.d.ts +3 -0
  664. package/types/lib/third-party/arborist/lib/from-path.d.ts.map +1 -0
  665. package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts +3 -0
  666. package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts.map +1 -0
  667. package/types/lib/third-party/arborist/lib/index.d.ts +3 -0
  668. package/types/lib/third-party/arborist/lib/index.d.ts.map +1 -0
  669. package/types/lib/third-party/arborist/lib/inventory.d.ts +14 -0
  670. package/types/lib/third-party/arborist/lib/inventory.d.ts.map +1 -0
  671. package/types/lib/third-party/arborist/lib/link.d.ts +18 -0
  672. package/types/lib/third-party/arborist/lib/link.d.ts.map +1 -0
  673. package/types/lib/third-party/arborist/lib/node.d.ts +112 -0
  674. package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -0
  675. package/types/lib/third-party/arborist/lib/npm-extension.d.ts +18 -0
  676. package/types/lib/third-party/arborist/lib/npm-extension.d.ts.map +1 -0
  677. package/types/lib/third-party/arborist/lib/override-resolves.d.ts +3 -0
  678. package/types/lib/third-party/arborist/lib/override-resolves.d.ts.map +1 -0
  679. package/types/lib/third-party/arborist/lib/override-set.d.ts +27 -0
  680. package/types/lib/third-party/arborist/lib/override-set.d.ts.map +1 -0
  681. package/types/lib/third-party/arborist/lib/package-extensions.d.ts +36 -0
  682. package/types/lib/third-party/arborist/lib/package-extensions.d.ts.map +1 -0
  683. package/types/lib/third-party/arborist/lib/printable.d.ts +3 -0
  684. package/types/lib/third-party/arborist/lib/printable.d.ts.map +1 -0
  685. package/types/lib/third-party/arborist/lib/proc-log.d.ts +20 -0
  686. package/types/lib/third-party/arborist/lib/proc-log.d.ts.map +1 -0
  687. package/types/lib/third-party/arborist/lib/realpath.d.ts +3 -0
  688. package/types/lib/third-party/arborist/lib/realpath.d.ts.map +1 -0
  689. package/types/lib/third-party/arborist/lib/relpath.d.ts +3 -0
  690. package/types/lib/third-party/arborist/lib/relpath.d.ts.map +1 -0
  691. package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts +54 -0
  692. package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts.map +1 -0
  693. package/types/lib/third-party/arborist/lib/spec-from-lock.d.ts +3 -0
  694. package/types/lib/third-party/arborist/lib/spec-from-lock.d.ts.map +1 -0
  695. package/types/lib/third-party/arborist/lib/tree-check.d.ts +3 -0
  696. package/types/lib/third-party/arborist/lib/tree-check.d.ts.map +1 -0
  697. package/types/lib/third-party/arborist/lib/validate-npm-package-name.d.ts +13 -0
  698. package/types/lib/third-party/arborist/lib/validate-npm-package-name.d.ts.map +1 -0
  699. package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts +6 -0
  700. package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts.map +1 -0
  701. package/types/lib/third-party/arborist/lib/yarn-lock.d.ts +35 -0
  702. package/types/lib/third-party/arborist/lib/yarn-lock.d.ts.map +1 -0
  703. package/types/lib/validator/bomValidator.d.ts +40 -0
  704. package/types/lib/validator/bomValidator.d.ts.map +1 -0
  705. package/types/lib/validator/complianceEngine.d.ts +72 -0
  706. package/types/lib/validator/complianceEngine.d.ts.map +1 -0
  707. package/types/lib/validator/complianceRules.d.ts +61 -0
  708. package/types/lib/validator/complianceRules.d.ts.map +1 -0
  709. package/types/lib/validator/index.d.ts +101 -0
  710. package/types/lib/validator/index.d.ts.map +1 -0
  711. package/types/lib/validator/reporters/annotations.d.ts +37 -0
  712. package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
  713. package/types/lib/validator/reporters/console.d.ts +34 -0
  714. package/types/lib/validator/reporters/console.d.ts.map +1 -0
  715. package/types/lib/validator/reporters/index.d.ts +30 -0
  716. package/types/lib/validator/reporters/index.d.ts.map +1 -0
  717. package/types/lib/validator/reporters/json.d.ts +11 -0
  718. package/types/lib/validator/reporters/json.d.ts.map +1 -0
  719. package/types/lib/validator/reporters/sarif.d.ts +22 -0
  720. package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
  721. package/mod.d.ts +0 -7
  722. package/mod.js +0 -22
@@ -0,0 +1,3459 @@
1
+ import {
2
+ lstatSync,
3
+ readdirSync,
4
+ readFileSync,
5
+ realpathSync,
6
+ statSync,
7
+ } from "node:fs";
8
+ import { platform as _platform, homedir } from "node:os";
9
+ import { basename, dirname, extname, join, relative, resolve } from "node:path";
10
+
11
+ import { build, Purl } from "@cdxgen/cdx-purl";
12
+
13
+ import {
14
+ DEBUG_MODE,
15
+ hasDangerousUnicode,
16
+ isDryRun,
17
+ readEnvironmentVariable,
18
+ recordActivity,
19
+ recordSymlinkResolution,
20
+ } from "../core/activity.js";
21
+ import { extractPathEnv } from "../core/env.js";
22
+ import {
23
+ getTmpDir,
24
+ multiChecksumFile,
25
+ safeExistsSync,
26
+ safeMkdirSync,
27
+ safeMkdtempSync,
28
+ safeRmSync,
29
+ safeSpawnSync,
30
+ } from "../core/fs.js";
31
+ import { mapWithConcurrency } from "../core/parallel.js";
32
+ import { isValidDriveRoot } from "../core/paths.js";
33
+ import { isCycloneDxComponentTypeEnabled } from "../inventory/bomUtils.js";
34
+ import { createContainerRiskProperties } from "../inventory/containerRisk.js";
35
+ import {
36
+ attachIdentityTools,
37
+ extractToolRefs,
38
+ } from "../inventory/evidenceUtils.js";
39
+ import { createGtfoBinsProperties } from "../inventory/gtfobins.js";
40
+ import {
41
+ canonicalDistroQualifier,
42
+ isCleanDistroQualifier,
43
+ OS_DISTRO_ALIAS,
44
+ OS_NAMESPACE_ALIAS,
45
+ readOsRelease,
46
+ } from "../inventory/osinfo.js";
47
+ import {
48
+ collectExecutables,
49
+ collectSharedLibs,
50
+ } from "../inventory/osPackageResolver.js";
51
+ import {
52
+ resolveCdxgenPlugins,
53
+ resolvePluginBinary,
54
+ setPluginsPathEnv,
55
+ } from "../inventory/plugins.js";
56
+ import { genericPurl, tryBuildPurl } from "../inventory/purl.js";
57
+ import {
58
+ adjustLicenseInformation,
59
+ findLicenseId,
60
+ isSpdxLicenseExpression,
61
+ } from "../inventory/spdx.js";
62
+ import { getDirs } from "./containerutils.js";
63
+
64
+ const isWin = _platform() === "win32";
65
+ const OS_PURL_TYPES = ["deb", "apk", "rpm", "alpm", "qpkg"];
66
+ const pluginRuntime = setPluginsPathEnv(resolveCdxgenPlugins());
67
+ const platform = pluginRuntime.platform;
68
+ const CDXGEN_PLUGINS_DIR = pluginRuntime.pluginsDir;
69
+ const PLUGIN_MANIFEST_FILE = pluginRuntime.pluginManifestFile;
70
+ let pluginManifest;
71
+
72
+ const MAX_PLUGIN_MANIFEST_BYTES = 1024 * 1024;
73
+ const MAX_PLUGIN_MANIFEST_PLUGINS = 32;
74
+ const MAX_PLUGIN_COMPONENT_HASHES = 16;
75
+ const MAX_PLUGIN_COMPONENT_REFERENCES = 32;
76
+ const MAX_PLUGIN_COMPONENT_PROPERTIES = 128;
77
+ const MAX_PLUGIN_COMPONENT_LICENSES = 8;
78
+ const MAX_PLUGIN_STRING_LENGTH = 4096;
79
+ const MAX_PLUGIN_LONG_STRING_LENGTH = 16384;
80
+
81
+ function sanitizeManifestString(value, maxLength = MAX_PLUGIN_STRING_LENGTH) {
82
+ if (typeof value !== "string") {
83
+ return undefined;
84
+ }
85
+ const trimmedValue = value.trim();
86
+ if (!trimmedValue || trimmedValue.length > maxLength) {
87
+ return undefined;
88
+ }
89
+ return trimmedValue;
90
+ }
91
+
92
+ function sanitizeManifestObjectList(values, limit, mapper) {
93
+ if (!Array.isArray(values) || !values.length) {
94
+ return undefined;
95
+ }
96
+ const mappedValues = values
97
+ .slice(0, limit)
98
+ .map((value) => mapper(value))
99
+ .filter(Boolean);
100
+ return mappedValues.length ? mappedValues : undefined;
101
+ }
102
+
103
+ function sanitizeManifestHash(hash) {
104
+ const alg = sanitizeManifestString(hash?.alg, 64);
105
+ const content = sanitizeManifestString(hash?.content, 512);
106
+ if (!alg || !content) {
107
+ return undefined;
108
+ }
109
+ return { alg, content };
110
+ }
111
+
112
+ function sanitizeManifestProperty(property) {
113
+ const name = sanitizeManifestString(property?.name, 256);
114
+ const value = sanitizeManifestString(
115
+ property?.value,
116
+ MAX_PLUGIN_LONG_STRING_LENGTH,
117
+ );
118
+ if (!name || !value) {
119
+ return undefined;
120
+ }
121
+ return { name, value };
122
+ }
123
+
124
+ function sanitizeManifestExternalReference(reference) {
125
+ const type = sanitizeManifestString(reference?.type, 64);
126
+ const url = sanitizeManifestString(
127
+ reference?.url,
128
+ MAX_PLUGIN_LONG_STRING_LENGTH,
129
+ );
130
+ if (!type || !url) {
131
+ return undefined;
132
+ }
133
+ const sanitizedReference = { type, url };
134
+ const comment = sanitizeManifestString(reference?.comment, 512);
135
+ if (comment) {
136
+ sanitizedReference.comment = comment;
137
+ }
138
+ return sanitizedReference;
139
+ }
140
+
141
+ function sanitizeManifestLicense(licenseEntry) {
142
+ const licenseId = sanitizeManifestString(licenseEntry?.license?.id, 128);
143
+ const licenseName = sanitizeManifestString(licenseEntry?.license?.name, 256);
144
+ const licenseUrl = sanitizeManifestString(
145
+ licenseEntry?.license?.url,
146
+ MAX_PLUGIN_LONG_STRING_LENGTH,
147
+ );
148
+ if (!licenseId && !licenseName) {
149
+ return undefined;
150
+ }
151
+ const license = {};
152
+ if (licenseId) {
153
+ license.id = licenseId;
154
+ }
155
+ if (licenseName) {
156
+ license.name = licenseName;
157
+ }
158
+ if (licenseUrl) {
159
+ license.url = licenseUrl;
160
+ }
161
+ return { license };
162
+ }
163
+
164
+ function sanitizeManifestComponent(component, fallbackName) {
165
+ if (!component || typeof component !== "object") {
166
+ return undefined;
167
+ }
168
+ const sanitizedComponent = {};
169
+ const stringFields = {
170
+ group: 256,
171
+ name: 256,
172
+ version: 256,
173
+ description: MAX_PLUGIN_LONG_STRING_LENGTH,
174
+ publisher: 256,
175
+ purl: MAX_PLUGIN_LONG_STRING_LENGTH,
176
+ "bom-ref": MAX_PLUGIN_LONG_STRING_LENGTH,
177
+ type: 64,
178
+ };
179
+ for (const [field, maxLength] of Object.entries(stringFields)) {
180
+ const sanitizedValue = sanitizeManifestString(component[field], maxLength);
181
+ if (sanitizedValue) {
182
+ sanitizedComponent[field] = sanitizedValue;
183
+ }
184
+ }
185
+ if (!sanitizedComponent.name && fallbackName) {
186
+ sanitizedComponent.name = fallbackName;
187
+ }
188
+ if (!sanitizedComponent.name || !sanitizedComponent["bom-ref"]) {
189
+ return undefined;
190
+ }
191
+ const hashes = sanitizeManifestObjectList(
192
+ component.hashes,
193
+ MAX_PLUGIN_COMPONENT_HASHES,
194
+ sanitizeManifestHash,
195
+ );
196
+ const externalReferences = sanitizeManifestObjectList(
197
+ component.externalReferences,
198
+ MAX_PLUGIN_COMPONENT_REFERENCES,
199
+ sanitizeManifestExternalReference,
200
+ );
201
+ const properties = sanitizeManifestObjectList(
202
+ component.properties,
203
+ MAX_PLUGIN_COMPONENT_PROPERTIES,
204
+ sanitizeManifestProperty,
205
+ );
206
+ const licenses = sanitizeManifestObjectList(
207
+ component.licenses,
208
+ MAX_PLUGIN_COMPONENT_LICENSES,
209
+ sanitizeManifestLicense,
210
+ );
211
+ if (hashes) {
212
+ sanitizedComponent.hashes = hashes;
213
+ }
214
+ if (externalReferences) {
215
+ sanitizedComponent.externalReferences = externalReferences;
216
+ }
217
+ if (properties) {
218
+ sanitizedComponent.properties = properties;
219
+ }
220
+ if (licenses) {
221
+ sanitizedComponent.licenses = licenses;
222
+ }
223
+ return sanitizedComponent;
224
+ }
225
+
226
+ function sanitizePluginManifest(manifest) {
227
+ if (!manifest || typeof manifest !== "object") {
228
+ return null;
229
+ }
230
+ const sanitizedManifest = {
231
+ plugins: [],
232
+ };
233
+ const generatedAt = sanitizeManifestString(manifest.generatedAt, 128);
234
+ if (generatedAt) {
235
+ sanitizedManifest.generatedAt = generatedAt;
236
+ }
237
+ if (manifest.package && typeof manifest.package === "object") {
238
+ const sanitizedPackage = {};
239
+ for (const [field, maxLength] of Object.entries({
240
+ name: 256,
241
+ version: 256,
242
+ repository: MAX_PLUGIN_LONG_STRING_LENGTH,
243
+ homepage: MAX_PLUGIN_LONG_STRING_LENGTH,
244
+ })) {
245
+ const sanitizedValue = sanitizeManifestString(
246
+ manifest.package[field],
247
+ maxLength,
248
+ );
249
+ if (sanitizedValue) {
250
+ sanitizedPackage[field] = sanitizedValue;
251
+ }
252
+ }
253
+ if (Object.keys(sanitizedPackage).length) {
254
+ sanitizedManifest.package = sanitizedPackage;
255
+ }
256
+ }
257
+ for (const pluginEntry of Array.isArray(manifest.plugins)
258
+ ? manifest.plugins.slice(0, MAX_PLUGIN_MANIFEST_PLUGINS)
259
+ : []) {
260
+ const name = sanitizeManifestString(pluginEntry?.name, 128);
261
+ const component = sanitizeManifestComponent(pluginEntry?.component, name);
262
+ if (!name || !component) {
263
+ continue;
264
+ }
265
+ const sanitizedPlugin = { name, component };
266
+ for (const [field, maxLength] of Object.entries({
267
+ version: 256,
268
+ binaryPath: MAX_PLUGIN_LONG_STRING_LENGTH,
269
+ sbomFile: MAX_PLUGIN_LONG_STRING_LENGTH,
270
+ sha256: 256,
271
+ })) {
272
+ const sanitizedValue = sanitizeManifestString(
273
+ pluginEntry?.[field],
274
+ maxLength,
275
+ );
276
+ if (sanitizedValue) {
277
+ sanitizedPlugin[field] = sanitizedValue;
278
+ }
279
+ }
280
+ sanitizedManifest.plugins.push(sanitizedPlugin);
281
+ }
282
+ return sanitizedManifest.plugins.length ? sanitizedManifest : null;
283
+ }
284
+
285
+ function loadPluginManifest() {
286
+ if (pluginManifest !== undefined) {
287
+ return pluginManifest;
288
+ }
289
+ if (!PLUGIN_MANIFEST_FILE) {
290
+ pluginManifest = null;
291
+ return pluginManifest;
292
+ }
293
+ try {
294
+ const manifestRealPath = realpathSync(PLUGIN_MANIFEST_FILE);
295
+ const manifestDirectory = dirname(manifestRealPath);
296
+ const expectedManifestDirectory = realpathSync(CDXGEN_PLUGINS_DIR);
297
+ const manifestStats = statSync(manifestRealPath);
298
+ if (
299
+ basename(manifestRealPath) !== "plugins-manifest.json" ||
300
+ manifestDirectory !== expectedManifestDirectory ||
301
+ !manifestStats.isFile() ||
302
+ manifestStats.size > MAX_PLUGIN_MANIFEST_BYTES
303
+ ) {
304
+ pluginManifest = null;
305
+ return pluginManifest;
306
+ }
307
+ pluginManifest = sanitizePluginManifest(
308
+ JSON.parse(readFileSync(manifestRealPath, { encoding: "utf-8" })),
309
+ );
310
+ } catch (_err) {
311
+ pluginManifest = null;
312
+ }
313
+ return pluginManifest;
314
+ }
315
+
316
+ function cloneSerializable(value) {
317
+ if (!value || typeof value !== "object") {
318
+ return value;
319
+ }
320
+ return JSON.parse(JSON.stringify(value));
321
+ }
322
+
323
+ function getPluginManifestEntry(toolName) {
324
+ const manifest = loadPluginManifest();
325
+ if (!manifest?.plugins?.length) {
326
+ return undefined;
327
+ }
328
+ return manifest.plugins.find((entry) => entry?.name === toolName);
329
+ }
330
+
331
+ function mergeToolComponent(manifestComponent, existingComponent) {
332
+ if (!manifestComponent) {
333
+ return cloneSerializable(existingComponent);
334
+ }
335
+ if (!existingComponent) {
336
+ return cloneSerializable(manifestComponent);
337
+ }
338
+ const merged = {
339
+ ...cloneSerializable(manifestComponent),
340
+ ...cloneSerializable(existingComponent),
341
+ };
342
+ for (const key of [
343
+ "group",
344
+ "name",
345
+ "version",
346
+ "description",
347
+ "publisher",
348
+ "purl",
349
+ "bom-ref",
350
+ "type",
351
+ ]) {
352
+ if (manifestComponent?.[key]) {
353
+ merged[key] = manifestComponent[key];
354
+ }
355
+ }
356
+ merged.hashes = manifestComponent?.hashes?.length
357
+ ? manifestComponent.hashes
358
+ : existingComponent?.hashes;
359
+ merged.externalReferences = uniqueExternalReferences(
360
+ (manifestComponent?.externalReferences || []).concat(
361
+ existingComponent?.externalReferences || [],
362
+ ),
363
+ );
364
+ merged.properties = uniqueProperties(
365
+ (manifestComponent?.properties || []).concat(
366
+ existingComponent?.properties || [],
367
+ ),
368
+ );
369
+ merged.licenses =
370
+ manifestComponent?.licenses?.length || !existingComponent?.licenses?.length
371
+ ? manifestComponent?.licenses
372
+ : existingComponent.licenses;
373
+ merged.evidence = manifestComponent?.evidence || existingComponent?.evidence;
374
+ return merged;
375
+ }
376
+
377
+ function uniqueExternalReferences(references) {
378
+ const seen = new Set();
379
+ const uniqueValues = [];
380
+ for (const reference of references || []) {
381
+ if (!reference?.url || !reference?.type) {
382
+ continue;
383
+ }
384
+ const key = `${reference.type}\u0000${reference.url}`;
385
+ if (seen.has(key)) {
386
+ continue;
387
+ }
388
+ seen.add(key);
389
+ uniqueValues.push(reference);
390
+ }
391
+ return uniqueValues;
392
+ }
393
+
394
+ /**
395
+ * Look up plugin manifest entries for the given tool names and return their
396
+ * CycloneDX tool component objects, de-duplicated by bom-ref.
397
+ *
398
+ * @param {string[]} [toolNames=[]] Plugin tool names to resolve.
399
+ * @returns {Object[]} De-duplicated CycloneDX tool component objects.
400
+ */
401
+ export function getPluginToolComponents(toolNames = []) {
402
+ const components = [];
403
+ const seenRefs = new Set();
404
+ for (const toolName of uniqueSortedStrings(toolNames)) {
405
+ const component = cloneSerializable(
406
+ getPluginManifestEntry(toolName)?.component,
407
+ );
408
+ if (!component?.["bom-ref"] || seenRefs.has(component["bom-ref"])) {
409
+ continue;
410
+ }
411
+ seenRefs.add(component["bom-ref"]);
412
+ components.push(component);
413
+ }
414
+ return components;
415
+ }
416
+
417
+ function enrichToolComponents(existingTools = [], toolNames = []) {
418
+ const manifestTools = getPluginToolComponents(toolNames);
419
+ if (!existingTools?.length) {
420
+ return manifestTools;
421
+ }
422
+ const toolMap = new Map();
423
+ for (const tool of existingTools) {
424
+ if (!tool) {
425
+ continue;
426
+ }
427
+ toolMap.set(tool["bom-ref"] || tool.name || JSON.stringify(tool), tool);
428
+ }
429
+ for (const manifestTool of manifestTools) {
430
+ const matchKey = Array.from(toolMap.keys()).find((key) => {
431
+ const existing = toolMap.get(key);
432
+ return (
433
+ existing?.["bom-ref"] === manifestTool["bom-ref"] ||
434
+ existing?.name === manifestTool.name
435
+ );
436
+ });
437
+ if (matchKey) {
438
+ toolMap.set(
439
+ matchKey,
440
+ mergeToolComponent(manifestTool, toolMap.get(matchKey)),
441
+ );
442
+ continue;
443
+ }
444
+ toolMap.set(manifestTool["bom-ref"], manifestTool);
445
+ }
446
+ return Array.from(toolMap.values());
447
+ }
448
+
449
+ const TRIVY_BIN = resolvePluginBinary("trivy", pluginRuntime);
450
+ const CARGO_AUDITABLE_BIN = resolvePluginBinary(
451
+ "cargo-auditable",
452
+ pluginRuntime,
453
+ );
454
+ const OSQUERY_BIN = resolvePluginBinary("osquery", pluginRuntime);
455
+ const DOSAI_BIN = resolvePluginBinary("dosai", pluginRuntime);
456
+ const TRUSTINSPECTOR_BIN = resolvePluginBinary("trustinspector", pluginRuntime);
457
+
458
+ // Blint bin
459
+ const BLINT_BIN = readEnvironmentVariable("BLINT_CMD") || "blint";
460
+
461
+ // sourcekitten
462
+ const SOURCEKITTEN_BIN = resolvePluginBinary("sourcekitten", pluginRuntime);
463
+
464
+ // TODO: Move the lists to a config file
465
+ const COMMON_RUNTIMES = [
466
+ "java",
467
+ "node",
468
+ "nodejs",
469
+ "nodejs-current",
470
+ "deno",
471
+ "bun",
472
+ "python",
473
+ "python3",
474
+ "ruby",
475
+ "ruby3",
476
+ "php",
477
+ "php7",
478
+ "php8",
479
+ "perl",
480
+ "openjdk",
481
+ "openjdk8",
482
+ "openjdk11",
483
+ "openjdk17",
484
+ "openjdk21",
485
+ "openjdk22",
486
+ "openjdk23",
487
+ "openjdk24",
488
+ "openjdk25",
489
+ "openjdk8-jdk",
490
+ "openjdk11-jdk",
491
+ "openjdk17-jdk",
492
+ "openjdk21-jdk",
493
+ "openjdk22-jdk",
494
+ "openjdk23-jdk",
495
+ "openjdk24-jdk",
496
+ "openjdk25-jdk",
497
+ "openjdk8-jre",
498
+ "openjdk11-jre",
499
+ "openjdk17-jre",
500
+ "openjdk21-jre",
501
+ "openjdk22-jre",
502
+ "openjdk23-jre",
503
+ "openjdk24-jre",
504
+ "openjdk25-jre",
505
+ ];
506
+
507
+ /**
508
+ * Run the `cargo-auditable` companion binary against the given source path and
509
+ * return its stdout.
510
+ *
511
+ * @param {string} src Path to the Rust binary or source to inspect.
512
+ * @returns {string|undefined} The tool's stdout, or undefined when the binary
513
+ * is unavailable or produced no output.
514
+ */
515
+ export function getCargoAuditableInfo(src) {
516
+ if (CARGO_AUDITABLE_BIN) {
517
+ const result = safeSpawnSync(CARGO_AUDITABLE_BIN, [src]);
518
+ if (result.status !== 0 || result.error) {
519
+ if (result.stdout || result.stderr) {
520
+ console.error(result.stdout, result.stderr);
521
+ }
522
+ }
523
+ if (result) {
524
+ const stdout = result.stdout;
525
+ if (stdout) {
526
+ return stdout;
527
+ }
528
+ }
529
+ }
530
+ return undefined;
531
+ }
532
+
533
+ /**
534
+ * Execute sourcekitten plugin with the given arguments
535
+ *
536
+ * @param args {Array} Arguments
537
+ * @returns {undefined|Object} Command output
538
+ */
539
+ export function executeSourcekitten(args) {
540
+ if (SOURCEKITTEN_BIN) {
541
+ const result = safeSpawnSync(SOURCEKITTEN_BIN, args);
542
+ if (result.status !== 0 || result.error) {
543
+ if (result.stdout || result.stderr) {
544
+ console.error(result.stdout, result.stderr);
545
+ }
546
+ }
547
+ if (result) {
548
+ const stdout = result.stdout;
549
+ if (stdout) {
550
+ return JSON.parse(stdout);
551
+ }
552
+ }
553
+ }
554
+ return undefined;
555
+ }
556
+
557
+ /**
558
+ * Canonicalise the distro vendor namespace of an OS package component
559
+ * produced by trivy, regardless of whether trivy expressed the vendor via the
560
+ * purl namespace or the component group (it is inconsistent between
561
+ * packages). e.g. pkg:rpm/alma/... -> pkg:rpm/almalinux/... and
562
+ * pkg:rpm/cbl-mariner/... -> pkg:rpm/azure-linux/... so the purls match the
563
+ * vulnerability feeds. Also aligns the ``distro`` qualifier vendor prefix
564
+ * (alma-9.8 -> almalinux-9.8, mariner-2.0 -> azure-linux-2.0), including
565
+ * spellings that disagree with the namespace, and rebuilds ``group`` and
566
+ * ``bom-ref`` from the rewritten purl.
567
+ *
568
+ * The namespace table is OS_NAMESPACE_ALIAS from lib/inventory/osinfo.js so
569
+ * the trivy path and the os-release path (getDistroInfo) cannot drift apart.
570
+ *
571
+ * A `distro` qualifier that is not purl-safe (trivy emits Amazon Linux as
572
+ * `amazon-2023.12.20260727+(Amazon Linux)`) is replaced with the os-release
573
+ * derived value, or dropped when there is none: an unusable qualifier
574
+ * invalidates the purl and fails validation for the whole BOM.
575
+ *
576
+ * @param {Object} comp trivy OS package component (mutated in place)
577
+ * @param {Object} purlObj parsed PackageURL of comp.purl (mutated in place)
578
+ * @param {string} name component package name (basename form)
579
+ * @param {string} group component group derived from the trivy component name
580
+ * @param {string} [distroId] canonical os-release distro id, e.g. "amazon-2023"
581
+ * @returns {string} the canonicalised group
582
+ */
583
+ export function canonicaliseOsPackageNamespace(
584
+ comp,
585
+ purlObj,
586
+ name,
587
+ group,
588
+ distroId,
589
+ ) {
590
+ if (!purlObj || !OS_PURL_TYPES.includes(purlObj.type)) {
591
+ return group;
592
+ }
593
+ const os_ns = purlObj.namespace || group || "";
594
+ const os_ns_alias = OS_NAMESPACE_ALIAS[os_ns];
595
+ const distroQual = purlObj.qualifiers?.distro;
596
+ let canonicalDistroQual = distroQual;
597
+ if (os_ns_alias && distroQual?.startsWith(`${os_ns}-`)) {
598
+ canonicalDistroQual = `${os_ns_alias}-${distroQual.slice(os_ns.length + 1)}`;
599
+ }
600
+ canonicalDistroQual = canonicalDistroQualifier(canonicalDistroQual);
601
+ if (canonicalDistroQual && !isCleanDistroQualifier(canonicalDistroQual)) {
602
+ canonicalDistroQual = isCleanDistroQualifier(distroId)
603
+ ? distroId
604
+ : undefined;
605
+ }
606
+ if (!os_ns_alias && canonicalDistroQual === distroQual) {
607
+ return group;
608
+ }
609
+ group = os_ns_alias || purlObj.namespace || group || "";
610
+ comp.group = group;
611
+ purlObj.namespace = group;
612
+ if (canonicalDistroQual !== distroQual) {
613
+ if (canonicalDistroQual) {
614
+ purlObj.qualifiers.distro = canonicalDistroQual;
615
+ } else {
616
+ delete purlObj.qualifiers.distro;
617
+ }
618
+ }
619
+ comp.purl = build({
620
+ type: purlObj.type,
621
+ namespace: purlObj.namespace || null,
622
+ name: name,
623
+ version: purlObj.version || null,
624
+ qualifiers: purlObj.qualifiers || null,
625
+ subpath: purlObj.subpath || null,
626
+ });
627
+ comp["bom-ref"] = decodeURIComponent(comp.purl);
628
+ return group;
629
+ }
630
+
631
+ /**
632
+ * Repair a `distro` qualifier that cannot survive purl parsing, in place.
633
+ *
634
+ * trivy stamps Amazon Linux with the full pretty version — the qualifier
635
+ * arrives as `distro=amazon-2023.12.20260727+%28Amazon+Linux%29`, whose spaces
636
+ * and parentheses `Purl.parse` rejects outright ("Invalid character in
637
+ * qualifier distro"). The component then bypasses every canonicalisation below
638
+ * (they all need a parsed purl) and reaches the BOM verbatim, where schema
639
+ * validation fails and the *entire* SBOM is discarded — an Amazon Linux scan
640
+ * produces no output at all today.
641
+ *
642
+ * The release is already known from os-release, so the unusable value is
643
+ * swapped for it; with no usable replacement the qualifier is dropped, since a
644
+ * missing qualifier costs one matching hint while an invalid one costs the
645
+ * whole document.
646
+ *
647
+ * @param {Object} comp component whose purl may carry an unusable qualifier
648
+ * @param {string} [distroId] canonical os-release distro id, e.g. "amazon-2023"
649
+ * @returns {Object} the same component
650
+ */
651
+ export function repairOsComponentPurl(comp, distroId) {
652
+ const purl = comp?.purl;
653
+ if (!purl?.includes("distro=")) {
654
+ return comp;
655
+ }
656
+ const match = /([?&]distro=)([^&]*)/.exec(purl);
657
+ if (!match) {
658
+ return comp;
659
+ }
660
+ let decoded = match[2];
661
+ try {
662
+ decoded = decodeURIComponent(match[2].replace(/\+/g, " "));
663
+ } catch (_err) {
664
+ // A value that cannot even be decoded is definitely not purl-safe
665
+ }
666
+ if (isCleanDistroQualifier(decoded)) {
667
+ return comp;
668
+ }
669
+ const replacement = isCleanDistroQualifier(distroId)
670
+ ? `${match[1]}${distroId}`
671
+ : "";
672
+ comp.purl = purl.replace(match[0], replacement).replace(/\?&/, "?");
673
+ if (comp.purl.endsWith("?")) {
674
+ comp.purl = comp.purl.slice(0, -1);
675
+ }
676
+ comp["bom-ref"] = decodeURIComponent(comp.purl);
677
+ return comp;
678
+ }
679
+
680
+ /**
681
+ * Drop source-package shadow components that duplicate a real installed
682
+ * package, in place.
683
+ *
684
+ * A shadow is emitted whenever an rpm/deb declares a source name different
685
+ * from its own (bzip2-libs -> bzip2) so that scanners keying on source names
686
+ * still match. When the source package is *itself* installed at the same
687
+ * version, the shadow's purl is identical to the real component's and the two
688
+ * collapse in trimComponents() — which keeps whichever was seen first and
689
+ * never merges `tags`. Seen-first is trivy's listing order, so the outcome is
690
+ * arbitrary: when the shadow wins, a genuinely installed package is reported
691
+ * with `tags: ["source"]` and identity confidence 0, the very marker the
692
+ * shadow carries so consumers can filter shadows out. That turns a real
693
+ * package into a filtered-away one.
694
+ *
695
+ * Dropping the redundant shadow is loss-free: it duplicates the real
696
+ * component's purl (so nothing that matches on purl changes) and its bom-ref
697
+ * is byte-identical, so no dependency reference can dangle.
698
+ *
699
+ * @param {Array} pkgList component list, mutated in place
700
+ * @returns {Array} the same list, for convenience
701
+ */
702
+ export function dropRedundantSourceComponents(pkgList) {
703
+ if (!Array.isArray(pkgList) || !pkgList.length) {
704
+ return pkgList;
705
+ }
706
+ const installedPurls = new Set();
707
+ for (const comp of pkgList) {
708
+ if (comp?.purl && !comp?.tags?.includes("source")) {
709
+ installedPurls.add(comp.purl);
710
+ }
711
+ }
712
+ if (!installedPurls.size) {
713
+ return pkgList;
714
+ }
715
+ for (let i = pkgList.length - 1; i >= 0; i--) {
716
+ const comp = pkgList[i];
717
+ if (comp?.tags?.includes("source") && installedPurls.has(comp.purl)) {
718
+ pkgList.splice(i, 1);
719
+ }
720
+ }
721
+ return pkgList;
722
+ }
723
+
724
+ /**
725
+ * Get the packages installed in the container image filesystem.
726
+ *
727
+ * @param src {String} Source directory containing the extracted filesystem.
728
+ * @param imageConfig {Object} Image configuration containing environment variables, command, entrypoints etc
729
+ * @param options CLI options controlling inventory generation
730
+ *
731
+ * @returns {Object} Metadata containing packages, dependencies, etc
732
+ */
733
+ export async function getOSPackages(src, imageConfig, options = {}) {
734
+ if (isDryRun) {
735
+ recordActivity({
736
+ kind: "container",
737
+ reason:
738
+ "Dry run mode blocks Trivy-based OS package generation because it executes external tools and writes temporary output.",
739
+ status: "blocked",
740
+ target: src,
741
+ });
742
+ return {
743
+ allTypes: new Set(),
744
+ binPaths: [],
745
+ bundledRuntimes: new Set(),
746
+ bundledSdks: new Set(),
747
+ dependenciesList: [],
748
+ executables: [],
749
+ osPackages: [],
750
+ osPackageFiles: [],
751
+ sharedLibs: [],
752
+ services: [],
753
+ tools: [],
754
+ };
755
+ }
756
+ const pkgList = [];
757
+ const osPackageEntries = [];
758
+ const dependenciesList = [];
759
+ const allTypes = new Set();
760
+ const bundledSdks = new Set();
761
+ const bundledRuntimes = new Set();
762
+ let osPackageFiles = [];
763
+ let services = [];
764
+ let tools = [];
765
+ let binPaths = extractPathEnv(imageConfig?.Env);
766
+ if (!binPaths?.length) {
767
+ const rootBinPaths = getDirs(src, "{sbin,bin}", true, false);
768
+ const usrBinPaths = getDirs(
769
+ src,
770
+ "/{app,opt,usr,home}/**/{sbin,bin}",
771
+ true,
772
+ true,
773
+ );
774
+ binPaths = Array.from(
775
+ new Set(
776
+ rootBinPaths
777
+ .concat(usrBinPaths)
778
+ .map((f) => relative(src, f))
779
+ .filter(Boolean),
780
+ ),
781
+ ).sort();
782
+ if (DEBUG_MODE && binPaths.length) {
783
+ console.log(
784
+ `Falling back to inferred binary paths for ${src}: ${binPaths.join(", ")}`,
785
+ );
786
+ }
787
+ }
788
+ if (TRIVY_BIN) {
789
+ let imageType = "image";
790
+ const trivyCacheDir = join(homedir(), ".cache", "trivy");
791
+ try {
792
+ safeMkdirSync(join(trivyCacheDir, "db"), { recursive: true });
793
+ safeMkdirSync(join(trivyCacheDir, "java-db"), { recursive: true });
794
+ } catch (_err) {
795
+ // ignore errors
796
+ }
797
+ if (safeExistsSync(src)) {
798
+ imageType = "rootfs";
799
+ }
800
+ const tempDir = safeMkdtempSync(join(getTmpDir(), "trivy-cdxgen-"));
801
+ const bomJsonFile = join(tempDir, "trivy-bom.json");
802
+ const args = [
803
+ imageType,
804
+ "--cache-dir",
805
+ trivyCacheDir,
806
+ "--output",
807
+ bomJsonFile,
808
+ ];
809
+ if (DEBUG_MODE) {
810
+ args.push("--debug");
811
+ }
812
+ args.push(src);
813
+ if (DEBUG_MODE) {
814
+ console.log("Executing", TRIVY_BIN, args.join(" "));
815
+ }
816
+ const result = safeSpawnSync(TRIVY_BIN, args);
817
+ if (result.status !== 0 || result.error) {
818
+ if (result.stdout || result.stderr) {
819
+ console.error(result.stdout, result.stderr);
820
+ }
821
+ }
822
+ if (safeExistsSync(bomJsonFile)) {
823
+ let tmpBom = {};
824
+ try {
825
+ tmpBom = JSON.parse(
826
+ readFileSync(bomJsonFile, {
827
+ encoding: "utf-8",
828
+ }),
829
+ );
830
+ } catch (_e) {
831
+ // ignore errors
832
+ }
833
+ // Clean up
834
+ if (tempDir?.startsWith(getTmpDir())) {
835
+ if (DEBUG_MODE) {
836
+ console.log(`Cleaning up ${tempDir}`);
837
+ }
838
+ safeRmSync(tempDir, { recursive: true, force: true });
839
+ }
840
+ const osReleaseData = readOsRelease(src);
841
+ if (DEBUG_MODE) {
842
+ console.log(osReleaseData);
843
+ }
844
+ // distro_name must be a real release codename/token (bookworm, jammy,
845
+ // rhel-9, alpine-3.19), never a distro *display name*. VERSION_CODENAME is
846
+ // authoritative when present; otherwise the RHEL/CentOS product-name
847
+ // fallbacks are only accepted when they resolve through OS_DISTRO_ALIAS
848
+ // (e.g. "red hat enterprise linux 9" -> "rhel-9"). Display names such as
849
+ // "Rocky Linux" or "AlmaLinux" have no alias entry and are dropped so we
850
+ // never emit a bogus distro_name qualifier (RPM distros track packages
851
+ // flat, without a release segment, so folding a display name breaks
852
+ // downstream vuln-db matching).
853
+ let distro_codename = (
854
+ osReleaseData["VERSION_CODENAME"] || ""
855
+ ).toLowerCase();
856
+ if (!distro_codename) {
857
+ const distro_display_name = (
858
+ osReleaseData["CENTOS_MANTISBT_PROJECT"] ||
859
+ osReleaseData["REDHAT_BUGZILLA_PRODUCT"] ||
860
+ osReleaseData["REDHAT_SUPPORT_PRODUCT"] ||
861
+ ""
862
+ ).toLowerCase();
863
+ if (distro_display_name && OS_DISTRO_ALIAS[distro_display_name]) {
864
+ distro_codename = OS_DISTRO_ALIAS[distro_display_name];
865
+ }
866
+ }
867
+ let distro_id = osReleaseData["ID"] || "";
868
+ const distro_id_like = osReleaseData["ID_LIKE"] || "";
869
+ let purl_type = "rpm";
870
+ switch (distro_id) {
871
+ case "debian":
872
+ case "ubuntu":
873
+ case "pop":
874
+ purl_type = "deb";
875
+ break;
876
+ case "sles":
877
+ case "suse":
878
+ case "opensuse":
879
+ purl_type = "rpm";
880
+ break;
881
+ case "alpine":
882
+ purl_type = "apk";
883
+ if (osReleaseData.VERSION_ID) {
884
+ const versionParts = osReleaseData["VERSION_ID"].split(".");
885
+ if (versionParts.length >= 2) {
886
+ distro_codename = `alpine-${versionParts[0]}.${versionParts[1]}`;
887
+ }
888
+ }
889
+ break;
890
+ default:
891
+ if (distro_id_like.includes("debian")) {
892
+ purl_type = "deb";
893
+ } else if (
894
+ distro_id_like.includes("rhel") ||
895
+ distro_id_like.includes("centos") ||
896
+ distro_id_like.includes("fedora")
897
+ ) {
898
+ purl_type = "rpm";
899
+ }
900
+ break;
901
+ }
902
+ if (osReleaseData["VERSION_ID"]) {
903
+ distro_id = `${distro_id}-${osReleaseData["VERSION_ID"]}`;
904
+ if (OS_DISTRO_ALIAS[distro_id]) {
905
+ distro_codename = OS_DISTRO_ALIAS[distro_id];
906
+ }
907
+ }
908
+ // Canonicalise the distro qualifier vendor (e.g. "mariner-2.0" ->
909
+ // "azure-linux-2.0") so every spelling of one release yields a single
910
+ // qualifier value. This runs before distro_id is used to overwrite
911
+ // trivy's own qualifier below.
912
+ distro_id = canonicalDistroQualifier(distro_id);
913
+ const tmpDependencies = {};
914
+ tools = enrichToolComponents(
915
+ (Array.isArray(tmpBom?.metadata?.tools)
916
+ ? tmpBom.metadata.tools
917
+ : tmpBom?.metadata?.tools?.components || []
918
+ ).filter((tool) => tool?.["bom-ref"] && tool?.name !== "cdxgen"),
919
+ ["trivy"].concat(imageType === "rootfs" ? ["trustinspector"] : []),
920
+ );
921
+ const toolRefs = extractToolRefs(
922
+ { components: tools },
923
+ (tool) => tool?.name !== "cdxgen",
924
+ );
925
+ (tmpBom.dependencies || []).forEach((d) => {
926
+ tmpDependencies[d.ref] = d.dependsOn;
927
+ });
928
+ if (tmpBom?.components) {
929
+ for (const comp of tmpBom.components) {
930
+ if (comp.purl) {
931
+ const origBomRef = comp["bom-ref"];
932
+ // Fix the group
933
+ let group = dirname(comp.name);
934
+ const name = basename(comp.name);
935
+ let purlObj;
936
+ if (group === ".") {
937
+ group = "";
938
+ }
939
+ comp.group = group;
940
+ comp.name = name;
941
+ const modularityLabel = extractModularityLabel(comp);
942
+ repairOsComponentPurl(comp, distro_id);
943
+ try {
944
+ purlObj = Purl.parse(comp.purl);
945
+ purlObj.qualifiers = purlObj.qualifiers || {};
946
+ } catch (_err) {
947
+ // continue regardless of error
948
+ }
949
+ // Canonicalise the distro vendor namespace (see
950
+ // canonicaliseOsPackageNamespace) before any later bom-ref
951
+ // rebuild so the rewritten purl, group and bom-ref stay in sync.
952
+ group = canonicaliseOsPackageNamespace(
953
+ comp,
954
+ purlObj,
955
+ name,
956
+ group,
957
+ distro_id,
958
+ );
959
+ if (
960
+ group === "" &&
961
+ purlObj &&
962
+ OS_PURL_TYPES.includes(purlObj["type"])
963
+ ) {
964
+ try {
965
+ if (purlObj?.namespace && purlObj.namespace !== "") {
966
+ group =
967
+ OS_NAMESPACE_ALIAS[purlObj.namespace] || purlObj.namespace;
968
+ comp.group = group;
969
+ purlObj.namespace = group;
970
+ }
971
+ if (distro_id?.length) {
972
+ purlObj.qualifiers["distro"] = distro_id;
973
+ }
974
+ if (distro_codename?.length) {
975
+ purlObj.qualifiers["distro_name"] = distro_codename;
976
+ }
977
+ // Bug fix for mageia and oracle linux
978
+ // Type is being returned as none for ubuntu as well!
979
+ if (purlObj?.type === "none") {
980
+ purlObj["type"] = purl_type;
981
+ purlObj["namespace"] = "";
982
+ comp.group = "";
983
+ if (comp.purl?.includes(".mga")) {
984
+ purlObj["namespace"] = "mageia";
985
+ comp.group = "mageia";
986
+ purlObj.qualifiers["distro"] = "mageia";
987
+ distro_codename = "mga";
988
+ }
989
+ comp.purl = build({
990
+ type: purlObj.type,
991
+ namespace: purlObj.namespace || null,
992
+ name: name,
993
+ version: purlObj.version || null,
994
+ qualifiers: purlObj.qualifiers || null,
995
+ subpath: purlObj.subpath || null,
996
+ });
997
+ comp["bom-ref"] = decodeURIComponent(comp.purl);
998
+ }
999
+ if (purlObj?.type !== "none") {
1000
+ allTypes.add(purlObj.type);
1001
+ }
1002
+ // Prefix distro codename for ubuntu
1003
+ if (purlObj?.qualifiers?.distro) {
1004
+ allTypes.add(purlObj.qualifiers.distro);
1005
+ if (OS_DISTRO_ALIAS[purlObj.qualifiers.distro]) {
1006
+ distro_codename =
1007
+ OS_DISTRO_ALIAS[purlObj.qualifiers.distro];
1008
+ } else if (group === "alpine") {
1009
+ const dtmpA = purlObj.qualifiers.distro.split(".");
1010
+ if (dtmpA && dtmpA.length > 2) {
1011
+ distro_codename = `${dtmpA[0]}.${dtmpA[1]}`;
1012
+ }
1013
+ } else if (group === "photon") {
1014
+ const dtmpA = purlObj.qualifiers.distro.split("-");
1015
+ if (dtmpA && dtmpA.length > 1) {
1016
+ distro_codename = dtmpA[0];
1017
+ }
1018
+ } else if (group === "redhat") {
1019
+ const dtmpA = purlObj.qualifiers.distro.split(".");
1020
+ if (dtmpA && dtmpA.length > 1) {
1021
+ distro_codename = dtmpA[0].replace(
1022
+ "redhat",
1023
+ "enterprise_linux",
1024
+ );
1025
+ }
1026
+ }
1027
+ }
1028
+ if (distro_codename !== "") {
1029
+ allTypes.add(distro_codename);
1030
+ allTypes.add(purlObj.namespace);
1031
+ comp.purl = build({
1032
+ type: purlObj.type,
1033
+ namespace: purlObj.namespace || null,
1034
+ name: name,
1035
+ version: purlObj.version || null,
1036
+ qualifiers: purlObj.qualifiers || null,
1037
+ subpath: purlObj.subpath || null,
1038
+ });
1039
+ comp["bom-ref"] = decodeURIComponent(comp.purl);
1040
+ }
1041
+ } catch (_err) {
1042
+ // continue regardless of error
1043
+ }
1044
+ }
1045
+ if (comp.purl.includes("epoch=")) {
1046
+ try {
1047
+ const epoch = purlObj.qualifiers?.epoch;
1048
+ // trivy seems to be removing the epoch from the version and moving it to a qualifier
1049
+ // let's fix this hack to improve confidence.
1050
+ if (epoch) {
1051
+ purlObj.version = `${epoch}:${purlObj.version}`;
1052
+ comp.version = purlObj.version;
1053
+ }
1054
+ comp.evidence = {
1055
+ identity: [
1056
+ {
1057
+ field: "purl",
1058
+ confidence: 1,
1059
+ methods: [
1060
+ {
1061
+ technique: "other",
1062
+ confidence: 1,
1063
+ value: comp.purl,
1064
+ },
1065
+ ],
1066
+ },
1067
+ ],
1068
+ };
1069
+ if (distro_id?.length) {
1070
+ purlObj.qualifiers["distro"] = distro_id;
1071
+ }
1072
+ if (distro_codename?.length) {
1073
+ purlObj.qualifiers["distro_name"] = distro_codename;
1074
+ }
1075
+ allTypes.add(purlObj.namespace);
1076
+ comp.purl = build({
1077
+ type: purlObj.type,
1078
+ namespace: purlObj.namespace || null,
1079
+ name: name,
1080
+ version: purlObj.version || null,
1081
+ qualifiers: purlObj.qualifiers || null,
1082
+ subpath: purlObj.subpath || null,
1083
+ });
1084
+ comp["bom-ref"] = decodeURIComponent(comp.purl);
1085
+ } catch (err) {
1086
+ // continue regardless of error
1087
+ console.log(err);
1088
+ }
1089
+ }
1090
+ attachIdentityTools(comp, toolRefs);
1091
+ // Fix licenses
1092
+ if (
1093
+ comp.licenses &&
1094
+ Array.isArray(comp.licenses) &&
1095
+ comp.licenses.length
1096
+ ) {
1097
+ const newLicenses = [];
1098
+ for (const aLic of comp.licenses) {
1099
+ if (aLic?.license?.name) {
1100
+ if (isSpdxLicenseExpression(aLic.license.name)) {
1101
+ newLicenses.push({ expression: aLic.license.name });
1102
+ } else {
1103
+ const possibleId = findLicenseId(aLic.license.name);
1104
+ if (possibleId !== aLic.license.name) {
1105
+ newLicenses.push({ license: { id: possibleId } });
1106
+ } else {
1107
+ newLicenses.push({
1108
+ license: { name: aLic.license.name },
1109
+ });
1110
+ }
1111
+ }
1112
+ } else if (
1113
+ aLic?.license &&
1114
+ Object.keys(aLic).length &&
1115
+ Object.keys(aLic.license).length
1116
+ ) {
1117
+ newLicenses.push(aLic);
1118
+ }
1119
+ }
1120
+ comp.licenses = adjustLicenseInformation(newLicenses);
1121
+ }
1122
+ // Fix hashes
1123
+ if (
1124
+ comp.hashes &&
1125
+ Array.isArray(comp.hashes) &&
1126
+ comp.hashes.length
1127
+ ) {
1128
+ const hashContent = comp.hashes[0].content;
1129
+ if (!hashContent || hashContent.length < 32) {
1130
+ delete comp.hashes;
1131
+ }
1132
+ }
1133
+ const compProperties = comp.properties;
1134
+ const trivyMetadata = extractTrivyOsPackageMetadata(compProperties);
1135
+ const fallbackIdentityProperties = promoteTrivyOsPackageIdentity(
1136
+ comp,
1137
+ trivyMetadata,
1138
+ );
1139
+ let { srcName, srcVersion, srcRelease, epoch } = trivyMetadata;
1140
+ // See issue #2067
1141
+ if (srcVersion && srcRelease) {
1142
+ srcVersion = `${srcVersion}-${srcRelease}`;
1143
+ }
1144
+ if (epoch) {
1145
+ srcVersion = `${epoch}:${srcVersion}`;
1146
+ }
1147
+ const labelProperties = modularityLabel
1148
+ ? [{ name: "cdx:os:modularityLabel", value: modularityLabel }]
1149
+ : [];
1150
+ if (
1151
+ trivyMetadata.retainedProperties.length ||
1152
+ fallbackIdentityProperties.length ||
1153
+ labelProperties.length
1154
+ ) {
1155
+ comp.properties = uniqueProperties(
1156
+ trivyMetadata.retainedProperties
1157
+ .concat(fallbackIdentityProperties)
1158
+ .concat(labelProperties),
1159
+ );
1160
+ } else {
1161
+ delete comp.properties;
1162
+ }
1163
+ // A golang purl carries the module path in its namespace, and the
1164
+ // Go standard library has no module path — trivy reports it as the
1165
+ // bare name `stdlib`, which is not a valid purl. The component is
1166
+ // worth keeping, since the toolchain version is a real signal, so
1167
+ // the purl is dropped rather than given an invented namespace.
1168
+ if (
1169
+ comp.purl?.startsWith("pkg:golang/") &&
1170
+ !tryBuildPurl({
1171
+ type: "golang",
1172
+ namespace: comp.group || null,
1173
+ name: comp.name,
1174
+ version: comp.version || null,
1175
+ })
1176
+ ) {
1177
+ comp["bom-ref"] = decodeURIComponent(comp.purl);
1178
+ delete comp.purl;
1179
+ }
1180
+ // Bug fix: We can get bom-ref like this: pkg:rpm/sles/libstdc%2B%2B6@14.2.0+git10526-150000.1.6.1?arch=x86_64&distro=sles-15.5
1181
+ if (
1182
+ comp["bom-ref"] &&
1183
+ comp.purl &&
1184
+ comp["bom-ref"] !== decodeURIComponent(comp.purl)
1185
+ ) {
1186
+ comp["bom-ref"] = decodeURIComponent(comp.purl);
1187
+ }
1188
+ pkgList.push(comp);
1189
+ if (trivyMetadata.installedFiles.length) {
1190
+ osPackageEntries.push({
1191
+ capabilities: trivyMetadata.capabilities,
1192
+ commandPaths: trivyMetadata.installedCommandPaths,
1193
+ commands: trivyMetadata.installedCommands,
1194
+ files: trivyMetadata.installedFiles,
1195
+ packageName: comp.name,
1196
+ packageRef: comp["bom-ref"],
1197
+ packageVersion: comp.version,
1198
+ });
1199
+ }
1200
+ detectSdksRuntimes(comp, bundledSdks, bundledRuntimes);
1201
+ const compDeps = retrieveDependencies(
1202
+ tmpDependencies,
1203
+ origBomRef,
1204
+ comp,
1205
+ );
1206
+ if (compDeps) {
1207
+ dependenciesList.push(compDeps);
1208
+ }
1209
+ // HACK: Many vulnerability databases, including vdb, track vulnerabilities based on source package names :(
1210
+ // If there is a source package defined we include it as well to make such SCA scanners work.
1211
+ // As a compromise, we reduce the confidence to zero so that there is a way to filter these out.
1212
+ if (srcName && srcVersion && srcName !== comp.name) {
1213
+ const newComp = Object.assign({}, comp);
1214
+ newComp.name = srcName;
1215
+ newComp.version = srcVersion;
1216
+ newComp.tags = ["source"];
1217
+ newComp.evidence = {
1218
+ identity: [
1219
+ {
1220
+ field: "purl",
1221
+ confidence: 0,
1222
+ methods: [
1223
+ {
1224
+ technique: "filename",
1225
+ confidence: 0,
1226
+ value: comp.name,
1227
+ },
1228
+ ],
1229
+ },
1230
+ ],
1231
+ };
1232
+ // Track upstream and source versions as qualifiers
1233
+ if (purlObj) {
1234
+ const newCompQualifiers = {
1235
+ ...purlObj.qualifiers,
1236
+ };
1237
+ delete newCompQualifiers.epoch;
1238
+ if (epoch) {
1239
+ newCompQualifiers.epoch = epoch;
1240
+ }
1241
+ newComp.purl = build({
1242
+ type: purlObj.type,
1243
+ namespace: purlObj.namespace || null,
1244
+ name: srcName,
1245
+ version: srcVersion || null,
1246
+ qualifiers: newCompQualifiers || null,
1247
+ subpath: purlObj.subpath || null,
1248
+ });
1249
+ }
1250
+ newComp["bom-ref"] = decodeURIComponent(newComp.purl);
1251
+ delete newComp.properties;
1252
+ attachIdentityTools(newComp, toolRefs);
1253
+ pkgList.push(newComp);
1254
+ detectSdksRuntimes(newComp, bundledSdks, bundledRuntimes);
1255
+ }
1256
+ }
1257
+ }
1258
+ }
1259
+ }
1260
+ }
1261
+ dropRedundantSourceComponents(pkgList);
1262
+ const rootfsRepositoryInventory = await collectRootfsRepositoryInventory(
1263
+ src,
1264
+ options,
1265
+ );
1266
+ if (rootfsRepositoryInventory.components.length) {
1267
+ pkgList.push(...rootfsRepositoryInventory.components);
1268
+ }
1269
+ if (rootfsRepositoryInventory.dependenciesList.length) {
1270
+ dependenciesList.push(...rootfsRepositoryInventory.dependenciesList);
1271
+ }
1272
+ const {
1273
+ components: ownedFileComponents,
1274
+ dependenciesList: ownedFileDependencies,
1275
+ ownedFilePaths,
1276
+ services: ownedServices,
1277
+ } = await createOSPackageFileComponents(src, osPackageEntries);
1278
+ if (ownedFileComponents.length) {
1279
+ osPackageFiles = ownedFileComponents;
1280
+ }
1281
+ if (ownedFileDependencies.length) {
1282
+ dependenciesList.push(...ownedFileDependencies);
1283
+ }
1284
+ if (ownedServices.length) {
1285
+ services = ownedServices;
1286
+ }
1287
+ let executables = [];
1288
+ if (binPaths?.length) {
1289
+ executables = await fileComponents(
1290
+ src,
1291
+ collectExecutables(src, binPaths, ownedFilePaths),
1292
+ "executable",
1293
+ );
1294
+ }
1295
+ // Directories containing shared libraries
1296
+ const defaultLibPaths = [
1297
+ "/lib",
1298
+ "/lib64",
1299
+ "/usr/lib",
1300
+ "/usr/lib64",
1301
+ "/usr/local/lib64",
1302
+ "/usr/local/lib",
1303
+ "/lib/x86_64-linux-gnu",
1304
+ "/usr/lib/x86_64-linux-gnu",
1305
+ "/lib/i386-linux-gnu",
1306
+ "/usr/lib/i386-linux-gnu",
1307
+ "/lib/arm-linux-gnueabihf",
1308
+ "/usr/lib/arm-linux-gnueabihf",
1309
+ "/opt/**/lib",
1310
+ "/root/**/lib",
1311
+ ];
1312
+ const sharedLibs = await fileComponents(
1313
+ src,
1314
+ collectSharedLibs(
1315
+ src,
1316
+ defaultLibPaths,
1317
+ "/etc/ld.so.conf",
1318
+ "/etc/ld.so.conf.d/*.conf",
1319
+ ownedFilePaths,
1320
+ ),
1321
+ "shared_library",
1322
+ );
1323
+ return {
1324
+ osPackages: pkgList,
1325
+ osPackageFiles,
1326
+ dependenciesList,
1327
+ allTypes: Array.from(allTypes).sort(),
1328
+ bundledSdks: Array.from(bundledSdks).sort(),
1329
+ bundledRuntimes: Array.from(bundledRuntimes).sort(),
1330
+ binPaths,
1331
+ executables,
1332
+ sharedLibs,
1333
+ services,
1334
+ tools,
1335
+ };
1336
+ }
1337
+
1338
+ function extractTrivyOsPackageMetadata(compProperties) {
1339
+ const metadata = {
1340
+ capabilities: [],
1341
+ installedCommandPaths: [],
1342
+ installedCommands: [],
1343
+ installedFiles: [],
1344
+ packageMaintainer: undefined,
1345
+ packageVendor: undefined,
1346
+ retainedProperties: [],
1347
+ srcName: undefined,
1348
+ srcRelease: undefined,
1349
+ srcVersion: undefined,
1350
+ epoch: undefined,
1351
+ };
1352
+ if (!Array.isArray(compProperties) || !compProperties.length) {
1353
+ return metadata;
1354
+ }
1355
+ for (const aprop of compProperties) {
1356
+ if (!aprop?.name) {
1357
+ continue;
1358
+ }
1359
+ if (aprop.name.endsWith("SrcName")) {
1360
+ metadata.srcName = aprop.value;
1361
+ continue;
1362
+ }
1363
+ if (aprop.name.endsWith("SrcVersion")) {
1364
+ metadata.srcVersion = aprop.value;
1365
+ continue;
1366
+ }
1367
+ if (aprop.name.endsWith("SrcRelease")) {
1368
+ metadata.srcRelease = aprop.value;
1369
+ continue;
1370
+ }
1371
+ if (aprop.name.endsWith("SrcEpoch")) {
1372
+ metadata.epoch = aprop.value;
1373
+ continue;
1374
+ }
1375
+ if (aprop.name.endsWith("PackageMaintainer")) {
1376
+ metadata.packageMaintainer = aprop.value;
1377
+ continue;
1378
+ }
1379
+ if (aprop.name.endsWith("PackageVendor")) {
1380
+ metadata.packageVendor = aprop.value;
1381
+ continue;
1382
+ }
1383
+ if (aprop.name.endsWith("InstalledFile")) {
1384
+ metadata.installedFiles.push(aprop.value);
1385
+ continue;
1386
+ }
1387
+ if (aprop.name.endsWith("InstalledCommandPath")) {
1388
+ metadata.installedCommandPaths.push(aprop.value);
1389
+ metadata.retainedProperties.push(aprop);
1390
+ continue;
1391
+ }
1392
+ if (aprop.name.endsWith("InstalledCommand")) {
1393
+ metadata.installedCommands.push(aprop.value);
1394
+ metadata.retainedProperties.push(aprop);
1395
+ continue;
1396
+ }
1397
+ if (aprop.name.endsWith("Capability")) {
1398
+ metadata.capabilities.push(aprop.value);
1399
+ metadata.retainedProperties.push(aprop);
1400
+ continue;
1401
+ }
1402
+ if (
1403
+ aprop.name.endsWith("CapabilityCount") ||
1404
+ aprop.name.endsWith("InstalledFileCount") ||
1405
+ aprop.name.endsWith("InstalledCommandCount")
1406
+ ) {
1407
+ metadata.retainedProperties.push(aprop);
1408
+ continue;
1409
+ }
1410
+ metadata.retainedProperties.push(aprop);
1411
+ }
1412
+ metadata.capabilities = uniqueSortedStrings(metadata.capabilities);
1413
+ metadata.installedCommandPaths = uniqueSortedStrings(
1414
+ metadata.installedCommandPaths,
1415
+ );
1416
+ metadata.installedCommands = uniqueSortedStrings(metadata.installedCommands);
1417
+ metadata.installedFiles = uniqueSortedStrings(metadata.installedFiles);
1418
+ metadata.retainedProperties = uniqueProperties(metadata.retainedProperties);
1419
+ return metadata;
1420
+ }
1421
+
1422
+ function getOrganizationalEntityName(entity) {
1423
+ if (!entity) {
1424
+ return undefined;
1425
+ }
1426
+ if (typeof entity === "string") {
1427
+ return entity.trim() || undefined;
1428
+ }
1429
+ if (typeof entity === "object" && typeof entity.name === "string") {
1430
+ return entity.name.trim() || undefined;
1431
+ }
1432
+ return undefined;
1433
+ }
1434
+
1435
+ function sameOrganizationalEntity(entity, expectedName) {
1436
+ const currentName = getOrganizationalEntityName(entity);
1437
+ return Boolean(
1438
+ currentName &&
1439
+ expectedName &&
1440
+ currentName.localeCompare(expectedName, undefined, {
1441
+ sensitivity: "accent",
1442
+ }) === 0,
1443
+ );
1444
+ }
1445
+
1446
+ function mergeOrganizationalEntityField(component, fieldName, entityName) {
1447
+ const normalizedName = `${entityName || ""}`.trim();
1448
+ if (!normalizedName) {
1449
+ return { applied: false, represented: false };
1450
+ }
1451
+ if (!component?.[fieldName]) {
1452
+ component[fieldName] = { name: normalizedName };
1453
+ return { applied: true, represented: true };
1454
+ }
1455
+ if (sameOrganizationalEntity(component[fieldName], normalizedName)) {
1456
+ return { applied: false, represented: true };
1457
+ }
1458
+ return { applied: false, represented: false };
1459
+ }
1460
+
1461
+ function parseOrganizationalContact(value) {
1462
+ const normalizedValue = `${value || ""}`.trim();
1463
+ if (!normalizedValue) {
1464
+ return undefined;
1465
+ }
1466
+ const match = normalizedValue.match(/^([^<>]+?)\s*<([^<>\s]+@[^<>\s]+)>$/);
1467
+ if (match) {
1468
+ return {
1469
+ name: match[1].trim(),
1470
+ email: match[2].trim(),
1471
+ };
1472
+ }
1473
+ return { name: normalizedValue };
1474
+ }
1475
+
1476
+ function sameOrganizationalContact(left, right) {
1477
+ if (!left || !right) {
1478
+ return false;
1479
+ }
1480
+ const leftName = `${left.name || ""}`.trim();
1481
+ const rightName = `${right.name || ""}`.trim();
1482
+ const leftEmail = `${left.email || ""}`.trim().toLowerCase();
1483
+ const rightEmail = `${right.email || ""}`.trim().toLowerCase();
1484
+ return leftName === rightName && leftEmail === rightEmail;
1485
+ }
1486
+
1487
+ function mergeAuthorsFromMaintainer(component, maintainerValue) {
1488
+ const authorContact = parseOrganizationalContact(maintainerValue);
1489
+ if (!authorContact?.name) {
1490
+ return { applied: false, represented: false };
1491
+ }
1492
+ if (!Array.isArray(component?.authors) || !component.authors.length) {
1493
+ component.authors = [authorContact];
1494
+ return { applied: true, represented: true };
1495
+ }
1496
+ if (
1497
+ component.authors.some((author) =>
1498
+ sameOrganizationalContact(author, authorContact),
1499
+ )
1500
+ ) {
1501
+ return { applied: false, represented: true };
1502
+ }
1503
+ return { applied: false, represented: false };
1504
+ }
1505
+
1506
+ function promoteTrivyOsPackageIdentity(component, trivyMetadata) {
1507
+ const fallbackProperties = [];
1508
+ const vendorValue = `${trivyMetadata?.packageVendor || ""}`.trim();
1509
+ const supplierName = getOrganizationalEntityName(component?.supplier);
1510
+ const maintainerValue = `${
1511
+ trivyMetadata?.packageMaintainer || supplierName || ""
1512
+ }`.trim();
1513
+
1514
+ const maintainerAuthorResult = mergeAuthorsFromMaintainer(
1515
+ component,
1516
+ maintainerValue,
1517
+ );
1518
+ const maintainerSupplierResult = mergeOrganizationalEntityField(
1519
+ component,
1520
+ "supplier",
1521
+ maintainerValue,
1522
+ );
1523
+ if (
1524
+ trivyMetadata?.packageMaintainer &&
1525
+ !maintainerAuthorResult.represented &&
1526
+ !maintainerSupplierResult.represented
1527
+ ) {
1528
+ fallbackProperties.push({
1529
+ name: "internal:PackageMaintainer",
1530
+ value: trivyMetadata.packageMaintainer,
1531
+ });
1532
+ }
1533
+
1534
+ const vendorSupplierResult = mergeOrganizationalEntityField(
1535
+ component,
1536
+ "supplier",
1537
+ vendorValue,
1538
+ );
1539
+ const vendorManufacturerResult = mergeOrganizationalEntityField(
1540
+ component,
1541
+ "manufacturer",
1542
+ vendorValue,
1543
+ );
1544
+ if (
1545
+ vendorValue &&
1546
+ !vendorSupplierResult.represented &&
1547
+ !vendorManufacturerResult.represented
1548
+ ) {
1549
+ fallbackProperties.push({
1550
+ name: "internal:PackageVendor",
1551
+ value: vendorValue,
1552
+ });
1553
+ }
1554
+ return fallbackProperties;
1555
+ }
1556
+
1557
+ async function collectRootfsRepositoryInventory(basePath, options = {}) {
1558
+ let trustedKeyComponents = [];
1559
+ let refsByPath = new Map();
1560
+ if (isCycloneDxComponentTypeEnabled("cryptographic-asset", options)) {
1561
+ ({ components: trustedKeyComponents, refsByPath } =
1562
+ await collectTrustedKeyComponents(basePath));
1563
+ trustedKeyComponents = applyTrustMaterialEnhancements(
1564
+ trustedKeyComponents,
1565
+ collectTrustInspectorRootfsInventory(basePath),
1566
+ );
1567
+ }
1568
+ refsByPath = new Map(
1569
+ trustedKeyComponents
1570
+ .map((component) => {
1571
+ const srcFile = (component.properties || []).find(
1572
+ (property) => property.name === "internal:SrcFile",
1573
+ )?.value;
1574
+ return srcFile
1575
+ ? [normalizeContainerPath(srcFile), component["bom-ref"]]
1576
+ : undefined;
1577
+ })
1578
+ .filter(Boolean),
1579
+ );
1580
+ const repositoryEntries = uniqueRepositoryEntries(
1581
+ parseAptRepositorySources(basePath).concat(
1582
+ parseYumRepositorySources(basePath),
1583
+ ),
1584
+ );
1585
+ const components = [...trustedKeyComponents];
1586
+ const dependenciesList = [];
1587
+ const seenComponentRefs = new Set(components.map((comp) => comp["bom-ref"]));
1588
+ for (const entry of repositoryEntries) {
1589
+ const component = createRepositorySourceComponent(entry);
1590
+ if (seenComponentRefs.has(component["bom-ref"])) {
1591
+ continue;
1592
+ }
1593
+ seenComponentRefs.add(component["bom-ref"]);
1594
+ components.push(component);
1595
+ const dependsOn = uniqueSortedStrings(
1596
+ (entry.keyReferences || [])
1597
+ .map((keyRef) => normalizeLocalRepositoryReference(keyRef))
1598
+ .map((keyRef) => refsByPath.get(keyRef))
1599
+ .filter(Boolean),
1600
+ );
1601
+ if (dependsOn.length) {
1602
+ dependenciesList.push({
1603
+ ref: component["bom-ref"],
1604
+ dependsOn,
1605
+ });
1606
+ }
1607
+ }
1608
+ return { components, dependenciesList };
1609
+ }
1610
+
1611
+ async function collectTrustedKeyComponents(basePath) {
1612
+ const refsByPath = new Map();
1613
+ const components = [];
1614
+ for (const normalizedPath of collectTrustedKeyPaths(basePath)) {
1615
+ const component = await createTrustedKeyComponent(basePath, normalizedPath);
1616
+ if (!component?.["bom-ref"]) {
1617
+ continue;
1618
+ }
1619
+ refsByPath.set(normalizedPath, component["bom-ref"]);
1620
+ components.push(component);
1621
+ }
1622
+ return { components, refsByPath };
1623
+ }
1624
+
1625
+ function collectTrustedKeyPaths(basePath) {
1626
+ const results = new Set();
1627
+ for (const candidate of [
1628
+ "/etc/apt/trusted.gpg",
1629
+ "/etc/apt/trusted.gpg.d",
1630
+ "/usr/share/keyrings",
1631
+ "/etc/pki/rpm-gpg",
1632
+ "/usr/share/distribution-gpg-keys",
1633
+ "/etc/apk/keys",
1634
+ ]) {
1635
+ const normalizedCandidate = normalizeContainerPath(candidate);
1636
+ const absoluteCandidate = join(
1637
+ basePath,
1638
+ normalizedCandidate.replace(/^\/+/, ""),
1639
+ );
1640
+ if (!safeExistsSync(absoluteCandidate)) {
1641
+ continue;
1642
+ }
1643
+ const stats = statSync(absoluteCandidate, { throwIfNoEntry: false });
1644
+ if (!stats) {
1645
+ continue;
1646
+ }
1647
+ if (stats.isDirectory()) {
1648
+ for (const filePath of walkRootfsFiles(basePath, normalizedCandidate)) {
1649
+ if (isTrustedKeyPath(filePath)) {
1650
+ results.add(filePath);
1651
+ }
1652
+ }
1653
+ continue;
1654
+ }
1655
+ if (isTrustedKeyPath(normalizedCandidate)) {
1656
+ results.add(normalizedCandidate);
1657
+ }
1658
+ }
1659
+ return Array.from(results).sort();
1660
+ }
1661
+
1662
+ function walkRootfsFiles(basePath, normalizedDir) {
1663
+ const results = [];
1664
+ const absoluteDir = join(basePath, normalizedDir.replace(/^\/+/, ""));
1665
+ if (!safeExistsSync(absoluteDir)) {
1666
+ return results;
1667
+ }
1668
+ let entries;
1669
+ try {
1670
+ entries = readdirSync(absoluteDir, { withFileTypes: true });
1671
+ } catch (_e) {
1672
+ return results;
1673
+ }
1674
+ for (const entry of entries) {
1675
+ const normalizedPath = normalizeContainerPath(
1676
+ `${normalizedDir.replace(/\/+$/, "")}/${entry.name}`,
1677
+ );
1678
+ if (entry.isDirectory()) {
1679
+ results.push(...walkRootfsFiles(basePath, normalizedPath));
1680
+ continue;
1681
+ }
1682
+ if (entry.isFile()) {
1683
+ results.push(normalizedPath);
1684
+ }
1685
+ }
1686
+ return results;
1687
+ }
1688
+
1689
+ function isTrustedKeyPath(normalizedPath) {
1690
+ const lowerPath = normalizeContainerPath(normalizedPath)?.toLowerCase();
1691
+ if (!lowerPath) {
1692
+ return false;
1693
+ }
1694
+ return (
1695
+ lowerPath === "/etc/apt/trusted.gpg" ||
1696
+ lowerPath.includes("/trusted.gpg.d/") ||
1697
+ lowerPath.includes("/keyrings/") ||
1698
+ lowerPath.includes("/rpm-gpg/") ||
1699
+ lowerPath.includes("/distribution-gpg-keys/") ||
1700
+ lowerPath.includes("/apk/keys/")
1701
+ );
1702
+ }
1703
+
1704
+ async function createTrustedKeyComponent(basePath, normalizedPath) {
1705
+ const absolutePath = join(basePath, normalizedPath.replace(/^\/+/, ""));
1706
+ const stats = statSync(absolutePath, { throwIfNoEntry: false });
1707
+ if (!stats || stats.isDirectory()) {
1708
+ return undefined;
1709
+ }
1710
+ let hashValues = {};
1711
+ try {
1712
+ hashValues = await multiChecksumFile(["sha1", "sha256"], absolutePath);
1713
+ } catch (_err) {
1714
+ // ignore
1715
+ }
1716
+ const version = hashValues.sha256 || hashValues.sha1 || `${stats.mtimeMs}`;
1717
+ const hashes = [];
1718
+ if (hashValues.sha1) {
1719
+ hashes.push({ alg: "SHA-1", content: hashValues.sha1 });
1720
+ }
1721
+ if (hashValues.sha256) {
1722
+ hashes.push({ alg: "SHA-256", content: hashValues.sha256 });
1723
+ }
1724
+ return {
1725
+ "bom-ref": `crypto/related-crypto-material/public-key/${encodeURIComponent(normalizedPath)}@${hashValues.sha256 ? `sha256:${hashValues.sha256}` : version}`,
1726
+ name: basename(normalizedPath),
1727
+ type: "cryptographic-asset",
1728
+ version,
1729
+ hashes,
1730
+ cryptoProperties: {
1731
+ assetType: "related-crypto-material",
1732
+ relatedCryptoMaterialProperties: {
1733
+ type: "public-key",
1734
+ id: hashValues.sha256 || hashValues.sha1 || normalizedPath,
1735
+ state: "active",
1736
+ },
1737
+ },
1738
+ properties: uniqueProperties([
1739
+ { name: "internal:SrcFile", value: normalizedPath },
1740
+ {
1741
+ name: "cdx:crypto:trustDomain",
1742
+ value: deriveTrustedKeyDomain(normalizedPath),
1743
+ },
1744
+ { name: "cdx:crypto:keyPath", value: normalizedPath },
1745
+ {
1746
+ name: "cdx:crypto:fileExtension",
1747
+ value: extname(normalizedPath).replace(/^\./, "") || "gpg",
1748
+ },
1749
+ ]),
1750
+ };
1751
+ }
1752
+
1753
+ function deriveTrustedKeyDomain(normalizedPath) {
1754
+ const lowerPath = normalizeContainerPath(normalizedPath)?.toLowerCase() || "";
1755
+ if (lowerPath.includes("/apt/") || lowerPath.includes("/keyrings/")) {
1756
+ return "apt";
1757
+ }
1758
+ if (
1759
+ lowerPath.includes("/rpm-gpg/") ||
1760
+ lowerPath.includes("/distribution-gpg-keys/")
1761
+ ) {
1762
+ return "rpm";
1763
+ }
1764
+ if (lowerPath.includes("/apk/keys/")) {
1765
+ return "apk";
1766
+ }
1767
+ return "generic";
1768
+ }
1769
+
1770
+ function trustInspectorToolRefs() {
1771
+ return extractToolRefs({
1772
+ components: getPluginToolComponents(["trustinspector"]),
1773
+ });
1774
+ }
1775
+
1776
+ function executeTrustInspector(args, activity) {
1777
+ if (isDryRun) {
1778
+ recordActivity({
1779
+ kind: "trustinspector",
1780
+ reason:
1781
+ "Dry run mode blocks trustinspector execution and reports the requested inspection instead.",
1782
+ status: "blocked",
1783
+ ...activity,
1784
+ });
1785
+ return undefined;
1786
+ }
1787
+ if (!TRUSTINSPECTOR_BIN) {
1788
+ return undefined;
1789
+ }
1790
+ if (DEBUG_MODE) {
1791
+ console.log("Executing", TRUSTINSPECTOR_BIN, args.join(" "));
1792
+ }
1793
+ const result = safeSpawnSync(TRUSTINSPECTOR_BIN, args);
1794
+ if (result?.status !== 0 || result?.error) {
1795
+ if (DEBUG_MODE && (result?.stdout || result?.stderr)) {
1796
+ console.error(result.stdout, result.stderr);
1797
+ }
1798
+ return undefined;
1799
+ }
1800
+ if (!result?.stdout) {
1801
+ return undefined;
1802
+ }
1803
+ try {
1804
+ return JSON.parse(result.stdout);
1805
+ } catch (_err) {
1806
+ return undefined;
1807
+ }
1808
+ }
1809
+
1810
+ function normalizeTrustInspectorTargetPath(basePath) {
1811
+ if (typeof basePath !== "string") {
1812
+ return undefined;
1813
+ }
1814
+ const trimmedPath = basePath.trim();
1815
+ if (
1816
+ !trimmedPath ||
1817
+ hasDangerousUnicode(trimmedPath) ||
1818
+ /[\r\n]/.test(trimmedPath)
1819
+ ) {
1820
+ return undefined;
1821
+ }
1822
+ const resolvedPath = resolve(trimmedPath);
1823
+ if (
1824
+ !resolvedPath ||
1825
+ hasDangerousUnicode(resolvedPath) ||
1826
+ /[\r\n]/.test(resolvedPath)
1827
+ ) {
1828
+ return undefined;
1829
+ }
1830
+ if (
1831
+ (isWin &&
1832
+ !(
1833
+ resolvedPath.startsWith("\\\\") ||
1834
+ isValidDriveRoot(resolvedPath.slice(0, 3))
1835
+ )) ||
1836
+ (!isWin && !resolvedPath.startsWith("/")) ||
1837
+ !safeExistsSync(resolvedPath)
1838
+ ) {
1839
+ return undefined;
1840
+ }
1841
+ const targetStats = statSync(resolvedPath, { throwIfNoEntry: false });
1842
+ if (!targetStats?.isDirectory()) {
1843
+ return undefined;
1844
+ }
1845
+ let canonicalPath;
1846
+ try {
1847
+ canonicalPath = realpathSync(resolvedPath);
1848
+ } catch (_err) {
1849
+ return undefined;
1850
+ }
1851
+ if (
1852
+ !canonicalPath ||
1853
+ hasDangerousUnicode(canonicalPath) ||
1854
+ /[\r\n]/.test(canonicalPath)
1855
+ ) {
1856
+ return undefined;
1857
+ }
1858
+ return resolvedPath;
1859
+ }
1860
+
1861
+ function trustMaterialHashes(material) {
1862
+ const hashes = [];
1863
+ if (material?.sha1) {
1864
+ hashes.push({ alg: "SHA-1", content: material.sha1 });
1865
+ }
1866
+ if (material?.sha256) {
1867
+ hashes.push({ alg: "SHA-256", content: material.sha256 });
1868
+ }
1869
+ return hashes;
1870
+ }
1871
+
1872
+ function trustMaterialState(material) {
1873
+ const expiresAt = material?.expiresAt
1874
+ ? Date.parse(material.expiresAt)
1875
+ : Number.NaN;
1876
+ if (!Number.isNaN(expiresAt) && expiresAt < Date.now()) {
1877
+ return "expired";
1878
+ }
1879
+ return "active";
1880
+ }
1881
+
1882
+ function createTrustMaterialComponent(material) {
1883
+ const normalizedPath = normalizeContainerPath(material?.path);
1884
+ if (!normalizedPath || !material?.kind) {
1885
+ return undefined;
1886
+ }
1887
+ const hashes = trustMaterialHashes(material);
1888
+ const sharedProperties = uniqueProperties([
1889
+ { name: "internal:SrcFile", value: normalizedPath },
1890
+ ...(material?.properties || []),
1891
+ ...(material?.fingerprint
1892
+ ? [{ name: "cdx:crypto:fingerprint", value: material.fingerprint }]
1893
+ : []),
1894
+ ...(material?.algorithm
1895
+ ? [{ name: "cdx:crypto:algorithm", value: material.algorithm }]
1896
+ : []),
1897
+ ...(material?.keyStrength
1898
+ ? [{ name: "cdx:crypto:keyStrength", value: `${material.keyStrength}` }]
1899
+ : []),
1900
+ ...(material?.createdAt
1901
+ ? [{ name: "cdx:crypto:createdAt", value: material.createdAt }]
1902
+ : []),
1903
+ ...(material?.expiresAt
1904
+ ? [{ name: "cdx:crypto:expiresAt", value: material.expiresAt }]
1905
+ : []),
1906
+ ]);
1907
+ let component;
1908
+ if (material.kind === "certificate") {
1909
+ component = {
1910
+ "bom-ref": `crypto/certificate/${encodeURIComponent(material.name || normalizedPath)}@${material.sha256 ? `sha256:${material.sha256}` : material.serial || material.expiresAt || "unknown"}`,
1911
+ name: material.name || basename(normalizedPath),
1912
+ type: "cryptographic-asset",
1913
+ version:
1914
+ material.sha256 ||
1915
+ material.serial ||
1916
+ material.expiresAt ||
1917
+ "configured",
1918
+ hashes,
1919
+ description: material.subject || normalizedPath,
1920
+ cryptoProperties: {
1921
+ assetType: "certificate",
1922
+ algorithmProperties: {
1923
+ executionEnvironment: "unknown",
1924
+ implementationPlatform: "unknown",
1925
+ },
1926
+ certificateProperties: {
1927
+ serialNumber: material.serial || undefined,
1928
+ subjectName: material.subject || undefined,
1929
+ issuerName: material.issuer || undefined,
1930
+ notValidBefore: material.createdAt || undefined,
1931
+ notValidAfter: material.expiresAt || undefined,
1932
+ certificateFormat: material.format || "X.509",
1933
+ certificateFileExtension: material.fileExtension || undefined,
1934
+ fingerprint: material.fingerprint
1935
+ ? { alg: "SHA-256", content: material.fingerprint }
1936
+ : undefined,
1937
+ },
1938
+ },
1939
+ properties: uniqueProperties(
1940
+ sharedProperties.concat(
1941
+ material.trustDomain
1942
+ ? [{ name: "cdx:crypto:trustDomain", value: material.trustDomain }]
1943
+ : [],
1944
+ ),
1945
+ ),
1946
+ };
1947
+ } else {
1948
+ component = {
1949
+ "bom-ref": `crypto/related-crypto-material/public-key/${encodeURIComponent(normalizedPath)}@${material.sha256 ? `sha256:${material.sha256}` : material.keyId || "unknown"}`,
1950
+ name: material.name || basename(normalizedPath),
1951
+ type: "cryptographic-asset",
1952
+ version: material.sha256 || material.keyId || normalizedPath,
1953
+ hashes,
1954
+ cryptoProperties: {
1955
+ assetType: "related-crypto-material",
1956
+ relatedCryptoMaterialProperties: {
1957
+ type: "public-key",
1958
+ id:
1959
+ material.keyId ||
1960
+ material.fingerprint ||
1961
+ material.sha256 ||
1962
+ normalizedPath,
1963
+ state: trustMaterialState(material),
1964
+ },
1965
+ },
1966
+ properties: uniqueProperties(
1967
+ sharedProperties.concat([
1968
+ {
1969
+ name: "cdx:crypto:trustDomain",
1970
+ value:
1971
+ material.trustDomain || deriveTrustedKeyDomain(normalizedPath),
1972
+ },
1973
+ { name: "cdx:crypto:keyPath", value: normalizedPath },
1974
+ {
1975
+ name: "cdx:crypto:fileExtension",
1976
+ value:
1977
+ material.fileExtension ||
1978
+ extname(normalizedPath).replace(/^\./, "") ||
1979
+ "gpg",
1980
+ },
1981
+ ...(material?.keyId
1982
+ ? [{ name: "cdx:crypto:keyId", value: material.keyId }]
1983
+ : []),
1984
+ ...(material?.userIds || []).map((value) => ({
1985
+ name: "cdx:crypto:userId",
1986
+ value,
1987
+ })),
1988
+ ]),
1989
+ ),
1990
+ };
1991
+ }
1992
+ attachIdentityTools(component, trustInspectorToolRefs());
1993
+ return component;
1994
+ }
1995
+
1996
+ function enhanceComponentFromTrustMaterial(component, material) {
1997
+ if (!component || !material) {
1998
+ return component;
1999
+ }
2000
+ component.hashes = component.hashes?.length
2001
+ ? component.hashes
2002
+ : trustMaterialHashes(material);
2003
+ component.properties = uniqueProperties(
2004
+ (component.properties || []).concat(
2005
+ createTrustMaterialComponent(material)?.properties || [],
2006
+ ),
2007
+ );
2008
+ if (!component.cryptoProperties) {
2009
+ component.cryptoProperties =
2010
+ createTrustMaterialComponent(material)?.cryptoProperties;
2011
+ }
2012
+ attachIdentityTools(component, trustInspectorToolRefs());
2013
+ return component;
2014
+ }
2015
+
2016
+ function applyTrustMaterialEnhancements(components, materials) {
2017
+ if (!materials?.length) {
2018
+ return components || [];
2019
+ }
2020
+ const componentList = [...(components || [])];
2021
+ const bySrcFile = new Map();
2022
+ const seenRefs = new Set();
2023
+ for (const component of componentList) {
2024
+ if (component?.["bom-ref"]) {
2025
+ seenRefs.add(component["bom-ref"]);
2026
+ }
2027
+ const srcFile = (component?.properties || []).find(
2028
+ (property) => property.name === "internal:SrcFile",
2029
+ )?.value;
2030
+ if (srcFile) {
2031
+ bySrcFile.set(normalizeContainerPath(srcFile), component);
2032
+ }
2033
+ }
2034
+ for (const material of materials) {
2035
+ const srcFile = normalizeContainerPath(material?.path);
2036
+ const existing = bySrcFile.get(srcFile);
2037
+ if (
2038
+ existing &&
2039
+ existing.type === "cryptographic-asset" &&
2040
+ material?.kind === "public-key"
2041
+ ) {
2042
+ enhanceComponentFromTrustMaterial(existing, material);
2043
+ continue;
2044
+ }
2045
+ const component = createTrustMaterialComponent(material);
2046
+ if (!component?.["bom-ref"] || seenRefs.has(component["bom-ref"])) {
2047
+ continue;
2048
+ }
2049
+ seenRefs.add(component["bom-ref"]);
2050
+ componentList.push(component);
2051
+ if (srcFile) {
2052
+ bySrcFile.set(srcFile, component);
2053
+ }
2054
+ }
2055
+ return componentList;
2056
+ }
2057
+
2058
+ function collectTrustInspectorRootfsInventory(basePath) {
2059
+ const targetPath = normalizeTrustInspectorTargetPath(basePath);
2060
+ if (!targetPath) {
2061
+ return [];
2062
+ }
2063
+ const trustData = executeTrustInspector(["rootfs", targetPath], {
2064
+ target: targetPath,
2065
+ });
2066
+ return trustData?.materials || [];
2067
+ }
2068
+
2069
+ function parseAptRepositorySources(basePath) {
2070
+ const sourceFiles = [
2071
+ "/etc/apt/sources.list",
2072
+ ...walkRootfsFiles(basePath, "/etc/apt/sources.list.d").filter(
2073
+ (filePath) => filePath.endsWith(".list") || filePath.endsWith(".sources"),
2074
+ ),
2075
+ ].filter((filePath, index, values) => values.indexOf(filePath) === index);
2076
+ const entries = [];
2077
+ for (const sourceFile of sourceFiles) {
2078
+ const data = readRootfsTextFile(basePath, sourceFile);
2079
+ if (!data) {
2080
+ continue;
2081
+ }
2082
+ if (sourceFile.endsWith(".sources")) {
2083
+ entries.push(...parseDeb822AptRepositorySources(data, sourceFile));
2084
+ continue;
2085
+ }
2086
+ entries.push(...parseLegacyAptRepositorySources(data, sourceFile));
2087
+ }
2088
+ return entries;
2089
+ }
2090
+
2091
+ function parseLegacyAptRepositorySources(data, sourceFile) {
2092
+ const entries = [];
2093
+ for (const rawLine of data.split(/\r?\n/)) {
2094
+ const line = rawLine.split("#")[0].trim();
2095
+ if (!line || (!line.startsWith("deb ") && !line.startsWith("deb-src "))) {
2096
+ continue;
2097
+ }
2098
+ const match = line.match(
2099
+ /^(deb(?:-src)?)\s+(?:\[(?<options>[^\]]+)\]\s+)?(?<uri>\S+)\s+(?<suite>\S+)(?:\s+(?<components>.+))?$/,
2100
+ );
2101
+ if (!match?.groups?.uri) {
2102
+ continue;
2103
+ }
2104
+ const options = parseRepositoryOptionString(match.groups.options);
2105
+ entries.push({
2106
+ name: deriveRepositoryDisplayName(match.groups.uri, sourceFile),
2107
+ path: sourceFile,
2108
+ repoType: isPpaRepository(match.groups.uri) ? "ppa-source" : "apt-source",
2109
+ release: match.groups.suite,
2110
+ url: match.groups.uri,
2111
+ description: line,
2112
+ enabled: true,
2113
+ keyReferences: extractRepositoryKeyReferences(options["signed-by"]),
2114
+ properties: uniqueProperties([
2115
+ { name: "internal:SrcFile", value: sourceFile },
2116
+ { name: "cdx:os:repo:kind", value: match[1] },
2117
+ { name: "cdx:os:repo:url", value: match.groups.uri },
2118
+ { name: "cdx:os:repo:release", value: match.groups.suite },
2119
+ ...(match.groups.components
2120
+ ? [
2121
+ {
2122
+ name: "cdx:os:repo:components",
2123
+ value: match.groups.components,
2124
+ },
2125
+ ]
2126
+ : []),
2127
+ ...(options.arch
2128
+ ? [{ name: "cdx:os:repo:architectures", value: options.arch }]
2129
+ : []),
2130
+ ...(options["signed-by"]
2131
+ ? [{ name: "cdx:os:repo:signedBy", value: options["signed-by"] }]
2132
+ : []),
2133
+ ]),
2134
+ });
2135
+ }
2136
+ return entries;
2137
+ }
2138
+
2139
+ function parseDeb822AptRepositorySources(data, sourceFile) {
2140
+ const entries = [];
2141
+ for (const stanza of data.split(/\n\s*\n/)) {
2142
+ const fields = parseDeb822Fields(stanza);
2143
+ const uris = splitRepositoryField(fields.uris);
2144
+ const suites = splitRepositoryField(fields.suites || fields.suite);
2145
+ if (!uris.length) {
2146
+ continue;
2147
+ }
2148
+ for (const uri of uris) {
2149
+ entries.push({
2150
+ name: deriveRepositoryDisplayName(uri, sourceFile),
2151
+ path: sourceFile,
2152
+ repoType: isPpaRepository(uri) ? "ppa-source" : "apt-source",
2153
+ release: suites.join(",") || "configured",
2154
+ url: uri,
2155
+ description: stanza.trim(),
2156
+ enabled: !["no", "false", "0"].includes(
2157
+ `${fields.enabled || "yes"}`.toLowerCase(),
2158
+ ),
2159
+ keyReferences: extractRepositoryKeyReferences(fields["signed-by"]),
2160
+ properties: uniqueProperties([
2161
+ { name: "internal:SrcFile", value: sourceFile },
2162
+ { name: "cdx:os:repo:kind", value: fields.types || "deb" },
2163
+ { name: "cdx:os:repo:url", value: uri },
2164
+ ...(suites.length
2165
+ ? [{ name: "cdx:os:repo:release", value: suites.join(",") }]
2166
+ : []),
2167
+ ...(fields.components
2168
+ ? [{ name: "cdx:os:repo:components", value: fields.components }]
2169
+ : []),
2170
+ ...(fields.architectures
2171
+ ? [
2172
+ {
2173
+ name: "cdx:os:repo:architectures",
2174
+ value: fields.architectures,
2175
+ },
2176
+ ]
2177
+ : []),
2178
+ ...(fields["signed-by"]
2179
+ ? [
2180
+ {
2181
+ name: "cdx:os:repo:signedBy",
2182
+ value: fields["signed-by"],
2183
+ },
2184
+ ]
2185
+ : []),
2186
+ ]),
2187
+ });
2188
+ }
2189
+ }
2190
+ return entries;
2191
+ }
2192
+
2193
+ function parseYumRepositorySources(basePath) {
2194
+ const entries = [];
2195
+ for (const repoFile of walkRootfsFiles(basePath, "/etc/yum.repos.d").filter(
2196
+ (f) => f.endsWith(".repo"),
2197
+ )) {
2198
+ const data = readRootfsTextFile(basePath, repoFile);
2199
+ if (!data) {
2200
+ continue;
2201
+ }
2202
+ let currentSection;
2203
+ let currentConfig = {};
2204
+ const flushCurrentSection = () => {
2205
+ if (!currentSection) {
2206
+ return;
2207
+ }
2208
+ const url =
2209
+ currentConfig.baseurl ||
2210
+ currentConfig.mirrorlist ||
2211
+ currentConfig.metalink;
2212
+ if (!url) {
2213
+ return;
2214
+ }
2215
+ entries.push({
2216
+ name: currentSection,
2217
+ path: repoFile,
2218
+ repoType: "yum-source",
2219
+ release:
2220
+ `${currentConfig.enabled || "1"}` === "1" ? "enabled" : "disabled",
2221
+ url,
2222
+ description: `${repoFile}#${currentSection}`,
2223
+ enabled: `${currentConfig.enabled || "1"}` === "1",
2224
+ keyReferences: extractRepositoryKeyReferences(currentConfig.gpgkey),
2225
+ properties: uniqueProperties([
2226
+ { name: "internal:SrcFile", value: repoFile },
2227
+ { name: "cdx:os:repo:url", value: url },
2228
+ ...(currentConfig.baseurl
2229
+ ? [{ name: "cdx:os:repo:baseurl", value: currentConfig.baseurl }]
2230
+ : []),
2231
+ ...(currentConfig.mirrorlist
2232
+ ? [
2233
+ {
2234
+ name: "cdx:os:repo:mirrorlist",
2235
+ value: currentConfig.mirrorlist,
2236
+ },
2237
+ ]
2238
+ : []),
2239
+ ...(currentConfig.metalink
2240
+ ? [{ name: "cdx:os:repo:metalink", value: currentConfig.metalink }]
2241
+ : []),
2242
+ {
2243
+ name: "cdx:os:repo:enabled",
2244
+ value: `${currentConfig.enabled || "1"}`,
2245
+ },
2246
+ ...(currentConfig.gpgcheck
2247
+ ? [{ name: "cdx:os:repo:gpgcheck", value: currentConfig.gpgcheck }]
2248
+ : []),
2249
+ ...(currentConfig.gpgkey
2250
+ ? [{ name: "cdx:os:repo:gpgkey", value: currentConfig.gpgkey }]
2251
+ : []),
2252
+ ]),
2253
+ });
2254
+ };
2255
+ for (const rawLine of data.split(/\r?\n/)) {
2256
+ const line = rawLine.trim();
2257
+ if (!line || line.startsWith("#") || line.startsWith(";")) {
2258
+ continue;
2259
+ }
2260
+ if (line.startsWith("[") && line.endsWith("]")) {
2261
+ flushCurrentSection();
2262
+ currentSection = line.slice(1, -1).trim();
2263
+ currentConfig = {};
2264
+ continue;
2265
+ }
2266
+ const equalsIndex = line.indexOf("=");
2267
+ if (equalsIndex === -1) {
2268
+ continue;
2269
+ }
2270
+ currentConfig[line.slice(0, equalsIndex).trim().toLowerCase()] = line
2271
+ .slice(equalsIndex + 1)
2272
+ .trim();
2273
+ }
2274
+ flushCurrentSection();
2275
+ }
2276
+ return entries;
2277
+ }
2278
+
2279
+ function createRepositorySourceComponent(entry) {
2280
+ const version = entry.release || "configured";
2281
+ const purl = build({
2282
+ type: "generic",
2283
+ namespace: "os-repository",
2284
+ name: entry.name,
2285
+ version: version || null,
2286
+ });
2287
+ return {
2288
+ "bom-ref": decodeURIComponent(purl),
2289
+ purl,
2290
+ name: entry.name,
2291
+ type: "data",
2292
+ version,
2293
+ description: entry.description || entry.url,
2294
+ properties: uniqueProperties(
2295
+ [
2296
+ { name: "internal:SrcFile", value: entry.path },
2297
+ { name: "cdx:os:repo:type", value: entry.repoType },
2298
+ { name: "cdx:os:repo:url", value: entry.url },
2299
+ {
2300
+ name: "cdx:os:repo:enabled",
2301
+ value: entry.enabled === false ? "false" : "true",
2302
+ },
2303
+ ].concat(entry.properties || []),
2304
+ ),
2305
+ };
2306
+ }
2307
+
2308
+ function uniqueRepositoryEntries(entries) {
2309
+ const seen = new Set();
2310
+ const results = [];
2311
+ for (const entry of entries || []) {
2312
+ if (!entry?.name || !entry?.path || !entry?.url) {
2313
+ continue;
2314
+ }
2315
+ const key = `${entry.repoType}\u0000${entry.path}\u0000${entry.url}\u0000${entry.release || ""}`;
2316
+ if (seen.has(key)) {
2317
+ continue;
2318
+ }
2319
+ seen.add(key);
2320
+ results.push(entry);
2321
+ }
2322
+ return results;
2323
+ }
2324
+
2325
+ function deriveRepositoryDisplayName(url, sourceFile) {
2326
+ try {
2327
+ const parsedUrl = new URL(url);
2328
+ const repoPath = parsedUrl.pathname.replace(/\/+$/, "") || "/";
2329
+ return `${parsedUrl.hostname}${repoPath}`;
2330
+ } catch (_err) {
2331
+ return basename(sourceFile);
2332
+ }
2333
+ }
2334
+
2335
+ function isPpaRepository(url) {
2336
+ try {
2337
+ const hostname = new URL(`${url || ""}`).hostname.toLowerCase();
2338
+ return (
2339
+ hostname === "ppa.launchpadcontent.net" ||
2340
+ hostname === "ppa.launchpad.net"
2341
+ );
2342
+ } catch (_err) {
2343
+ return false;
2344
+ }
2345
+ }
2346
+
2347
+ function parseRepositoryOptionString(optionString) {
2348
+ const options = {};
2349
+ for (const token of `${optionString || ""}`.split(/\s+/).filter(Boolean)) {
2350
+ const [key, ...valueParts] = token.split("=");
2351
+ if (!key || !valueParts.length) {
2352
+ continue;
2353
+ }
2354
+ options[key.toLowerCase()] = valueParts.join("=");
2355
+ }
2356
+ return options;
2357
+ }
2358
+
2359
+ function parseDeb822Fields(stanza) {
2360
+ const fields = {};
2361
+ let currentKey;
2362
+ for (const rawLine of stanza.split(/\r?\n/)) {
2363
+ if (!rawLine.trim()) {
2364
+ continue;
2365
+ }
2366
+ if (/^[ \t]/.test(rawLine) && currentKey) {
2367
+ fields[currentKey] = `${fields[currentKey]} ${rawLine.trim()}`.trim();
2368
+ continue;
2369
+ }
2370
+ const separatorIndex = rawLine.indexOf(":");
2371
+ if (separatorIndex === -1) {
2372
+ continue;
2373
+ }
2374
+ currentKey = rawLine.slice(0, separatorIndex).trim().toLowerCase();
2375
+ fields[currentKey] = rawLine.slice(separatorIndex + 1).trim();
2376
+ }
2377
+ return fields;
2378
+ }
2379
+
2380
+ function splitRepositoryField(value) {
2381
+ return `${value || ""}`.split(/\s+/).filter(Boolean);
2382
+ }
2383
+
2384
+ function extractRepositoryKeyReferences(value) {
2385
+ return uniqueSortedStrings(
2386
+ `${value || ""}`
2387
+ .split(/[\s,]+/)
2388
+ .map((part) => normalizeLocalRepositoryReference(part))
2389
+ .filter(Boolean),
2390
+ );
2391
+ }
2392
+
2393
+ function normalizeLocalRepositoryReference(value) {
2394
+ if (!value) {
2395
+ return undefined;
2396
+ }
2397
+ const trimmedValue = `${value}`.trim();
2398
+ if (!trimmedValue || trimmedValue.includes("BEGIN PGP PUBLIC KEY BLOCK")) {
2399
+ return undefined;
2400
+ }
2401
+ if (trimmedValue.startsWith("file://")) {
2402
+ return normalizeContainerPath(trimmedValue.slice("file://".length));
2403
+ }
2404
+ if (trimmedValue.startsWith("/")) {
2405
+ return normalizeContainerPath(trimmedValue);
2406
+ }
2407
+ return undefined;
2408
+ }
2409
+
2410
+ function readRootfsTextFile(basePath, normalizedPath) {
2411
+ const absolutePath = join(basePath, normalizedPath.replace(/^\/+/, ""));
2412
+ if (!safeExistsSync(absolutePath)) {
2413
+ return undefined;
2414
+ }
2415
+ try {
2416
+ return readFileSync(absolutePath, "utf-8");
2417
+ } catch (_err) {
2418
+ return undefined;
2419
+ }
2420
+ }
2421
+
2422
+ async function createOSPackageFileComponents(basePath, osPackageEntries) {
2423
+ const components = [];
2424
+ const dependenciesList = [];
2425
+ const ownedFilePaths = new Set();
2426
+ const services = [];
2427
+ const componentByPath = new Map();
2428
+ for (const packageEntry of osPackageEntries) {
2429
+ const commandPathSet = new Set(packageEntry.commandPaths || []);
2430
+ const providedRefs = new Set();
2431
+ for (const filePath of packageEntry.files || []) {
2432
+ if (!filePath) {
2433
+ continue;
2434
+ }
2435
+ ownedFilePaths.add(filePath);
2436
+ let fileComponent = componentByPath.get(filePath);
2437
+ if (!fileComponent) {
2438
+ fileComponent = await createOSPackageFileComponent(
2439
+ basePath,
2440
+ filePath,
2441
+ commandPathSet,
2442
+ );
2443
+ if (!fileComponent) {
2444
+ continue;
2445
+ }
2446
+ componentByPath.set(filePath, fileComponent);
2447
+ components.push(fileComponent);
2448
+ }
2449
+ providedRefs.add(fileComponent["bom-ref"]);
2450
+ }
2451
+ const serviceResults = await createOSPackageServices(
2452
+ basePath,
2453
+ packageEntry,
2454
+ componentByPath,
2455
+ );
2456
+ for (const service of serviceResults.services) {
2457
+ services.push(service);
2458
+ providedRefs.add(service["bom-ref"]);
2459
+ }
2460
+ if (serviceResults.dependenciesList.length) {
2461
+ dependenciesList.push(...serviceResults.dependenciesList);
2462
+ }
2463
+ if (providedRefs.size) {
2464
+ dependenciesList.push({
2465
+ ref: packageEntry.packageRef,
2466
+ provides: Array.from(providedRefs).sort(),
2467
+ });
2468
+ }
2469
+ }
2470
+ return {
2471
+ components,
2472
+ dependenciesList,
2473
+ ownedFilePaths: Array.from(ownedFilePaths).sort(),
2474
+ services: dedupeServices(services),
2475
+ };
2476
+ }
2477
+
2478
+ async function createOSPackageFileComponent(
2479
+ basePath,
2480
+ filePath,
2481
+ commandPathSet,
2482
+ ) {
2483
+ const normalizedFilePath = normalizeContainerPath(filePath);
2484
+ if (!normalizedFilePath) {
2485
+ return undefined;
2486
+ }
2487
+ let hashes;
2488
+ try {
2489
+ const hashValues = await multiChecksumFile(
2490
+ ["md5", "sha1"],
2491
+ join(basePath, normalizedFilePath.replace(/^\/+/, "")),
2492
+ );
2493
+ hashes = [
2494
+ { alg: "MD5", content: hashValues.md5 },
2495
+ { alg: "SHA-1", content: hashValues.sha1 },
2496
+ ];
2497
+ } catch (_e) {
2498
+ // ignore
2499
+ }
2500
+ const fileName = basename(normalizedFilePath);
2501
+ const stats = statSync(
2502
+ join(basePath, normalizedFilePath.replace(/^\/+/, "")),
2503
+ {
2504
+ throwIfNoEntry: false,
2505
+ },
2506
+ );
2507
+ if (!stats || stats.isDirectory()) {
2508
+ return undefined;
2509
+ }
2510
+ let linkedName;
2511
+ try {
2512
+ const resolvedPath = realpathSync(
2513
+ join(basePath, normalizedFilePath.replace(/^\/+/, "")),
2514
+ );
2515
+ const linkStats = lstatSync(
2516
+ join(basePath, normalizedFilePath.replace(/^\/+/, "")),
2517
+ );
2518
+ if (linkStats?.isSymbolicLink()) {
2519
+ linkedName = basename(resolvedPath);
2520
+ recordSymlinkResolution(
2521
+ join(basePath, normalizedFilePath.replace(/^\/+/, "")),
2522
+ resolvedPath,
2523
+ {
2524
+ basePath,
2525
+ metadata: {
2526
+ resolutionKind: "container-os-package-file",
2527
+ },
2528
+ },
2529
+ );
2530
+ }
2531
+ } catch (_e) {
2532
+ // ignore
2533
+ }
2534
+ const fileType = determineOwnedFileType(
2535
+ normalizedFilePath,
2536
+ stats,
2537
+ commandPathSet,
2538
+ );
2539
+ const properties = [{ name: "internal:SrcFile", value: normalizedFilePath }];
2540
+ if (fileType === "executable") {
2541
+ properties.push({ name: "internal:is_executable", value: "true" });
2542
+ } else if (fileType === "shared_library") {
2543
+ properties.push({ name: "internal:is_shared_library", value: "true" });
2544
+ } else {
2545
+ properties.push({ name: "internal:is_file", value: "true" });
2546
+ }
2547
+ properties.push(...createContainerRiskProperties(fileName, linkedName));
2548
+ properties.push(...createGtfoBinsProperties(fileName, linkedName));
2549
+ // The file path is a purl subpath, not a qualifier: `path` is not among the
2550
+ // qualifiers the generic type allows. The SrcFile property above already
2551
+ // carries the absolute path, so the subpath is stripped to a relative one as
2552
+ // the spec requires.
2553
+ const purl = tryBuildPurl({
2554
+ type: "generic",
2555
+ name: fileName,
2556
+ subpath: normalizedFilePath ? normalizedFilePath.replace(/^\/+/, "") : null,
2557
+ });
2558
+ return {
2559
+ name: fileName,
2560
+ type: "file",
2561
+ ...(purl ? { purl, "bom-ref": purl } : {}),
2562
+ hashes,
2563
+ properties,
2564
+ evidence: {
2565
+ identity: [
2566
+ {
2567
+ field: "purl",
2568
+ confidence: 0,
2569
+ methods: [
2570
+ {
2571
+ technique: "filename",
2572
+ confidence: 0,
2573
+ value: normalizedFilePath,
2574
+ },
2575
+ ],
2576
+ concludedValue: normalizedFilePath,
2577
+ },
2578
+ ],
2579
+ },
2580
+ };
2581
+ }
2582
+
2583
+ async function createOSPackageServices(
2584
+ basePath,
2585
+ packageEntry,
2586
+ componentByPath,
2587
+ ) {
2588
+ const services = [];
2589
+ const dependenciesList = [];
2590
+ for (const filePath of packageEntry.files || []) {
2591
+ const serviceDescriptor = parseOwnedServiceFile(basePath, filePath);
2592
+ if (!serviceDescriptor) {
2593
+ continue;
2594
+ }
2595
+ const service = buildOwnedServiceComponent(packageEntry, serviceDescriptor);
2596
+ services.push(service);
2597
+ const dependsOn = new Set([packageEntry.packageRef]);
2598
+ const fileComponent = componentByPath.get(filePath);
2599
+ if (fileComponent?.["bom-ref"]) {
2600
+ dependsOn.add(fileComponent["bom-ref"]);
2601
+ }
2602
+ for (const execPath of serviceDescriptor.execPaths) {
2603
+ const execComponent = componentByPath.get(execPath);
2604
+ if (execComponent?.["bom-ref"]) {
2605
+ dependsOn.add(execComponent["bom-ref"]);
2606
+ }
2607
+ }
2608
+ dependenciesList.push({
2609
+ ref: service["bom-ref"],
2610
+ dependsOn: Array.from(dependsOn).sort(),
2611
+ });
2612
+ }
2613
+ return { services, dependenciesList };
2614
+ }
2615
+
2616
+ function parseOwnedServiceFile(basePath, filePath) {
2617
+ const normalizedFilePath = normalizeContainerPath(filePath);
2618
+ if (!normalizedFilePath) {
2619
+ return undefined;
2620
+ }
2621
+ const fileContentPath = join(
2622
+ basePath,
2623
+ normalizedFilePath.replace(/^\/+/, ""),
2624
+ );
2625
+ if (!safeExistsSync(fileContentPath)) {
2626
+ return undefined;
2627
+ }
2628
+ if (isSystemdServiceFile(normalizedFilePath)) {
2629
+ const unitData = readFileSync(fileContentPath, "utf-8");
2630
+ const unitMetadata = parseSystemdUnitFile(unitData);
2631
+ return {
2632
+ description: unitMetadata.description,
2633
+ execPaths: unitMetadata.execPaths,
2634
+ filePath: normalizedFilePath,
2635
+ manager: "systemd",
2636
+ name: basename(normalizedFilePath).replace(/\.[^.]+$/, ""),
2637
+ properties: [
2638
+ { name: "cdx:service:manager", value: "systemd" },
2639
+ {
2640
+ name: "cdx:service:unitType",
2641
+ value: basename(normalizedFilePath).split(".").pop() || "service",
2642
+ },
2643
+ ...unitMetadata.properties,
2644
+ ],
2645
+ };
2646
+ }
2647
+ if (isInitServiceFile(normalizedFilePath)) {
2648
+ const initData = readFileSync(fileContentPath, "utf-8");
2649
+ const initMetadata = parseInitScriptMetadata(initData);
2650
+ return {
2651
+ description: initMetadata.description,
2652
+ execPaths: initMetadata.execPaths,
2653
+ filePath: normalizedFilePath,
2654
+ manager: "sysvinit",
2655
+ name: initMetadata.name || basename(normalizedFilePath),
2656
+ properties: [
2657
+ { name: "cdx:service:manager", value: "sysvinit" },
2658
+ ...initMetadata.properties,
2659
+ ],
2660
+ };
2661
+ }
2662
+ return undefined;
2663
+ }
2664
+
2665
+ function buildOwnedServiceComponent(packageEntry, serviceDescriptor) {
2666
+ const serviceName = sanitizeServiceName(serviceDescriptor.name);
2667
+ const manager = serviceDescriptor.manager;
2668
+ return {
2669
+ "bom-ref": `urn:service:${manager}:${sanitizeServiceRefToken(packageEntry.packageRef)}:${sanitizeServiceRefToken(serviceName)}`,
2670
+ name: serviceName,
2671
+ version: packageEntry.packageVersion || "latest",
2672
+ group: packageEntry.packageName || manager,
2673
+ description: serviceDescriptor.description,
2674
+ properties: uniqueProperties(
2675
+ [
2676
+ { name: "internal:SrcFile", value: serviceDescriptor.filePath },
2677
+ { name: "cdx:service:packageRef", value: packageEntry.packageRef },
2678
+ { name: "cdx:service:packageName", value: packageEntry.packageName },
2679
+ ].concat(serviceDescriptor.properties || []),
2680
+ ),
2681
+ };
2682
+ }
2683
+
2684
+ function parseSystemdUnitFile(unitData) {
2685
+ const properties = [];
2686
+ const execPaths = new Set();
2687
+ let description;
2688
+ let currentSection = "";
2689
+ for (const rawLine of unitData.split(/\r?\n/)) {
2690
+ const line = rawLine.trim();
2691
+ if (!line || line.startsWith("#") || line.startsWith(";")) {
2692
+ continue;
2693
+ }
2694
+ if (line.startsWith("[") && line.endsWith("]")) {
2695
+ currentSection = line.slice(1, -1);
2696
+ continue;
2697
+ }
2698
+ const equalsIndex = line.indexOf("=");
2699
+ if (equalsIndex === -1) {
2700
+ continue;
2701
+ }
2702
+ const key = line.slice(0, equalsIndex).trim();
2703
+ const value = line.slice(equalsIndex + 1).trim();
2704
+ if (!value) {
2705
+ continue;
2706
+ }
2707
+ if (currentSection === "Unit" && key === "Description") {
2708
+ description = value;
2709
+ }
2710
+ if (currentSection === "Service") {
2711
+ if (key.startsWith("Exec")) {
2712
+ properties.push({ name: `cdx:service:${key}`, value });
2713
+ const execPath = extractExecPath(value);
2714
+ if (execPath) {
2715
+ execPaths.add(execPath);
2716
+ }
2717
+ } else if (
2718
+ ["Type", "User", "Group", "WorkingDirectory", "Restart"].includes(key)
2719
+ ) {
2720
+ properties.push({ name: `cdx:service:${key}`, value });
2721
+ }
2722
+ }
2723
+ if (
2724
+ currentSection === "Install" &&
2725
+ ["WantedBy", "RequiredBy", "Alias"].includes(key)
2726
+ ) {
2727
+ properties.push({ name: `cdx:service:${key}`, value });
2728
+ }
2729
+ if (
2730
+ currentSection === "Unit" &&
2731
+ ["After", "Requires", "Wants"].includes(key)
2732
+ ) {
2733
+ properties.push({ name: `cdx:service:${key}`, value });
2734
+ }
2735
+ }
2736
+ return {
2737
+ description,
2738
+ execPaths: Array.from(execPaths).sort(),
2739
+ properties: uniqueProperties(properties),
2740
+ };
2741
+ }
2742
+
2743
+ function parseInitScriptMetadata(initData) {
2744
+ const properties = [];
2745
+ const execPaths = new Set();
2746
+ let description;
2747
+ let name;
2748
+ for (const rawLine of initData.split(/\r?\n/)) {
2749
+ const line = rawLine.trim();
2750
+ if (!line.startsWith("#")) {
2751
+ const execPath = extractExecPath(line);
2752
+ if (execPath) {
2753
+ execPaths.add(execPath);
2754
+ }
2755
+ continue;
2756
+ }
2757
+ const normalized = line.replace(/^#+\s*/, "");
2758
+ if (normalized.startsWith("Provides:")) {
2759
+ const providedName = normalized
2760
+ .replace(/^Provides:\s*/, "")
2761
+ .split(/\s+/)[0];
2762
+ if (providedName) {
2763
+ name = providedName;
2764
+ properties.push({ name: "cdx:service:Provides", value: providedName });
2765
+ }
2766
+ continue;
2767
+ }
2768
+ if (normalized.startsWith("Short-Description:")) {
2769
+ description = normalized.replace(/^Short-Description:\s*/, "");
2770
+ continue;
2771
+ }
2772
+ if (normalized.startsWith("Description:")) {
2773
+ description = normalized.replace(/^Description:\s*/, "");
2774
+ continue;
2775
+ }
2776
+ if (normalized.startsWith("Required-Start:")) {
2777
+ properties.push({
2778
+ name: "cdx:service:RequiredStart",
2779
+ value: normalized.replace(/^Required-Start:\s*/, ""),
2780
+ });
2781
+ continue;
2782
+ }
2783
+ if (normalized.startsWith("Default-Start:")) {
2784
+ properties.push({
2785
+ name: "cdx:service:DefaultStart",
2786
+ value: normalized.replace(/^Default-Start:\s*/, ""),
2787
+ });
2788
+ }
2789
+ }
2790
+ return {
2791
+ description,
2792
+ execPaths: Array.from(execPaths).sort(),
2793
+ name,
2794
+ properties: uniqueProperties(properties),
2795
+ };
2796
+ }
2797
+
2798
+ function extractExecPath(commandLine) {
2799
+ if (!commandLine) {
2800
+ return undefined;
2801
+ }
2802
+ const sanitized = commandLine.replace(/^[\-@:+!|]+/, "").trim();
2803
+ const match = sanitized.match(/^("[^"]+"|'[^']+'|\S+)/);
2804
+ if (!match?.[1]) {
2805
+ return undefined;
2806
+ }
2807
+ const candidate = match[1].replace(/^['"]|['"]$/g, "");
2808
+ return candidate.startsWith("/")
2809
+ ? normalizeContainerPath(candidate)
2810
+ : undefined;
2811
+ }
2812
+
2813
+ function determineOwnedFileType(filePath, stats, commandPathSet) {
2814
+ if (!stats) {
2815
+ return "file";
2816
+ }
2817
+ if (isSharedLibraryPath(filePath)) {
2818
+ return "shared_library";
2819
+ }
2820
+ if (commandPathSet?.has(filePath)) {
2821
+ return "executable";
2822
+ }
2823
+ if (stats.mode & 0o111) {
2824
+ return "executable";
2825
+ }
2826
+ return "file";
2827
+ }
2828
+
2829
+ function isSharedLibraryPath(filePath) {
2830
+ return /(?:^|\/)[^/]+\.(?:so(?:\.[^/]+)?|a|lib|dll)$/i.test(filePath);
2831
+ }
2832
+
2833
+ function isSystemdServiceFile(filePath) {
2834
+ return /\/(?:etc|lib|usr\/lib)\/systemd\/system\/.+\.(?:service|socket|timer|mount|path|target|slice|automount)$/i.test(
2835
+ filePath,
2836
+ );
2837
+ }
2838
+
2839
+ function isInitServiceFile(filePath) {
2840
+ return /\/(?:etc\/init\.d|etc\/rc\.d\/init\.d)\/.+/i.test(filePath);
2841
+ }
2842
+
2843
+ function sanitizeServiceName(value) {
2844
+ return String(value || "service").trim() || "service";
2845
+ }
2846
+
2847
+ function sanitizeServiceRefToken(value) {
2848
+ return (
2849
+ String(value || "service")
2850
+ .toLowerCase()
2851
+ .replace(/[^a-z0-9._:-]+/g, "-")
2852
+ .replace(/^-+|-+$/g, "") || "service"
2853
+ );
2854
+ }
2855
+
2856
+ function uniqueProperties(properties) {
2857
+ const seen = new Set();
2858
+ const uniqueValues = [];
2859
+ for (const property of properties || []) {
2860
+ if (!property?.name || !property?.value) {
2861
+ continue;
2862
+ }
2863
+ const key = `${property.name}\u0000${property.value}`;
2864
+ if (seen.has(key)) {
2865
+ continue;
2866
+ }
2867
+ seen.add(key);
2868
+ uniqueValues.push(property);
2869
+ }
2870
+ return uniqueValues;
2871
+ }
2872
+
2873
+ function uniqueSortedStrings(values) {
2874
+ return Array.from(new Set((values || []).filter(Boolean))).sort();
2875
+ }
2876
+
2877
+ function dedupeServices(services) {
2878
+ const serviceMap = new Map();
2879
+ for (const service of services || []) {
2880
+ if (!service?.["bom-ref"]) {
2881
+ continue;
2882
+ }
2883
+ if (!serviceMap.has(service["bom-ref"])) {
2884
+ serviceMap.set(service["bom-ref"], {
2885
+ ...service,
2886
+ properties: uniqueProperties(service.properties),
2887
+ });
2888
+ continue;
2889
+ }
2890
+ const existing = serviceMap.get(service["bom-ref"]);
2891
+ existing.properties = uniqueProperties(
2892
+ (existing.properties || []).concat(service.properties || []),
2893
+ );
2894
+ }
2895
+ return Array.from(serviceMap.values());
2896
+ }
2897
+
2898
+ function normalizeContainerPath(filePath) {
2899
+ if (!filePath) {
2900
+ return undefined;
2901
+ }
2902
+ const normalized = filePath.replace(/\\/g, "/").replace(/^\/+/, "/");
2903
+ return normalized.startsWith("/") ? normalized : `/${normalized}`;
2904
+ }
2905
+
2906
+ /**
2907
+ * Remove the `modularitylabel` qualifier from a component's purl and return its
2908
+ * decoded value.
2909
+ *
2910
+ * The label is a Red Hat modularity concept that trivy expresses as a purl
2911
+ * qualifier. It is not a qualifier the rpm purl type defines, and its value is
2912
+ * a colon-separated stream that trivy percent-encodes, which a canonical purl
2913
+ * does not. Either alone makes the purl unparseable, so the label travels as a
2914
+ * component property instead.
2915
+ *
2916
+ * @param {object} comp Component whose purl is rewritten in place
2917
+ * @returns {string|undefined} The decoded label, when one was present
2918
+ */
2919
+ function extractModularityLabel(comp) {
2920
+ const [base, query] = `${comp.purl}`.split("?");
2921
+ if (!query) {
2922
+ return undefined;
2923
+ }
2924
+ let label;
2925
+ const kept = query.split("&").filter((pair) => {
2926
+ if (!pair.startsWith("modularitylabel=")) {
2927
+ return true;
2928
+ }
2929
+ try {
2930
+ label = decodeURIComponent(pair.slice("modularitylabel=".length));
2931
+ } catch {
2932
+ label = pair.slice("modularitylabel=".length);
2933
+ }
2934
+ return false;
2935
+ });
2936
+ if (label === undefined) {
2937
+ return undefined;
2938
+ }
2939
+ comp.purl = kept.length ? `${base}?${kept.join("&")}` : base;
2940
+ return label;
2941
+ }
2942
+
2943
+ // Detect common sdks and runtimes from the name
2944
+ function detectSdksRuntimes(comp, bundledSdks, bundledRuntimes) {
2945
+ if (!comp?.name) {
2946
+ return;
2947
+ }
2948
+ if (/dotnet[6-9]?-sdk/.test(comp.name)) {
2949
+ bundledSdks.add(comp.name);
2950
+ }
2951
+ if (
2952
+ /dotnet[6-9]?-runtime/.test(comp.name) ||
2953
+ comp.name.includes("aspnet-runtime") ||
2954
+ /aspnetcore[6-9]?-runtime/.test(comp.name)
2955
+ ) {
2956
+ bundledRuntimes.add(comp.name);
2957
+ }
2958
+ // TODO: Need to test this for a range of base images
2959
+ if (COMMON_RUNTIMES.includes(comp.name)) {
2960
+ bundledRuntimes.add(comp.name);
2961
+ }
2962
+ }
2963
+
2964
+ const retrieveDependencies = (tmpDependencies, origBomRef, comp) => {
2965
+ try {
2966
+ const tmpDependsOn =
2967
+ tmpDependencies[origBomRef] || tmpDependencies[comp["bom-ref"]] || [];
2968
+ const dependsOn = new Set();
2969
+ tmpDependsOn.forEach((d) => {
2970
+ try {
2971
+ const compPurl = Purl.parse(comp.purl);
2972
+ const tmpPurl = Purl.parse(d.replace("none", compPurl.type));
2973
+ tmpPurl.type = compPurl.type;
2974
+ // FIXME: Check if this hack is still needed with the latest trivy
2975
+ if (OS_PURL_TYPES.includes(compPurl.type)) {
2976
+ tmpPurl.namespace = compPurl.namespace;
2977
+ tmpPurl.qualifiers = tmpPurl.qualifiers || {};
2978
+ if (compPurl.qualifiers) {
2979
+ if (compPurl.qualifiers.distro_name) {
2980
+ tmpPurl.qualifiers.distro_name = compPurl.qualifiers.distro_name;
2981
+ }
2982
+ if (compPurl.qualifiers.distro) {
2983
+ tmpPurl.qualifiers.distro = compPurl.qualifiers.distro;
2984
+ }
2985
+ }
2986
+ }
2987
+ if (tmpPurl.qualifiers) {
2988
+ if (
2989
+ tmpPurl.qualifiers.epoch &&
2990
+ !tmpPurl.version.startsWith(`${tmpPurl.qualifiers.epoch}:`)
2991
+ ) {
2992
+ tmpPurl.version = `${tmpPurl.qualifiers.epoch}:${tmpPurl.version}`;
2993
+ }
2994
+ }
2995
+ // Prevents purls ending with ?
2996
+ if (!Object.keys(tmpPurl.qualifiers).length) {
2997
+ tmpPurl.qualifiers = undefined;
2998
+ }
2999
+ dependsOn.add(decodeURIComponent(tmpPurl.toString()));
3000
+ } catch (_e) {
3001
+ // ignore
3002
+ }
3003
+ });
3004
+ return { ref: comp["bom-ref"], dependsOn: Array.from(dependsOn).sort() };
3005
+ } catch (_e) {
3006
+ // ignore
3007
+ }
3008
+ return undefined;
3009
+ };
3010
+
3011
+ function isHostInspectionPath(value) {
3012
+ if (!value) {
3013
+ return false;
3014
+ }
3015
+ if (platform === "darwin") {
3016
+ return value.startsWith("/");
3017
+ }
3018
+ if (platform === "windows") {
3019
+ return /^[a-z]:\\/i.test(value) || /^\\\\/.test(value);
3020
+ }
3021
+ return false;
3022
+ }
3023
+
3024
+ function isDarwinSystemHostPath(value) {
3025
+ if (typeof value !== "string") {
3026
+ return false;
3027
+ }
3028
+ return (
3029
+ value.startsWith("/bin/") ||
3030
+ value.startsWith("/sbin/") ||
3031
+ value.startsWith("/System/") ||
3032
+ value.startsWith("/usr/bin/") ||
3033
+ value.startsWith("/usr/libexec/") ||
3034
+ value.startsWith("/usr/sbin/") ||
3035
+ value.startsWith("/Library/Apple/System/") ||
3036
+ value.startsWith("/System/Volumes/Preboot/Cryptexes/")
3037
+ );
3038
+ }
3039
+
3040
+ function shouldInspectComponentHostPath(value) {
3041
+ if (!isHostInspectionPath(value)) {
3042
+ return false;
3043
+ }
3044
+ if (platform === "darwin") {
3045
+ return !(
3046
+ value.toLowerCase().endsWith(".plist") || isDarwinSystemHostPath(value)
3047
+ );
3048
+ }
3049
+ return true;
3050
+ }
3051
+
3052
+ function shouldInspectComponentTrust(component) {
3053
+ if (platform !== "darwin") {
3054
+ return true;
3055
+ }
3056
+ const queryCategory = `${
3057
+ (component?.properties || []).find(
3058
+ (property) => property?.name === "cdx:osquery:category",
3059
+ )?.value || ""
3060
+ }`.trim();
3061
+ if (!queryCategory) {
3062
+ return true;
3063
+ }
3064
+ return new Set(["launchd_services", "startup_items", "running_apps"]).has(
3065
+ queryCategory,
3066
+ );
3067
+ }
3068
+
3069
+ function extractComponentHostPaths(component) {
3070
+ if (!shouldInspectComponentTrust(component)) {
3071
+ return [];
3072
+ }
3073
+ const pathPropertyNames = new Set([
3074
+ "path",
3075
+ "bundle_path",
3076
+ "bundle_executable",
3077
+ "executable",
3078
+ "program",
3079
+ "image_path",
3080
+ "binary_path",
3081
+ "action_path",
3082
+ ]);
3083
+ const paths = [];
3084
+ for (const property of component?.properties || []) {
3085
+ const propertyName = `${property?.name || ""}`.toLowerCase();
3086
+ if (!pathPropertyNames.has(propertyName)) {
3087
+ continue;
3088
+ }
3089
+ const normalizedValue = `${property?.value || ""}`.trim();
3090
+ if (shouldInspectComponentHostPath(normalizedValue)) {
3091
+ paths.push(normalizedValue);
3092
+ }
3093
+ }
3094
+ return uniqueSortedStrings(paths);
3095
+ }
3096
+
3097
+ function createHostTrustFindingComponent(finding) {
3098
+ if (!finding?.kind || !finding?.name) {
3099
+ return undefined;
3100
+ }
3101
+ const purl = build({
3102
+ type: "generic",
3103
+ namespace: "host-trust",
3104
+ name: finding.name,
3105
+ version: finding.version || "observed",
3106
+ });
3107
+ const component = {
3108
+ "bom-ref": decodeURIComponent(purl),
3109
+ name: finding.name,
3110
+ version: finding.version || "observed",
3111
+ description: finding.description,
3112
+ purl,
3113
+ type: "data",
3114
+ properties: uniqueProperties(
3115
+ (finding.properties || []).concat([
3116
+ { name: "cdx:trustinspector:kind", value: finding.kind },
3117
+ ...(finding.path
3118
+ ? [{ name: "internal:SrcFile", value: finding.path }]
3119
+ : []),
3120
+ ]),
3121
+ ),
3122
+ };
3123
+ if (finding.sha256) {
3124
+ component.hashes = [{ alg: "SHA-256", content: finding.sha256 }];
3125
+ }
3126
+ attachIdentityTools(component, trustInspectorToolRefs());
3127
+ return component;
3128
+ }
3129
+
3130
+ /**
3131
+ * Batch-enrich operating-system components with host-path trust data using the
3132
+ * `trustinspector` helper on darwin and Windows.
3133
+ *
3134
+ * @param {Object[]} [components=[]] OS components to enrich.
3135
+ * @returns {{components: Object[], tools: Object[]}} The enriched components
3136
+ * and any trust-inspector tool components that should be attached to the BOM
3137
+ * metadata.
3138
+ */
3139
+ export function enrichOSComponentsWithTrustData(components = []) {
3140
+ if (!["darwin", "windows"].includes(platform) || !TRUSTINSPECTOR_BIN) {
3141
+ return { components, tools: [] };
3142
+ }
3143
+ const mergedComponents = [...components];
3144
+ const pathInspectionCandidates = uniqueSortedStrings(
3145
+ components.flatMap((component) => extractComponentHostPaths(component)),
3146
+ );
3147
+ if (pathInspectionCandidates.length) {
3148
+ const inspectionMap = new Map();
3149
+ const batchSize = 200;
3150
+ for (
3151
+ let offset = 0;
3152
+ offset < pathInspectionCandidates.length;
3153
+ offset += batchSize
3154
+ ) {
3155
+ const inspectionBatch = pathInspectionCandidates.slice(
3156
+ offset,
3157
+ offset + batchSize,
3158
+ );
3159
+ const inspectionData = executeTrustInspector(
3160
+ ["paths", ...inspectionBatch],
3161
+ {
3162
+ target: `${inspectionBatch.length} path(s)`,
3163
+ },
3164
+ );
3165
+ for (const inspection of inspectionData?.inspections || []) {
3166
+ inspectionMap.set(
3167
+ inspection.path,
3168
+ uniqueProperties(inspection.properties || []),
3169
+ );
3170
+ }
3171
+ }
3172
+ if (inspectionMap.size) {
3173
+ for (const component of mergedComponents) {
3174
+ const matchingProperties = extractComponentHostPaths(component)
3175
+ .map((candidatePath) => inspectionMap.get(candidatePath))
3176
+ .filter(Boolean)
3177
+ .flat();
3178
+ if (!matchingProperties.length) {
3179
+ continue;
3180
+ }
3181
+ component.properties = uniqueProperties(
3182
+ (component.properties || []).concat(matchingProperties),
3183
+ );
3184
+ attachIdentityTools(component, trustInspectorToolRefs());
3185
+ }
3186
+ }
3187
+ }
3188
+ const hostData = executeTrustInspector(["host"], { target: platform });
3189
+ const hostComponents = (hostData?.hostFindings || [])
3190
+ .map((finding) => createHostTrustFindingComponent(finding))
3191
+ .filter(Boolean);
3192
+ if (hostComponents.length) {
3193
+ mergedComponents.push(...hostComponents);
3194
+ }
3195
+ return {
3196
+ components: mergedComponents,
3197
+ tools:
3198
+ pathInspectionCandidates.length || hostComponents.length
3199
+ ? getPluginToolComponents(["trustinspector"])
3200
+ : [],
3201
+ };
3202
+ }
3203
+
3204
+ /**
3205
+ * Execute a SQL query against the bundled osquery binary and return the parsed
3206
+ * JSON result.
3207
+ *
3208
+ * @param {string} query The SQL query string to run.
3209
+ * @returns {Object|undefined} The parsed JSON result, or undefined when the
3210
+ * binary is unavailable, dry-run blocks execution, or the output cannot be
3211
+ * parsed.
3212
+ */
3213
+ export function executeOsQuery(query) {
3214
+ if (isDryRun) {
3215
+ recordActivity({
3216
+ kind: "osquery",
3217
+ reason:
3218
+ "Dry run mode blocks osquery execution and reports the query instead.",
3219
+ status: "blocked",
3220
+ target: query,
3221
+ });
3222
+ return undefined;
3223
+ }
3224
+ if (OSQUERY_BIN) {
3225
+ if (!query.endsWith(";")) {
3226
+ query = `${query};`;
3227
+ }
3228
+ const args = ["--S", "--disable_database", "--json", query];
3229
+ // On darwin, we need to disable the safety check and run cdxgen with sudo
3230
+ // https://github.com/osquery/osquery/issues/1382
3231
+ if (platform === "darwin") {
3232
+ args.push("--allow_unsafe");
3233
+ args.push("--disable_logging");
3234
+ args.push("--disable_events");
3235
+ }
3236
+ if (DEBUG_MODE) {
3237
+ console.log("Executing", OSQUERY_BIN, args.join(" "));
3238
+ }
3239
+ const result = safeSpawnSync(OSQUERY_BIN, args);
3240
+ if (result.status !== 0 || result.error) {
3241
+ if (
3242
+ DEBUG_MODE &&
3243
+ result.stderr &&
3244
+ !result.stderr.includes("no such table")
3245
+ ) {
3246
+ console.error(result.stdout, result.stderr);
3247
+ }
3248
+ }
3249
+ if (result) {
3250
+ const stdout = result.stdout;
3251
+ if (stdout) {
3252
+ const cmdOutput = stdout;
3253
+ if (cmdOutput !== "") {
3254
+ try {
3255
+ return JSON.parse(cmdOutput);
3256
+ } catch (_err) {
3257
+ // ignore
3258
+ if (DEBUG_MODE) {
3259
+ console.log("Unable to parse the output from query", query);
3260
+ console.log(
3261
+ "This could be due to the amount of data returned or the query being invalid for the given platform.",
3262
+ );
3263
+ }
3264
+ }
3265
+ }
3266
+ return undefined;
3267
+ }
3268
+ }
3269
+ }
3270
+ return undefined;
3271
+ }
3272
+
3273
+ /**
3274
+ * Method to execute dosai to create slices for dotnet
3275
+ *
3276
+ * @param {string} src Source Path
3277
+ * @param {string} slicesFile Slices file name
3278
+ * @returns boolean
3279
+ */
3280
+ export function getDotnetSlices(src, slicesFile) {
3281
+ if (!DOSAI_BIN) {
3282
+ return false;
3283
+ }
3284
+ const args = ["methods", "--path", src, "--o", slicesFile];
3285
+ if (DEBUG_MODE) {
3286
+ console.log("Executing", DOSAI_BIN, args.join(" "));
3287
+ }
3288
+ const result = safeSpawnSync(DOSAI_BIN, args, {
3289
+ cwd: src,
3290
+ });
3291
+ if (
3292
+ result?.stdout?.includes(
3293
+ "You must install or update .NET to run this application",
3294
+ ) ||
3295
+ result?.stderr?.includes(
3296
+ "You must install or update .NET to run this application",
3297
+ )
3298
+ ) {
3299
+ console.log(
3300
+ "Dotnet SDK is not installed. Please use the cdxgen dotnet container images to generate slices for this project.",
3301
+ );
3302
+ console.log(
3303
+ "Alternatively, download the dosai self-contained binary (-full suffix) from https://github.com/owasp-dep-scan/dosai/releases and set the environment variable DOSAI_CMD with its location.",
3304
+ );
3305
+ }
3306
+ if (result.status !== 0 || result.error) {
3307
+ if (DEBUG_MODE && result.error) {
3308
+ if (result.stderr) {
3309
+ console.error(result.stdout, result.stderr);
3310
+ } else {
3311
+ console.log("Check if dosai plugin was installed successfully.");
3312
+ }
3313
+ }
3314
+ return false;
3315
+ }
3316
+ return true;
3317
+ }
3318
+
3319
+ /**
3320
+ * Method to generate binary SBOM using blint
3321
+ *
3322
+ * @param {string} src Path to binary or its directory
3323
+ * @param {string} binaryBomFile Path to binary
3324
+ * @param {boolean} deepMode Deep mode flag
3325
+ *
3326
+ * @return {boolean} Result of the generation
3327
+ */
3328
+ export function getBinaryBom(src, binaryBomFile, deepMode) {
3329
+ if (!BLINT_BIN) {
3330
+ return false;
3331
+ }
3332
+ const args = ["sbom", "-i", resolve(src), "-o", binaryBomFile];
3333
+ if (deepMode) {
3334
+ args.push("--deep");
3335
+ }
3336
+ if (DEBUG_MODE) {
3337
+ console.log("Executing", BLINT_BIN, args.join(" "));
3338
+ }
3339
+ const lstatResult = lstatSync(src, { throwIfNoEntry: false });
3340
+ if (!lstatResult) {
3341
+ console.log(`Source path ${src} does not exist`);
3342
+ return false;
3343
+ }
3344
+ const cwd = lstatResult.isDirectory() ? src : dirname(src);
3345
+ const result = safeSpawnSync(BLINT_BIN, args, {
3346
+ cwd,
3347
+ });
3348
+ if (result.status !== 0 || result.error) {
3349
+ if (result.stderr) {
3350
+ console.error(result.stdout, result.stderr);
3351
+ } else {
3352
+ console.log(
3353
+ "Install blint using 'pip install blint' or use the cdxgen container image.",
3354
+ );
3355
+ }
3356
+ return false;
3357
+ }
3358
+ return true;
3359
+ }
3360
+
3361
+ async function fileComponents(basePath, fileList, fileType) {
3362
+ const hashResults = await mapWithConcurrency(fileList, (f) =>
3363
+ multiChecksumFile(["md5", "sha1"], join(basePath, f))
3364
+ .then((hashValues) => [
3365
+ { alg: "MD5", content: hashValues["md5"] },
3366
+ { alg: "SHA-1", content: hashValues["sha1"] },
3367
+ ])
3368
+ .catch(() => undefined),
3369
+ );
3370
+ const components = [];
3371
+ for (let i = 0; i < fileList.length; i++) {
3372
+ let f = fileList[i];
3373
+ const hashes = hashResults[i];
3374
+ if (!f.startsWith("/")) {
3375
+ f = `/${f}`;
3376
+ }
3377
+ const name = basename(f);
3378
+ let linkedName;
3379
+ try {
3380
+ const resolvedPath = realpathSync(join(basePath, f.replace(/^\/+/, "")));
3381
+ const linkStats = lstatSync(join(basePath, f.replace(/^\/+/, "")));
3382
+ if (linkStats?.isSymbolicLink()) {
3383
+ linkedName = basename(resolvedPath);
3384
+ recordSymlinkResolution(
3385
+ join(basePath, f.replace(/^\/+/, "")),
3386
+ resolvedPath,
3387
+ {
3388
+ basePath,
3389
+ metadata: {
3390
+ resolutionKind: "container-binary",
3391
+ },
3392
+ },
3393
+ );
3394
+ }
3395
+ } catch (_e) {
3396
+ // ignore
3397
+ }
3398
+ const purl = genericPurl(name);
3399
+ let isExecutable;
3400
+ let isSetuid;
3401
+ let isSetgid;
3402
+ let isSticky;
3403
+ try {
3404
+ const stats = statSync(join(basePath, f.replace(/^\/+/, "")));
3405
+ const mode = stats.mode;
3406
+ isExecutable = !!(mode & 0o111);
3407
+ isSetuid = !!(mode & 0o4000);
3408
+ isSetgid = !!(mode & 0o2000);
3409
+ isSticky = !!(mode & 0o1000);
3410
+ } catch (_e) {
3411
+ // ignore
3412
+ }
3413
+ const properties = [{ name: "internal:SrcFile", value: f }];
3414
+ if (fileType === "executable" && isExecutable !== undefined) {
3415
+ properties.push({
3416
+ name: `internal:is_${fileType}`,
3417
+ value: isExecutable.toString(),
3418
+ });
3419
+ } else {
3420
+ properties.push({ name: `internal:is_${fileType}`, value: "true" });
3421
+ }
3422
+ if (isSetuid) {
3423
+ properties.push({ name: "internal:has_setuid", value: "true" });
3424
+ }
3425
+ if (isSetgid) {
3426
+ properties.push({ name: "internal:has_setgid", value: "true" });
3427
+ }
3428
+ if (isSticky) {
3429
+ properties.push({ name: "internal:has_sticky", value: "true" });
3430
+ }
3431
+ properties.push(...createContainerRiskProperties(name, linkedName));
3432
+ properties.push(...createGtfoBinsProperties(name, linkedName));
3433
+ components.push({
3434
+ name,
3435
+ type: "file",
3436
+ purl,
3437
+ "bom-ref": purl,
3438
+ hashes,
3439
+ properties,
3440
+ evidence: {
3441
+ identity: [
3442
+ {
3443
+ field: "purl",
3444
+ confidence: 0,
3445
+ methods: [
3446
+ {
3447
+ technique: "filename",
3448
+ confidence: 0,
3449
+ value: f,
3450
+ },
3451
+ ],
3452
+ concludedValue: f,
3453
+ },
3454
+ ],
3455
+ },
3456
+ });
3457
+ }
3458
+ return components;
3459
+ }