@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,4222 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { readFileSync } from "node:fs";
3
+ import path, {
4
+ sep as _sep,
5
+ basename,
6
+ dirname,
7
+ join,
8
+ relative,
9
+ resolve,
10
+ } from "node:path";
11
+
12
+ import { build, Purl } from "@cdxgen/cdx-purl";
13
+ import ssri from "ssri";
14
+ import { parse as _load, parseAllDocuments } from "yaml";
15
+
16
+ import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
17
+ import { shouldFetchPackageMetadata } from "../core/env.js";
18
+ import { safeExistsSync } from "../core/fs.js";
19
+ import { analyzeSuspiciousJsFile } from "../inventory/analyzer.js";
20
+ import { encodeForPurl, npmPurl } from "../inventory/purl.js";
21
+ import Arborist from "../third-party/arborist/lib/index.js";
22
+ import { getNpmMetadata } from "./ecosystems.js";
23
+ import {
24
+ buildNpmGitDistributionIntakeRefs,
25
+ buildNpmGitPurlQualifiers,
26
+ buildPnpmGitPkgRefs,
27
+ collectNpmManifestSources,
28
+ findMatchingNpmWorkspace,
29
+ hydrateNpmNodePackage,
30
+ loadNpmrcConfig,
31
+ normalizePnpmLockKey,
32
+ parsePnpmGitLockKey,
33
+ setNpmDevelopmentProperty,
34
+ setNpmOptionalProperty,
35
+ setNpmPeerProperty,
36
+ } from "./npmutils.js";
37
+ import { addComponentProperty } from "./parsers-python.js";
38
+
39
+ /**
40
+ * Parse nodejs package json file
41
+ *
42
+ * @param {string} pkgJsonFile package.json file
43
+ * @param {boolean} simple Return a simpler representation of the component by skipping extended attributes and license fetch.
44
+ * @param {boolean} securityProps Collect security-related properties
45
+ */
46
+ const NPM_INSTALL_HOOK_NAMES = [
47
+ "preinstall",
48
+ "install",
49
+ "postinstall",
50
+ "prepublish",
51
+ "prepare",
52
+ ];
53
+
54
+ const NPM_BUILD_SCRIPT_NAME_PATTERN =
55
+ /\b(build|compile|bundle|pack|dist|watch|transpile|minif(?:y|ication)|uglif(?:y|ication))\b/i;
56
+
57
+ const NPM_LIFECYCLE_OBFUSCATION_PATTERNS = [
58
+ [
59
+ "base64-decode",
60
+ /\b(?:base64(?:\s+--decode|\s+-d)?|openssl\s+enc\s+-base64\s+-d)\b/i,
61
+ ],
62
+ ["buffer-base64", /Buffer\.from\s*\([^)]*,\s*["']base64["']\s*\)/i],
63
+ ["atob", /\batob\s*\(/i],
64
+ ["string-from-char-code", /\bString\.fromCharCode\s*\(/i],
65
+ ["long-base64-literal", /\b[A-Za-z0-9+/]{80,}={0,2}\b/],
66
+ ];
67
+
68
+ const NPM_LIFECYCLE_EXECUTION_PATTERNS = [
69
+ ["node-eval", /\bnode\b[^\n]*\s-[ep]\b/i],
70
+ ["eval", /\beval\s*\(/i],
71
+ ["function-constructor", /\b(?:new\s+Function|Function\s*\()/i],
72
+ [
73
+ "child-process",
74
+ /\b(?:child_process|node:child_process|execSync|execFileSync|spawnSync|execFile|spawn|exec)\b/i,
75
+ ],
76
+ [
77
+ "shell-inline",
78
+ /\b(?:sh|bash|cmd|powershell|pwsh)\b[^\n]*\s-(?:c|Command|EncodedCommand)\b/i,
79
+ ],
80
+ ];
81
+
82
+ const NPM_LIFECYCLE_NETWORK_PATTERNS = [
83
+ ["curl", /\bcurl\b/i],
84
+ ["wget", /\bwget\b/i],
85
+ ["invoke-webrequest", /\b(?:invoke-webrequest|iwr)\b/i],
86
+ ["http-url", /https?:\/\//i],
87
+ ];
88
+
89
+ const NPM_LIFECYCLE_JS_RUNNERS = new Set([
90
+ "babel-node",
91
+ "node",
92
+ "ts-node",
93
+ "tsx",
94
+ "bun",
95
+ "deno",
96
+ ]);
97
+
98
+ const NPM_LIFECYCLE_JS_RUNNER_VALUE_OPTIONS = new Set([
99
+ "-c",
100
+ "-e",
101
+ "-p",
102
+ "-r",
103
+ "--config",
104
+ "--conditions",
105
+ "--cwd-file",
106
+ "--compilerOptions",
107
+ "--cwd",
108
+ "--env-file",
109
+ "--env-file-if-exists",
110
+ "--eval",
111
+ "--experimental-loader",
112
+ "--ignore",
113
+ "--import",
114
+ "--import-map",
115
+ "--input-type",
116
+ "--inspect",
117
+ "--inspect-brk",
118
+ "--inspect-port",
119
+ "--loader",
120
+ "--print",
121
+ "--preload",
122
+ "--project",
123
+ "--require",
124
+ "--test-name-pattern",
125
+ "--test-reporter",
126
+ "--test-reporter-destination",
127
+ "--test-shard",
128
+ "--title",
129
+ "--tsconfig",
130
+ "--watch-path",
131
+ ]);
132
+
133
+ const NPM_LIFECYCLE_JS_SOURCE_FILE_PATTERN = /\.[cm]?[jt]sx?$/i;
134
+
135
+ const SHELL_ENV_ASSIGNMENT_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=.*/;
136
+
137
+ function splitLifecycleScriptCommands(scriptValue) {
138
+ const commands = [];
139
+ let current = "";
140
+ let escaped = false;
141
+ let quoteChar = "";
142
+
143
+ for (let index = 0; index < scriptValue.length; index++) {
144
+ const character = scriptValue[index];
145
+ if (escaped) {
146
+ current += character;
147
+ escaped = false;
148
+ continue;
149
+ }
150
+ if (character === "\\") {
151
+ current += character;
152
+ escaped = true;
153
+ continue;
154
+ }
155
+ if (quoteChar) {
156
+ current += character;
157
+ if (character === quoteChar) {
158
+ quoteChar = "";
159
+ }
160
+ continue;
161
+ }
162
+ if (character === '"' || character === "'") {
163
+ current += character;
164
+ quoteChar = character;
165
+ continue;
166
+ }
167
+ if (character === ";" || character === "|") {
168
+ if (current.trim()) {
169
+ commands.push(current.trim());
170
+ }
171
+ current = "";
172
+ continue;
173
+ }
174
+ if (character === "&") {
175
+ if (current.trim()) {
176
+ commands.push(current.trim());
177
+ }
178
+ current = "";
179
+ if (scriptValue[index + 1] === "&") {
180
+ index += 1;
181
+ }
182
+ continue;
183
+ }
184
+ current += character;
185
+ }
186
+ if (current.trim()) {
187
+ commands.push(current.trim());
188
+ }
189
+ return commands;
190
+ }
191
+
192
+ function lifecycleRunnerOptionConsumesValue(token) {
193
+ if (!token?.startsWith("-") || token === "--") {
194
+ return false;
195
+ }
196
+ if (token.includes("=")) {
197
+ return false;
198
+ }
199
+ if (token.startsWith("--")) {
200
+ return NPM_LIFECYCLE_JS_RUNNER_VALUE_OPTIONS.has(token);
201
+ }
202
+ if (token.length > 2) {
203
+ return false;
204
+ }
205
+ return NPM_LIFECYCLE_JS_RUNNER_VALUE_OPTIONS.has(token);
206
+ }
207
+
208
+ function isLifecycleScriptSourceFile(token) {
209
+ if (!token || token.startsWith("-")) {
210
+ return false;
211
+ }
212
+ const fileToken = token.split(/[?#]/u, 1)[0];
213
+ return NPM_LIFECYCLE_JS_SOURCE_FILE_PATTERN.test(fileToken);
214
+ }
215
+
216
+ function findLifecycleScriptSourceArg(tokens, startIndex) {
217
+ for (let index = startIndex; index < tokens.length; index++) {
218
+ const token = tokens[index]?.trim();
219
+ if (!token) {
220
+ continue;
221
+ }
222
+ if (token === "--" || SHELL_ENV_ASSIGNMENT_PATTERN.test(token)) {
223
+ continue;
224
+ }
225
+ if (token.startsWith("-")) {
226
+ if (
227
+ lifecycleRunnerOptionConsumesValue(token) &&
228
+ index + 1 < tokens.length
229
+ ) {
230
+ index += 1;
231
+ }
232
+ continue;
233
+ }
234
+ if (isLifecycleScriptSourceFile(token)) {
235
+ return token;
236
+ }
237
+ break;
238
+ }
239
+ return undefined;
240
+ }
241
+
242
+ function findLifecycleScriptSourceStartIndex(tokens, runnerIndex) {
243
+ const runnerToken = tokens[runnerIndex];
244
+ for (let index = runnerIndex + 1; index < tokens.length; index++) {
245
+ const token = tokens[index]?.trim();
246
+ if (!token || token === "--" || SHELL_ENV_ASSIGNMENT_PATTERN.test(token)) {
247
+ continue;
248
+ }
249
+ if (token.startsWith("-")) {
250
+ if (
251
+ lifecycleRunnerOptionConsumesValue(token) &&
252
+ index + 1 < tokens.length
253
+ ) {
254
+ index += 1;
255
+ }
256
+ continue;
257
+ }
258
+ if (runnerToken === "deno") {
259
+ return token === "run" ? index + 1 : undefined;
260
+ }
261
+ if (runnerToken === "bun" && token === "run") {
262
+ return index + 1;
263
+ }
264
+ return index;
265
+ }
266
+ return undefined;
267
+ }
268
+
269
+ function isResolvedPathWithinDirectory(baseDir, resolvedFile) {
270
+ const relativePath = relative(baseDir, resolvedFile);
271
+ if (!relativePath) {
272
+ return true;
273
+ }
274
+ return (
275
+ !relativePath.startsWith(`..${path.sep}`) &&
276
+ relativePath !== ".." &&
277
+ !path.isAbsolute(relativePath)
278
+ );
279
+ }
280
+
281
+ function collectLifecyclePatternIndicators(scriptValue, patterns) {
282
+ const indicators = [];
283
+ patterns.forEach(([name, pattern]) => {
284
+ if (pattern.test(scriptValue)) {
285
+ indicators.push(name);
286
+ }
287
+ });
288
+ return indicators;
289
+ }
290
+
291
+ function extractLifecycleScriptSourceFiles(pkgJsonFile, scriptValue) {
292
+ const sourceFiles = [];
293
+ const seen = new Set();
294
+ if (!scriptValue || typeof scriptValue !== "string") {
295
+ return sourceFiles;
296
+ }
297
+ const pkgJsonDir = resolve(dirname(pkgJsonFile));
298
+ const commandSegments = splitLifecycleScriptCommands(scriptValue);
299
+ for (const commandSegment of commandSegments) {
300
+ const tokens = splitCommandArgs(commandSegment);
301
+ for (let index = 0; index < tokens.length; index++) {
302
+ if (!NPM_LIFECYCLE_JS_RUNNERS.has(tokens[index])) {
303
+ continue;
304
+ }
305
+ const scriptStartIndex = findLifecycleScriptSourceStartIndex(
306
+ tokens,
307
+ index,
308
+ );
309
+ if (scriptStartIndex === undefined) {
310
+ continue;
311
+ }
312
+ const relativeFile = findLifecycleScriptSourceArg(
313
+ tokens,
314
+ scriptStartIndex,
315
+ );
316
+ if (!relativeFile) {
317
+ continue;
318
+ }
319
+ const resolvedFile = resolve(pkgJsonDir, relativeFile);
320
+ if (
321
+ !isResolvedPathWithinDirectory(pkgJsonDir, resolvedFile) ||
322
+ !safeExistsSync(resolvedFile) ||
323
+ seen.has(resolvedFile)
324
+ ) {
325
+ continue;
326
+ }
327
+ seen.add(resolvedFile);
328
+ sourceFiles.push(resolvedFile);
329
+ }
330
+ }
331
+ return sourceFiles;
332
+ }
333
+
334
+ function analyzeNpmLifecycleScripts(pkgJsonFile, scripts) {
335
+ const executionIndicators = new Set();
336
+ const networkIndicators = new Set();
337
+ const obfuscationIndicators = new Set();
338
+ const obfuscatedScripts = [];
339
+ const suspiciousScripts = [];
340
+ const scriptIndicatorMap = [];
341
+ const riskyScripts = NPM_INSTALL_HOOK_NAMES.filter(
342
+ (scriptName) => scripts?.[scriptName],
343
+ );
344
+ riskyScripts.forEach((scriptName) => {
345
+ const scriptValue = String(scripts[scriptName] || "");
346
+ const scriptExecutionIndicators = new Set(
347
+ collectLifecyclePatternIndicators(
348
+ scriptValue,
349
+ NPM_LIFECYCLE_EXECUTION_PATTERNS,
350
+ ),
351
+ );
352
+ const scriptNetworkIndicators = new Set(
353
+ collectLifecyclePatternIndicators(
354
+ scriptValue,
355
+ NPM_LIFECYCLE_NETWORK_PATTERNS,
356
+ ),
357
+ );
358
+ const scriptObfuscationIndicators = new Set(
359
+ collectLifecyclePatternIndicators(
360
+ scriptValue,
361
+ NPM_LIFECYCLE_OBFUSCATION_PATTERNS,
362
+ ),
363
+ );
364
+ extractLifecycleScriptSourceFiles(pkgJsonFile, scriptValue).forEach(
365
+ (sourceFile) => {
366
+ const astIndicators = analyzeSuspiciousJsFile(sourceFile);
367
+ astIndicators.executionIndicators.forEach((indicator) => {
368
+ scriptExecutionIndicators.add(`ast:${indicator}`);
369
+ });
370
+ astIndicators.networkIndicators.forEach((indicator) => {
371
+ scriptNetworkIndicators.add(`ast:${indicator}`);
372
+ });
373
+ astIndicators.obfuscationIndicators.forEach((indicator) => {
374
+ scriptObfuscationIndicators.add(`ast:${indicator}`);
375
+ });
376
+ },
377
+ );
378
+ if (scriptObfuscationIndicators.size) {
379
+ scriptExecutionIndicators.forEach((indicator) => {
380
+ executionIndicators.add(indicator);
381
+ });
382
+ scriptObfuscationIndicators.forEach((indicator) => {
383
+ obfuscationIndicators.add(indicator);
384
+ });
385
+ }
386
+ if (
387
+ scriptObfuscationIndicators.size &&
388
+ (scriptExecutionIndicators.size || scriptNetworkIndicators.size)
389
+ ) {
390
+ obfuscatedScripts.push(scriptName);
391
+ }
392
+ if (
393
+ scriptObfuscationIndicators.size ||
394
+ (scriptExecutionIndicators.size && scriptNetworkIndicators.size)
395
+ ) {
396
+ suspiciousScripts.push(scriptName);
397
+ }
398
+ scriptNetworkIndicators.forEach((indicator) => {
399
+ networkIndicators.add(indicator);
400
+ });
401
+ if (
402
+ scriptExecutionIndicators.size ||
403
+ scriptNetworkIndicators.size ||
404
+ scriptObfuscationIndicators.size
405
+ ) {
406
+ scriptIndicatorMap.push(
407
+ `${scriptName}:${[
408
+ ...scriptObfuscationIndicators,
409
+ ...scriptExecutionIndicators,
410
+ ...scriptNetworkIndicators,
411
+ ].join("+")}`,
412
+ );
413
+ }
414
+ });
415
+ return {
416
+ executionIndicators: Array.from(executionIndicators).sort(),
417
+ networkIndicators: Array.from(networkIndicators).sort(),
418
+ obfuscatedScripts: [...new Set(obfuscatedScripts)].sort(),
419
+ obfuscationIndicators: Array.from(obfuscationIndicators).sort(),
420
+ riskyScripts,
421
+ scriptIndicatorMap: scriptIndicatorMap.sort(),
422
+ suspiciousScripts: [...new Set(suspiciousScripts)].sort(),
423
+ };
424
+ }
425
+
426
+ /**
427
+ * Parse a package.json file into CycloneDX component objects.
428
+ *
429
+ * @param {string} pkgJsonFile Path to the package.json file.
430
+ * @param {boolean} [simple=false] When true, omit component properties and
431
+ * identity evidence and skip npm registry metadata fetching.
432
+ * @param {boolean} [securityProps=false] When true, append npm security-relevant
433
+ * properties such as lifecycle scripts, binaries, native addons, and deprecation notices.
434
+ * @returns {Promise<Array<object>>} Parsed component list, enriched with npm
435
+ * registry metadata when available and not in simple mode.
436
+ */
437
+ export async function parsePkgJson(
438
+ pkgJsonFile,
439
+ simple = false,
440
+ securityProps = false,
441
+ ) {
442
+ const pkgList = [];
443
+ if (safeExistsSync(pkgJsonFile)) {
444
+ try {
445
+ const pkgData = JSON.parse(readFileSync(pkgJsonFile, "utf8"));
446
+ const pkgIdentifier = parsePackageJsonName(pkgData.name);
447
+ let name = pkgIdentifier.fullName || pkgData.name;
448
+ if (!name && !pkgJsonFile.includes("node_modules")) {
449
+ name = basename(dirname(pkgJsonFile));
450
+ }
451
+ const group = pkgIdentifier.scope || "";
452
+ const purl = build({
453
+ type: "npm",
454
+ namespace: group || null,
455
+ name: name,
456
+ version: pkgData.version || null,
457
+ });
458
+ const author = pkgData.author;
459
+ const authorString =
460
+ author instanceof Object
461
+ ? `${author.name}${author.email ? ` <${author.email}>` : ""}${
462
+ author.url ? ` (${author.url})` : ""
463
+ }`
464
+ : author;
465
+ const apkg = {
466
+ name,
467
+ group,
468
+ version: pkgData.version,
469
+ description: pkgData.description,
470
+ purl: purl,
471
+ "bom-ref": decodeURIComponent(purl),
472
+ author: authorString,
473
+ license: pkgData.license,
474
+ };
475
+ if (pkgData.homepage) {
476
+ apkg.homepage = { url: pkgData.homepage };
477
+ }
478
+ if (pkgData.repository?.url) {
479
+ apkg.repository = { url: pkgData.repository.url };
480
+ }
481
+ if (!simple) {
482
+ apkg.properties = [
483
+ {
484
+ name: "internal:SrcFile",
485
+ value: pkgJsonFile,
486
+ },
487
+ ];
488
+ apkg.evidence = {
489
+ identity: {
490
+ field: "purl",
491
+ confidence: 0.7,
492
+ methods: [
493
+ {
494
+ technique: "manifest-analysis",
495
+ confidence: 0.7,
496
+ value: pkgJsonFile,
497
+ },
498
+ ],
499
+ },
500
+ };
501
+ }
502
+ if (securityProps) {
503
+ if (!apkg.properties) {
504
+ apkg.properties = [];
505
+ }
506
+ // Track executable binaries (potential code execution vectors)
507
+ if (pkgData.bin) {
508
+ const binValue =
509
+ typeof pkgData.bin === "object"
510
+ ? Object.keys(pkgData.bin).join(", ")
511
+ : pkgData.bin;
512
+ apkg.properties.push({
513
+ name: "cdx:npm:bin",
514
+ value: binValue,
515
+ });
516
+ apkg.properties.push({
517
+ name: "cdx:npm:has_binary",
518
+ value: "true",
519
+ });
520
+ }
521
+ // Track lifecycle scripts (preinstall, postinstall, etc. - code execution risk)
522
+ if (pkgData.scripts && Object.keys(pkgData.scripts).length) {
523
+ const scriptNames = Object.keys(pkgData.scripts).join(", ");
524
+ const lifecycleAnalysis = analyzeNpmLifecycleScripts(
525
+ pkgJsonFile,
526
+ pkgData.scripts,
527
+ );
528
+ apkg.properties.push({
529
+ name: "cdx:npm:scripts",
530
+ value: scriptNames,
531
+ });
532
+ // Highlight build-related scripts for downstream tooling consumers
533
+ const buildScriptNames = Object.keys(pkgData.scripts).filter(
534
+ (scriptName) => NPM_BUILD_SCRIPT_NAME_PATTERN.test(scriptName),
535
+ );
536
+ if (buildScriptNames.length) {
537
+ apkg.properties.push({
538
+ name: "cdx:npm:buildScripts",
539
+ value: buildScriptNames.join(", "),
540
+ });
541
+ }
542
+ // Flag high-risk scripts specifically
543
+ const riskyScripts = lifecycleAnalysis.riskyScripts;
544
+ if (riskyScripts.length) {
545
+ apkg.properties.push({
546
+ name: "cdx:npm:hasInstallScript",
547
+ value: "true",
548
+ });
549
+ apkg.properties.push({
550
+ name: "cdx:npm:risky_scripts",
551
+ value: riskyScripts.join(", "),
552
+ });
553
+ }
554
+ if (lifecycleAnalysis.suspiciousScripts.length) {
555
+ apkg.properties.push({
556
+ name: "cdx:npm:hasSuspiciousLifecycleScript",
557
+ value: "true",
558
+ });
559
+ apkg.properties.push({
560
+ name: "cdx:npm:suspiciousLifecycleScripts",
561
+ value: lifecycleAnalysis.suspiciousScripts.join(", "),
562
+ });
563
+ }
564
+ if (lifecycleAnalysis.obfuscatedScripts.length) {
565
+ apkg.properties.push({
566
+ name: "cdx:npm:hasObfuscatedLifecycleScript",
567
+ value: "true",
568
+ });
569
+ apkg.properties.push({
570
+ name: "cdx:npm:obfuscatedLifecycleScripts",
571
+ value: lifecycleAnalysis.obfuscatedScripts.join(", "),
572
+ });
573
+ }
574
+ if (lifecycleAnalysis.obfuscationIndicators.length) {
575
+ apkg.properties.push({
576
+ name: "cdx:npm:lifecycleObfuscationIndicators",
577
+ value: lifecycleAnalysis.obfuscationIndicators.join(", "),
578
+ });
579
+ }
580
+ if (lifecycleAnalysis.executionIndicators.length) {
581
+ apkg.properties.push({
582
+ name: "cdx:npm:lifecycleExecutionIndicators",
583
+ value: lifecycleAnalysis.executionIndicators.join(", "),
584
+ });
585
+ }
586
+ if (lifecycleAnalysis.networkIndicators.length) {
587
+ apkg.properties.push({
588
+ name: "cdx:npm:lifecycleNetworkIndicators",
589
+ value: lifecycleAnalysis.networkIndicators.join(", "),
590
+ });
591
+ }
592
+ if (lifecycleAnalysis.scriptIndicatorMap.length) {
593
+ apkg.properties.push({
594
+ name: "cdx:npm:lifecycleIndicatorMap",
595
+ value: lifecycleAnalysis.scriptIndicatorMap.join(" | "),
596
+ });
597
+ }
598
+ }
599
+ // Track platform/architecture constraints
600
+ if (pkgData.cpu && Array.isArray(pkgData.cpu) && pkgData.cpu.length) {
601
+ apkg.properties.push({
602
+ name: "cdx:npm:cpu",
603
+ value: pkgData.cpu.join(", "),
604
+ });
605
+ }
606
+ if (pkgData.os && Array.isArray(pkgData.os) && pkgData.os.length) {
607
+ apkg.properties.push({
608
+ name: "cdx:npm:os",
609
+ value: pkgData.os.join(", "),
610
+ });
611
+ }
612
+ if (
613
+ pkgData.libc &&
614
+ Array.isArray(pkgData.libc) &&
615
+ pkgData.libc.length
616
+ ) {
617
+ apkg.properties.push({
618
+ name: "cdx:npm:libc",
619
+ value: pkgData.libc.join(", "),
620
+ });
621
+ }
622
+ // Track deprecation notices
623
+ if (pkgData.deprecated) {
624
+ apkg.properties.push({
625
+ name: "cdx:npm:deprecation_notice",
626
+ value: pkgData.deprecated,
627
+ });
628
+ }
629
+ // Track if package uses node-gyp (native C/C++ addons = higher risk)
630
+ if (
631
+ pkgData.gypfile === true ||
632
+ pkgData.files?.some((f) => f.endsWith(".gyp") || f.endsWith(".gypi"))
633
+ ) {
634
+ apkg.properties.push({
635
+ name: "cdx:npm:gypfile",
636
+ value: "true",
637
+ });
638
+ apkg.properties.push({
639
+ name: "cdx:npm:native_addon",
640
+ value: "true",
641
+ });
642
+ const nativeDeps = [
643
+ "nan",
644
+ "node-addon-api",
645
+ "bindings",
646
+ "node-gyp-build",
647
+ ];
648
+ const foundNativeDeps = Object.keys(
649
+ pkgData.dependencies || {},
650
+ ).filter((dep) => nativeDeps.includes(dep));
651
+ if (foundNativeDeps.length) {
652
+ apkg.properties.push({
653
+ name: "cdx:npm:native_deps",
654
+ value: foundNativeDeps.join(", "),
655
+ });
656
+ }
657
+ }
658
+ }
659
+ pkgList.push(apkg);
660
+ } catch (_err) {
661
+ // continue regardless of error
662
+ }
663
+ }
664
+ if (!simple && shouldFetchPackageMetadata() && pkgList?.length) {
665
+ if (DEBUG_MODE) {
666
+ console.log(
667
+ `About to fetch npm registry metadata for ${pkgList.length} packages in parsePkgJson`,
668
+ );
669
+ }
670
+ return await getNpmMetadata(pkgList);
671
+ }
672
+ return pkgList;
673
+ }
674
+
675
+ // The four manifest fields that npm packageExtensions and .npm-extension
676
+ // transformManifest may add or correct. Mirrors EXTENSION_FIELDS in the
677
+ // vendored arborist's package-extensions.js and the upstream npm-extension.js
678
+ // at the pinned commit, so overlays and provenance stay in lockstep with npm.
679
+ const EXTENSION_FIELDS = [
680
+ "dependencies",
681
+ "optionalDependencies",
682
+ "peerDependencies",
683
+ "peerDependenciesMeta",
684
+ ];
685
+
686
+ /**
687
+ * Collect the dependency names that an extension provenance object says changed,
688
+ * sorted and comma-separated per the component-level list convention.
689
+ *
690
+ * @param {object|null|undefined} applied Provenance from arborist
691
+ * ({ selector|extensionPoint, dependencies?, optionalDependencies?, peerDependencies?, peerDependenciesMeta? })
692
+ * @returns {string|null} Sorted comma-separated names, or null when nothing changed.
693
+ */
694
+ function formatExtensionAppliedList(applied) {
695
+ if (!applied || typeof applied !== "object") {
696
+ return null;
697
+ }
698
+ const names = new Set();
699
+ for (const field of EXTENSION_FIELDS) {
700
+ const changed = applied[field];
701
+ if (Array.isArray(changed)) {
702
+ for (const name of changed) {
703
+ if (typeof name === "string" && name) {
704
+ names.add(name);
705
+ }
706
+ }
707
+ }
708
+ }
709
+ if (!names.size) {
710
+ return null;
711
+ }
712
+ return [...names].sort().join(", ");
713
+ }
714
+
715
+ /**
716
+ * Hash a root `.npm-extension` file exactly as npm does: ssri sha512 over
717
+ * `npm-extension:v1:<format>\n` followed by the raw file bytes. Mirrors
718
+ * `hashFile` in the vendored arborist's upstream npm-extension.js so the value
719
+ * is comparable to what npm writes into the lockfile as `npmExtensionHash`.
720
+ *
721
+ * @param {string} format Either "mjs" or "cjs"
722
+ * @param {Buffer} bytes Raw file contents
723
+ * @returns {string} ssri integrity string
724
+ */
725
+ export function hashNpmExtensionFile(format, bytes) {
726
+ return ssri
727
+ .fromData(
728
+ Buffer.concat([Buffer.from(`npm-extension:v1:${format}\n`), bytes]),
729
+ { algorithms: ["sha512"] },
730
+ )
731
+ .toString();
732
+ }
733
+
734
+ /**
735
+ * Detect a root-owned `.npm-extension.mjs` or `.npm-extension.cjs` file and
736
+ * return its format, path, and npm-comparable hash. Returns null when no file
737
+ * is present. A non-root workspace file is never consulted, matching upstream.
738
+ *
739
+ * @param {string} rootPath Real path of the project root (tree.realpath)
740
+ * @returns {{format: string, path: string, hash: string, duplicate: boolean}|null}
741
+ */
742
+ export function detectRootNpmExtension(rootPath) {
743
+ const candidates = [
744
+ { ext: "mjs", file: ".npm-extension.mjs" },
745
+ { ext: "cjs", file: ".npm-extension.cjs" },
746
+ ];
747
+ let found = null;
748
+ let duplicate = false;
749
+ for (const candidate of candidates) {
750
+ const filePath = join(rootPath, candidate.file);
751
+ if (safeExistsSync(filePath)) {
752
+ if (found) {
753
+ duplicate = true;
754
+ break;
755
+ }
756
+ found = { ext: candidate.ext, file: filePath };
757
+ }
758
+ }
759
+ if (!found) {
760
+ return null;
761
+ }
762
+ const bytes = readFileSync(found.file);
763
+ return {
764
+ format: found.ext,
765
+ path: found.file,
766
+ hash: hashNpmExtensionFile(found.ext, bytes),
767
+ duplicate,
768
+ };
769
+ }
770
+
771
+ /**
772
+ * Overlay the four post-extension dependency fields from the lockfile onto each
773
+ * node whose lockfile entry records an `.npm-extension` repair. This reproduces
774
+ * npm's repaired edges exactly without executing any project-supplied code.
775
+ * The lockfile entries hold the post-extension manifest because npm writes
776
+ * `node.package` — already repaired at that point — into the lockfile.
777
+ *
778
+ * @param {object} tree The arborist root node from `loadActual`
779
+ */
780
+ function applyNpmExtensionOverlay(tree) {
781
+ const sw = tree.meta;
782
+ const packages = sw?.data?.packages;
783
+ if (!packages) {
784
+ return;
785
+ }
786
+ for (const node of tree.inventory.values()) {
787
+ if (node.isProjectRoot) {
788
+ continue;
789
+ }
790
+ const loc = node.location;
791
+ if (!loc) {
792
+ continue;
793
+ }
794
+ const swEntry = packages[loc];
795
+ if (!swEntry?.npmExtensionApplied) {
796
+ continue;
797
+ }
798
+ const merged = { ...node.package };
799
+ for (const field of EXTENSION_FIELDS) {
800
+ if (swEntry[field] !== undefined) {
801
+ merged[field] = swEntry[field];
802
+ } else {
803
+ delete merged[field];
804
+ }
805
+ }
806
+ node.package = merged;
807
+ node.npmExtensionApplied = swEntry.npmExtensionApplied;
808
+ }
809
+ }
810
+
811
+ /**
812
+ * Undo packageExtensions repairs applied by the vendored arborist during
813
+ * loadActual, so the BOM reflects manifests as published. Only the four fields
814
+ * an extension can touch are restored from the node's on-disk package.json;
815
+ * every other field arborist derived while building the tree is left alone.
816
+ * Reassigning `package` rebuilds the node's edges from the restored manifest.
817
+ *
818
+ * @param {object} tree The arborist root node from `loadActual`
819
+ */
820
+ function undoPackageExtensions(tree) {
821
+ for (const node of tree.inventory.values()) {
822
+ if (!node.packageExtensionsApplied) {
823
+ continue;
824
+ }
825
+ let published;
826
+ try {
827
+ published = JSON.parse(
828
+ readFileSync(join(node.path, "package.json"), "utf8"),
829
+ );
830
+ } catch {
831
+ // Without a readable manifest there is nothing to restore from, so the
832
+ // repaired one stands and the provenance property keeps saying so.
833
+ continue;
834
+ }
835
+ const restored = { ...node.package };
836
+ for (const field of EXTENSION_FIELDS) {
837
+ if (published[field] === undefined) {
838
+ delete restored[field];
839
+ } else {
840
+ restored[field] = published[field];
841
+ }
842
+ }
843
+ node.package = restored;
844
+ node.packageExtensionsApplied = null;
845
+ }
846
+ }
847
+
848
+ /**
849
+ * Parse nodejs package lock file
850
+ *
851
+ * @param {string} pkgLockFile package-lock.json file
852
+ * @param {object} options Command line options
853
+ */
854
+ export async function parsePkgLock(pkgLockFile, options = {}) {
855
+ let pkgList = [];
856
+ let dependenciesList = [];
857
+ if (!options) {
858
+ options = {};
859
+ }
860
+ const npmrcConfig = loadNpmrcConfig(
861
+ options.projectRoot || dirname(pkgLockFile),
862
+ );
863
+ const pkgSpecVersionCache = {};
864
+ if (!safeExistsSync(pkgLockFile)) {
865
+ return {
866
+ pkgList,
867
+ dependenciesList,
868
+ };
869
+ }
870
+
871
+ const parseArboristNode = (
872
+ node,
873
+ rootNode,
874
+ parentRef = null,
875
+ visited = new Set(),
876
+ pkgSpecVersionCache = {},
877
+ options = {},
878
+ ) => {
879
+ if (visited.has(node)) {
880
+ return { pkgList: [], dependenciesList: [] };
881
+ }
882
+ visited.add(node);
883
+ let pkgList = [];
884
+ let dependenciesList = [];
885
+
886
+ // Create the package entry
887
+ const srcFilePath = node.path.includes(`${_sep}node_modules`)
888
+ ? node.path.split(`${_sep}node_modules`)[0]
889
+ : node.path;
890
+ const isDevelopmentNode = node.dev === true || node.devOptional === true;
891
+ const scope =
892
+ isDevelopmentNode || node.optional === true ? "optional" : undefined;
893
+ const integrity = node.integrity ? node.integrity : undefined;
894
+ const { nodePackage, diskPkg, packageJsonPath } = hydrateNpmNodePackage(
895
+ node,
896
+ options,
897
+ );
898
+
899
+ let pkg;
900
+ let purlString;
901
+ const author = nodePackage.author;
902
+ const authorString =
903
+ author instanceof Object
904
+ ? `${author.name}${author.email ? ` <${author.email}>` : ""}${
905
+ author.url ? ` (${author.url})` : ""
906
+ }`
907
+ : author;
908
+ if (node === rootNode) {
909
+ const projectGroup = options.projectGroup;
910
+ const projectName =
911
+ "project-name" in options ? options.projectName : node.packageName;
912
+ const projectVersion = options.projectVersion || node.version;
913
+ // A package.json need not declare a version, and npm installs without
914
+ // one, so the root of a lockfile can reach here with none. A purl carries
915
+ // no version rather than an empty one.
916
+ purlString = Purl.parse(
917
+ `pkg:npm/${projectGroup ? `${encodeURIComponent(projectGroup).replace(/%2F/g, "/")}/` : ""}${encodeURIComponent(projectName).replace(/%2F/g, "/")}${projectVersion ? `@${projectVersion}` : ""}`,
918
+ )
919
+ .toString()
920
+ .replace(/%2F/g, "/");
921
+ pkg = {
922
+ author: authorString,
923
+ group: options.projectGroup || "",
924
+ name:
925
+ "project-name" in options ? options.projectName : node.packageName,
926
+ version: options.projectVersion || node.version,
927
+ type: "application",
928
+ purl: purlString,
929
+ "bom-ref": decodeURIComponent(purlString),
930
+ properties: [],
931
+ };
932
+ if (lockfilePackageExtensionsHash) {
933
+ pkg.properties.push({
934
+ name: "cdx:npm:packageExtensionsHash",
935
+ value: lockfilePackageExtensionsHash,
936
+ });
937
+ }
938
+ if (extensionFile) {
939
+ pkg.properties.push({
940
+ name: "cdx:npm:extensionHash",
941
+ value: extensionFile.hash,
942
+ });
943
+ pkg.properties.push({
944
+ name: "cdx:npm:extensionFormat",
945
+ value: extensionFile.format,
946
+ });
947
+ }
948
+ if (extensionAppliedStatus !== null) {
949
+ pkg.properties.push({
950
+ name: "cdx:npm:extensionApplied",
951
+ value: extensionAppliedStatus,
952
+ });
953
+ }
954
+ if (noPackageExtensions && isDeep) {
955
+ pkg.properties.push({
956
+ name: "cdx:npm:packageExtensionsDisabled",
957
+ value: "true",
958
+ });
959
+ }
960
+ } else {
961
+ let namespace = "";
962
+ let name = node.packageName;
963
+ if (node.packageName.startsWith("@")) {
964
+ const slashIndex = node.packageName.indexOf("/");
965
+ if (slashIndex > 0) {
966
+ namespace = node.packageName.substring(0, slashIndex);
967
+ name = node.packageName.substring(slashIndex + 1);
968
+ }
969
+ }
970
+ let qualifiers = null;
971
+ let extRefs = [];
972
+ const isGitDep =
973
+ node.resolved &&
974
+ (node.resolved.startsWith("git+") ||
975
+ node.resolved.startsWith("git://") ||
976
+ node.resolved.startsWith("ssh://") ||
977
+ node.resolved.startsWith("git@"));
978
+ if (isGitDep) {
979
+ qualifiers = buildNpmGitPurlQualifiers(
980
+ node.resolved,
981
+ namespace,
982
+ npmrcConfig,
983
+ );
984
+ const gitIntakeRefs = buildNpmGitDistributionIntakeRefs(
985
+ namespace,
986
+ name,
987
+ node.version,
988
+ npmrcConfig,
989
+ );
990
+ if (gitIntakeRefs) {
991
+ extRefs = extRefs.concat(gitIntakeRefs);
992
+ }
993
+ }
994
+ purlString = build({
995
+ type: "npm",
996
+ namespace: namespace || null,
997
+ name: name,
998
+ version: node.version || null,
999
+ qualifiers: qualifiers || null,
1000
+ });
1001
+ const pkgLockFile = join(
1002
+ srcFilePath.replace("/", _sep),
1003
+ "package-lock.json",
1004
+ );
1005
+ pkg = {
1006
+ group: namespace,
1007
+ name: name,
1008
+ version: node.version,
1009
+ author: authorString,
1010
+ scope: scope,
1011
+ _integrity: integrity,
1012
+ externalReferences: extRefs,
1013
+ properties: [
1014
+ {
1015
+ name: "internal:SrcFile",
1016
+ value: pkgLockFile,
1017
+ },
1018
+ ],
1019
+ evidence: {
1020
+ identity: {
1021
+ field: "purl",
1022
+ confidence: 1,
1023
+ methods: [
1024
+ {
1025
+ technique: "manifest-analysis",
1026
+ confidence: 1,
1027
+ value: pkgLockFile,
1028
+ },
1029
+ ],
1030
+ },
1031
+ },
1032
+ type: parentRef ? "npm" : "application",
1033
+ purl: purlString,
1034
+ "bom-ref": decodeURIComponent(purlString),
1035
+ };
1036
+ if (isDevelopmentNode) {
1037
+ setNpmDevelopmentProperty(pkg);
1038
+ }
1039
+ if (node.optional === true) {
1040
+ setNpmOptionalProperty(pkg);
1041
+ }
1042
+ if (node.peer === true) {
1043
+ setNpmPeerProperty(pkg);
1044
+ }
1045
+ if (node.resolved) {
1046
+ if (node.resolved.startsWith("file:")) {
1047
+ pkg.properties.push({
1048
+ name: "cdx:npm:resolvedPath",
1049
+ value: node.realpath
1050
+ ? relative(dirname(pkgLockFile), node.realpath)
1051
+ : relative(
1052
+ dirname(pkgLockFile),
1053
+ resolve(node.resolved.replace("file:", "")),
1054
+ ),
1055
+ });
1056
+ } else {
1057
+ pkg.properties.push({
1058
+ name: "internal:ResolvedUrl",
1059
+ value: node.resolved,
1060
+ });
1061
+ pkg.externalReferences.push({
1062
+ type: "distribution",
1063
+ url: node.resolved,
1064
+ });
1065
+ }
1066
+ }
1067
+ if (node.location) {
1068
+ pkg.properties.push({
1069
+ name: "internal:LocalNodeModulesPath",
1070
+ value: node.location,
1071
+ });
1072
+ }
1073
+ if (node?.installLinks) {
1074
+ pkg.properties.push({
1075
+ name: "cdx:npm:installLinks",
1076
+ value: "true",
1077
+ });
1078
+ }
1079
+ if (node?.binPaths?.length) {
1080
+ pkg.properties.push({
1081
+ name: "cdx:npm:binPaths",
1082
+ value: node.binPaths.join(", "),
1083
+ });
1084
+ }
1085
+ if (nodePackage?.bin) {
1086
+ const binValue =
1087
+ typeof nodePackage.bin === "object"
1088
+ ? Object.keys(nodePackage.bin).join(", ")
1089
+ : nodePackage.bin;
1090
+ pkg.properties.push({
1091
+ name: "cdx:npm:bin",
1092
+ value: binValue,
1093
+ });
1094
+ pkg.properties.push({
1095
+ name: "cdx:npm:has_binary",
1096
+ value: "true",
1097
+ });
1098
+ }
1099
+ if (node?.hasInstallScript) {
1100
+ pkg.properties.push({
1101
+ name: "cdx:npm:hasInstallScript",
1102
+ value: "true",
1103
+ });
1104
+ }
1105
+ if (node?.isLink) {
1106
+ pkg.properties.push({
1107
+ name: "cdx:npm:isLink",
1108
+ value: "true",
1109
+ });
1110
+ }
1111
+ const npmManifestSources = collectNpmManifestSources(node);
1112
+ if (npmManifestSources.length) {
1113
+ addComponentProperty(
1114
+ pkg,
1115
+ "cdx:npm:manifestSourceType",
1116
+ npmManifestSources
1117
+ .map((manifestSource) => manifestSource.type)
1118
+ .join(","),
1119
+ );
1120
+ addComponentProperty(
1121
+ pkg,
1122
+ "cdx:npm:manifestSource",
1123
+ npmManifestSources
1124
+ .map((manifestSource) => manifestSource.value)
1125
+ .join(","),
1126
+ );
1127
+ }
1128
+ // This getter method could fail with errors at times.
1129
+ // Example Error: Invalid tag name "^>=6.0.0" of package "^>=6.0.0": Tags may not have any characters that encodeURIComponent encodes.
1130
+ try {
1131
+ if (!node?.isRegistryDependency) {
1132
+ pkg.properties.push({
1133
+ name: "cdx:npm:isRegistryDependency",
1134
+ value: "false",
1135
+ });
1136
+ }
1137
+ } catch (_err) {
1138
+ // ignore
1139
+ }
1140
+ if (node?.isWorkspace) {
1141
+ pkg.properties.push({
1142
+ name: "cdx:npm:isWorkspace",
1143
+ value: "true",
1144
+ });
1145
+ }
1146
+ // Detect version spoofing by comparing the version in the lockfile with the version in package.json
1147
+ if (packageJsonPath && safeExistsSync(packageJsonPath)) {
1148
+ try {
1149
+ const onDiskPkg =
1150
+ diskPkg || JSON.parse(readFileSync(packageJsonPath, "utf8"));
1151
+ if (!onDiskPkg.name || onDiskPkg.name !== node.packageName) {
1152
+ console.warn(
1153
+ `\x1b[1;35mWARNING: Package name spoofing detected for ${node.packageName}! Lockfile says ${node.packageName}, but disk says ${onDiskPkg.name}.\x1b[0m`,
1154
+ );
1155
+ if (onDiskPkg.name) {
1156
+ pkg.properties.push({
1157
+ name: "cdx:npm:nameMismatchError",
1158
+ value: `${onDiskPkg.name} used instead of ${node.packageName}`,
1159
+ });
1160
+ }
1161
+ }
1162
+ if (!onDiskPkg.version || onDiskPkg.version !== node.version) {
1163
+ console.warn(
1164
+ `\x1b[1;35mWARNING: Package version spoofing detected for ${node.packageName}! Lockfile says ${node.version}, but disk says ${onDiskPkg.version}.\x1b[0m`,
1165
+ );
1166
+ if (onDiskPkg.version) {
1167
+ pkg.properties.push({
1168
+ name: "cdx:npm:versionMismatchError",
1169
+ value: `${onDiskPkg.version} used instead of ${node.version}`,
1170
+ });
1171
+ }
1172
+ }
1173
+ } catch (_err) {
1174
+ // ignore
1175
+ }
1176
+ }
1177
+ if (node?.inBundle) {
1178
+ pkg.properties.push({
1179
+ name: "cdx:npm:inBundle",
1180
+ value: "true",
1181
+ });
1182
+ }
1183
+ if (node?.inDepBundle) {
1184
+ pkg.properties.push({
1185
+ name: "cdx:npm:inDepBundle",
1186
+ value: "true",
1187
+ });
1188
+ }
1189
+ const packageExtensionsAppliedList = formatExtensionAppliedList(
1190
+ node.packageExtensionsApplied,
1191
+ );
1192
+ if (packageExtensionsAppliedList) {
1193
+ pkg.properties.push({
1194
+ name: "cdx:npm:packageExtensionsApplied",
1195
+ value: packageExtensionsAppliedList,
1196
+ });
1197
+ }
1198
+ const npmExtensionAppliedList = formatExtensionAppliedList(
1199
+ node.npmExtensionApplied,
1200
+ );
1201
+ if (npmExtensionAppliedList) {
1202
+ pkg.properties.push({
1203
+ name: "cdx:npm:extensionFieldsApplied",
1204
+ value: npmExtensionAppliedList,
1205
+ });
1206
+ }
1207
+ if (nodePackage?.repository?.url) {
1208
+ pkg.externalReferences.push({
1209
+ type: "vcs",
1210
+ url: nodePackage.repository.url,
1211
+ });
1212
+ }
1213
+ if (nodePackage?.bugs?.url) {
1214
+ pkg.externalReferences.push({
1215
+ type: "issue-tracker",
1216
+ url: nodePackage.bugs.url,
1217
+ });
1218
+ }
1219
+ if (nodePackage?.keywords?.length) {
1220
+ pkg.tags = Array.isArray(nodePackage.keywords)
1221
+ ? nodePackage.keywords.sort()
1222
+ : nodePackage.keywords.split(",");
1223
+ }
1224
+ if (nodePackage?.description) {
1225
+ pkg.description = nodePackage.description;
1226
+ }
1227
+ }
1228
+ if (nodePackage?.license) {
1229
+ // License will be overridden if shouldFetchLicense() is enabled
1230
+ pkg.license = nodePackage.license;
1231
+ }
1232
+ const deprecatedMessage = nodePackage?.deprecated;
1233
+ if (deprecatedMessage) {
1234
+ pkg.properties.push({
1235
+ name: "cdx:npm:deprecated",
1236
+ value: deprecatedMessage,
1237
+ });
1238
+ }
1239
+ if (!pkg.properties.length) {
1240
+ delete pkg.properties;
1241
+ }
1242
+ pkgList.push(pkg);
1243
+ // retrieve workspace node pkglists
1244
+ const workspaceDependsOn = [];
1245
+ if (node.fsChildren && node.fsChildren.size > 0) {
1246
+ for (const workspaceNode of node.fsChildren) {
1247
+ const {
1248
+ pkgList: childPkgList,
1249
+ dependenciesList: childDependenciesList,
1250
+ } = parseArboristNode(
1251
+ workspaceNode,
1252
+ rootNode,
1253
+ purlString,
1254
+ visited,
1255
+ pkgSpecVersionCache,
1256
+ options,
1257
+ );
1258
+ pkgList = pkgList.concat(childPkgList);
1259
+ dependenciesList = dependenciesList.concat(childDependenciesList);
1260
+ let depWorkspacePurlString = decodeURIComponent(
1261
+ new Purl({
1262
+ type: "npm",
1263
+ namespace: "" || null,
1264
+ name: workspaceNode.name,
1265
+ version: workspaceNode.version || null,
1266
+ })
1267
+ .toString()
1268
+ .replace(/%2F/g, "/"),
1269
+ );
1270
+ let purlStringFromPkgid;
1271
+ if (workspaceNode.pkgid) {
1272
+ purlStringFromPkgid = `pkg:npm/${workspaceNode.pkgid.replace(`${workspaceNode.name}@npm:`, "")}`;
1273
+ }
1274
+ if (
1275
+ purlStringFromPkgid &&
1276
+ purlStringFromPkgid !== depWorkspacePurlString
1277
+ ) {
1278
+ if (DEBUG_MODE) {
1279
+ console.log(
1280
+ `Internal warning: Got two different refs for this workspace node: ${depWorkspacePurlString} and ${purlStringFromPkgid}. Assuming the bom-ref as ${purlStringFromPkgid} based on pkgid.`,
1281
+ );
1282
+ }
1283
+ depWorkspacePurlString = purlStringFromPkgid;
1284
+ }
1285
+ if (decodeURIComponent(purlString) !== depWorkspacePurlString) {
1286
+ workspaceDependsOn.push(depWorkspacePurlString);
1287
+ }
1288
+ }
1289
+ }
1290
+
1291
+ // this handles the case when a node has ["dependencies"] key in a package-lock.json
1292
+ // for a node. We exclude the root node because it's already been handled
1293
+ // If the node has "requires", we don't have to track the "dependencies"
1294
+ const childrenDependsOn = [];
1295
+ if (node !== rootNode && !node.edgesOut.size) {
1296
+ for (const child of node.children) {
1297
+ const childNode = child[1];
1298
+ const {
1299
+ pkgList: childPkgList,
1300
+ dependenciesList: childDependenciesList,
1301
+ } = parseArboristNode(
1302
+ childNode,
1303
+ rootNode,
1304
+ decodeURIComponent(purlString),
1305
+ visited,
1306
+ pkgSpecVersionCache,
1307
+ options,
1308
+ );
1309
+ pkgList = pkgList.concat(childPkgList);
1310
+ dependenciesList = dependenciesList.concat(childDependenciesList);
1311
+ const depChildString = decodeURIComponent(
1312
+ new Purl({
1313
+ type: "npm",
1314
+ namespace: "" || null,
1315
+ name: childNode.name,
1316
+ version: childNode.version || null,
1317
+ })
1318
+ .toString()
1319
+ .replace(/%2F/g, "/"),
1320
+ );
1321
+ if (decodeURIComponent(purlString) !== depChildString) {
1322
+ childrenDependsOn.push(depChildString);
1323
+ }
1324
+ }
1325
+ }
1326
+
1327
+ // this handles the case when a node has a ["requires"] key
1328
+ const pkgDependsOn = [];
1329
+ for (const edge of node.edgesOut.values()) {
1330
+ let targetVersion;
1331
+ let targetName;
1332
+ let foundMatch = false;
1333
+ // This cache is required to help us down the line.
1334
+ if (edge?.to?.version && edge?.spec) {
1335
+ pkgSpecVersionCache[`${edge.name}-${edge.spec}`] = edge.to.version;
1336
+ }
1337
+ // if the edge doesn't have an integrity, it's likely a peer dependency
1338
+ // which isn't installed
1339
+ // Bug #795. At times, npm loses the integrity node completely and such packages are getting missed out
1340
+ // To keep things safe, we include these packages.
1341
+ let edgeToIntegrityOrLocation = edge.to ? edge.to.integrity : undefined;
1342
+ // Fallback to location based lookups when integrity is missing
1343
+ if (!edgeToIntegrityOrLocation && edge.to && edge.to.location) {
1344
+ edgeToIntegrityOrLocation = edge.to.location;
1345
+ }
1346
+ if (!edgeToIntegrityOrLocation) {
1347
+ // This hack is required to fix the package name
1348
+ targetName = edge.name.replace(/-cjs$/, "");
1349
+ foundMatch = false;
1350
+ } else {
1351
+ // the edges don't actually contain a version, so we need to search the root node
1352
+ // children to find the correct version. we check the node children first, then
1353
+ // we check the root node children
1354
+ for (const child of node.children) {
1355
+ if (edgeToIntegrityOrLocation) {
1356
+ if (
1357
+ child[1].integrity === edgeToIntegrityOrLocation ||
1358
+ child[1].location === edgeToIntegrityOrLocation
1359
+ ) {
1360
+ targetName = child[0].replace(/node_modules\//g, "");
1361
+ // The package name could be different from the targetName retrieved
1362
+ // Eg: "string-width-cjs": "npm:string-width@^4.2.0",
1363
+ if (child[1].packageName && child[1].packageName !== targetName) {
1364
+ targetName = child[1].packageName;
1365
+ }
1366
+ targetVersion = child[1].version;
1367
+ foundMatch = true;
1368
+ break;
1369
+ }
1370
+ }
1371
+ }
1372
+ }
1373
+ if (!foundMatch) {
1374
+ for (const child of rootNode.children) {
1375
+ if (
1376
+ edgeToIntegrityOrLocation &&
1377
+ (child[1].integrity === edgeToIntegrityOrLocation ||
1378
+ child[1].location === edgeToIntegrityOrLocation)
1379
+ ) {
1380
+ targetName = child[0].replace(/node_modules\//g, "");
1381
+ targetVersion = child[1].version;
1382
+ // The package name could be different from the targetName retrieved
1383
+ // "string-width-cjs": "npm:string-width@^4.2.0",
1384
+ if (child[1].packageName && child[1].packageName !== targetName) {
1385
+ targetName = child[1].packageName;
1386
+ }
1387
+ break;
1388
+ }
1389
+ }
1390
+ if (!targetVersion || !targetName) {
1391
+ if (pkgSpecVersionCache[`${edge.name}-${edge.spec}`]) {
1392
+ targetVersion = pkgSpecVersionCache[`${edge.name}-${edge.spec}`];
1393
+ targetName = edge.name.replace(/-cjs$/, "");
1394
+ }
1395
+ }
1396
+ }
1397
+
1398
+ // if we can't find the version of the edge, continue
1399
+ // it may be an optional peer dependency
1400
+ if (!targetVersion || !targetName) {
1401
+ if (
1402
+ DEBUG_MODE &&
1403
+ !options.deep &&
1404
+ !["optional", "peer", "peerOptional"].includes(edge?.type)
1405
+ ) {
1406
+ if (!targetVersion) {
1407
+ console.log(
1408
+ `Unable to determine the version for the dependency ${edge.name} from the path ${edge?.from?.path}. This is likely an edge case that is not handled.`,
1409
+ edge,
1410
+ );
1411
+ } else if (!targetName) {
1412
+ console.log(
1413
+ `Unable to determine the name for the dependency from the edge from the path ${edge?.from?.path}. This is likely an edge case that is not handled.`,
1414
+ edge,
1415
+ );
1416
+ }
1417
+ }
1418
+ // juice-shop
1419
+ // Lock files created with --legacy-peer-deps will have certain peer dependencies missing
1420
+ // This flags any non-missing peers
1421
+ if (DEBUG_MODE && edge?.type === "peer" && edge?.error !== "MISSING") {
1422
+ console.log(
1423
+ `Unable to determine the version for the dependency ${edge.name} from the path ${edge?.from?.path}. This is likely an edge case that is not handled.`,
1424
+ edge,
1425
+ );
1426
+ }
1427
+ continue;
1428
+ }
1429
+ const depPurlString = decodeURIComponent(
1430
+ npmPurl(targetName, targetVersion),
1431
+ );
1432
+ if (decodeURIComponent(purlString) !== depPurlString) {
1433
+ pkgDependsOn.push(depPurlString);
1434
+ }
1435
+ if (edge.to == null) {
1436
+ continue;
1437
+ }
1438
+ const { pkgList: childPkgList, dependenciesList: childDependenciesList } =
1439
+ parseArboristNode(
1440
+ edge.to,
1441
+ rootNode,
1442
+ decodeURIComponent(purlString),
1443
+ visited,
1444
+ pkgSpecVersionCache,
1445
+ options,
1446
+ );
1447
+ pkgList = pkgList.concat(childPkgList);
1448
+ dependenciesList = dependenciesList.concat(childDependenciesList);
1449
+ }
1450
+ dependenciesList.push({
1451
+ ref: decodeURIComponent(purlString),
1452
+ dependsOn: [
1453
+ ...new Set(
1454
+ workspaceDependsOn.concat(childrenDependsOn).concat(pkgDependsOn),
1455
+ ),
1456
+ ].sort(),
1457
+ });
1458
+
1459
+ return { pkgList, dependenciesList };
1460
+ };
1461
+
1462
+ let arb = new Arborist({
1463
+ path: path.dirname(pkgLockFile),
1464
+ // legacyPeerDeps=false enables npm >v3 package dependency resolution
1465
+ legacyPeerDeps: false,
1466
+ });
1467
+ let tree;
1468
+ try {
1469
+ const rootNodeModulesDir = join(path.dirname(pkgLockFile), "node_modules");
1470
+ if (safeExistsSync(rootNodeModulesDir)) {
1471
+ if (options.deep) {
1472
+ console.log(
1473
+ `Constructing the actual dependency hierarchy from ${rootNodeModulesDir}.`,
1474
+ );
1475
+ tree = await arb.loadActual();
1476
+ } else {
1477
+ if (DEBUG_MODE) {
1478
+ console.log(
1479
+ "Constructing virtual dependency tree based on the lock file. Pass --deep argument to construct the actual dependency tree from disk.",
1480
+ );
1481
+ }
1482
+ tree = await arb.loadVirtual();
1483
+ }
1484
+ } else {
1485
+ tree = await arb.loadVirtual();
1486
+ }
1487
+ } catch (e) {
1488
+ console.log(e);
1489
+ console.log(
1490
+ `Unable to parse ${pkgLockFile} without legacy peer dependencies. Retrying ...`,
1491
+ );
1492
+ if (DEBUG_MODE) {
1493
+ console.log(e);
1494
+ }
1495
+ try {
1496
+ arb = new Arborist({
1497
+ path: path.dirname(pkgLockFile),
1498
+ legacyPeerDeps: true,
1499
+ });
1500
+ tree = await arb.loadVirtual();
1501
+ } catch (e) {
1502
+ console.log(e);
1503
+ console.log(
1504
+ `Unable to parse ${pkgLockFile} in legacy and non-legacy mode. The resulting SBOM would be incomplete.`,
1505
+ );
1506
+ if (DEBUG_MODE) {
1507
+ console.log(e);
1508
+ }
1509
+ return { pkgList, dependenciesList };
1510
+ }
1511
+ }
1512
+ if (!tree) {
1513
+ return { pkgList, dependenciesList };
1514
+ }
1515
+
1516
+ const isDeep = Boolean(options.deep);
1517
+ const noPackageExtensions = options.packageExtensions === false;
1518
+ const lockfilePackageExtensionsHash =
1519
+ tree.meta?.packageExtensionsHash || null;
1520
+ const lockfileNpmExtensionHash = tree.meta?.npmExtensionHash || null;
1521
+
1522
+ let extensionFile = null;
1523
+ try {
1524
+ extensionFile = detectRootNpmExtension(tree.realpath);
1525
+ } catch (e) {
1526
+ if (DEBUG_MODE) {
1527
+ console.log(`Failed to inspect .npm-extension: ${e.message}`);
1528
+ }
1529
+ }
1530
+
1531
+ // Whether the repairs the root .npm-extension defines are reflected in this
1532
+ // BOM. null when no extension file is present, so an absent property always
1533
+ // means "no extension", never "we did not look".
1534
+ //
1535
+ // The lockfile is the only source for those repairs, since cdxgen never
1536
+ // executes the file. A lockfile written against the same file bytes carries
1537
+ // the repaired dependency fields, whichever load path built the tree:
1538
+ // loadVirtual reads them directly, loadActual overlays them onto the
1539
+ // manifests found on disk. So the hash comparison, not the load path, decides
1540
+ // the status.
1541
+ let extensionAppliedStatus = null;
1542
+ if (extensionFile) {
1543
+ if (extensionFile.duplicate) {
1544
+ console.warn(
1545
+ "\x1b[1;33mWARNING: Found both .npm-extension.mjs and .npm-extension.cjs at the project root. Using the .mjs file.\x1b[0m",
1546
+ );
1547
+ }
1548
+ if (!lockfileNpmExtensionHash) {
1549
+ extensionAppliedStatus = "false";
1550
+ console.warn(
1551
+ "\x1b[1;33mWARNING: A root .npm-extension file is present but the lockfile does not record one, and cdxgen does not execute project-supplied code. " +
1552
+ "The dependency graph may be incomplete because the manifest repairs the extension defines are not reflected.\x1b[0m",
1553
+ );
1554
+ } else if (extensionFile.hash !== lockfileNpmExtensionHash) {
1555
+ extensionAppliedStatus = "unverified";
1556
+ console.warn(
1557
+ "\x1b[1;33mWARNING: The root .npm-extension file has changed since the lockfile was written. " +
1558
+ "The dependency graph is reported as unverified because the on-disk hash does not match the lockfile.\x1b[0m",
1559
+ );
1560
+ } else {
1561
+ if (isDeep) {
1562
+ applyNpmExtensionOverlay(tree);
1563
+ }
1564
+ extensionAppliedStatus = "true";
1565
+ }
1566
+ }
1567
+
1568
+ if (noPackageExtensions && isDeep) {
1569
+ undoPackageExtensions(tree);
1570
+ }
1571
+
1572
+ ({ pkgList, dependenciesList } = parseArboristNode(
1573
+ tree,
1574
+ tree,
1575
+ null,
1576
+ new Set(),
1577
+ pkgSpecVersionCache,
1578
+ options,
1579
+ ));
1580
+
1581
+ if (shouldFetchPackageMetadata() && pkgList?.length) {
1582
+ if (DEBUG_MODE) {
1583
+ console.log(
1584
+ `About to fetch npm registry metadata for ${pkgList.length} packages in parsePkgLock`,
1585
+ );
1586
+ }
1587
+ pkgList = await getNpmMetadata(pkgList);
1588
+ return { pkgList, dependenciesList };
1589
+ }
1590
+ return {
1591
+ pkgList,
1592
+ dependenciesList,
1593
+ };
1594
+ }
1595
+
1596
+ /**
1597
+ * Given a lock file this method would return an Object with the identity as the key and parsed name and value
1598
+ * eg: "@actions/core@^1.2.6", "@actions/core@^1.6.0":
1599
+ * version "1.6.0"
1600
+ * would result in two entries
1601
+ *
1602
+ * @param {string} lockData Yarn Lockfile data
1603
+ */
1604
+ export function yarnLockToIdentMap(lockData) {
1605
+ const identMap = {};
1606
+ let currentIdents = [];
1607
+ lockData.split("\n").forEach((l) => {
1608
+ l = l.replaceAll("\r", "");
1609
+ if (l === "\n" || !l.length || l.startsWith("#")) {
1610
+ return;
1611
+ }
1612
+ // "@actions/core@^1.2.6", "@actions/core@^1.6.0":
1613
+ if (!l.startsWith(" ") && l.trim().length > 0) {
1614
+ const tmpA = l.replace(/["']/g, "").split(", ");
1615
+ if (tmpA?.length) {
1616
+ for (let s of tmpA) {
1617
+ if (!s.startsWith("__")) {
1618
+ if (s.endsWith(":")) {
1619
+ s = s.substring(0, s.length - 1);
1620
+ }
1621
+ // Non-strict mode parsing
1622
+ const match = s.match(/^(?:(@[^/]+?)\/)?([^/]+?)(?:@(.+))?$/);
1623
+ if (!match) {
1624
+ continue;
1625
+ }
1626
+ let [, group, name, range] = match;
1627
+ if (group) {
1628
+ group = `${group}/`;
1629
+ }
1630
+ // "lru-cache@npm:^6.0.0":
1631
+ // "string-width-cjs@npm:string-width@^4.2.0":
1632
+ // Here range can be
1633
+ // - npm:^6.0.0
1634
+ // - npm:@types/ioredis@^4.28.10
1635
+ // - npm:strip-ansi@^6.0.1
1636
+ // See test cases with yarn3.lock and yarn6.lock
1637
+ if (range?.startsWith("npm:")) {
1638
+ if (range.includes("@")) {
1639
+ range = range.split("@").slice(-1)[0];
1640
+ } else {
1641
+ range = range.replace("npm:", "");
1642
+ }
1643
+ if (
1644
+ !l.includes(" ") &&
1645
+ l.includes("@npm:") &&
1646
+ l.match(/@/g).length === 2
1647
+ ) {
1648
+ const newName = l
1649
+ .split("@npm:")
1650
+ .pop()
1651
+ .replace(`@${range}`, "")
1652
+ .replace(/[:"']/g, "");
1653
+ if (
1654
+ !newName.startsWith("*") &&
1655
+ !newName.includes(range) &&
1656
+ newName.length > name.length
1657
+ ) {
1658
+ name = newName;
1659
+ }
1660
+ }
1661
+ }
1662
+ currentIdents.push(`${group || ""}${name}|${range}`);
1663
+ }
1664
+ }
1665
+ }
1666
+ } else if (
1667
+ (l.startsWith(" version") || l.startsWith(' "version')) &&
1668
+ currentIdents.length
1669
+ ) {
1670
+ const tmpA = l.replace(/"/g, "").split(" ");
1671
+ const version = tmpA[tmpA.length - 1].trim();
1672
+ for (const id of currentIdents) {
1673
+ identMap[id] = version;
1674
+ }
1675
+ currentIdents = [];
1676
+ }
1677
+ });
1678
+ return identMap;
1679
+ }
1680
+
1681
+ function _parseYarnLine(l) {
1682
+ let name = "";
1683
+ let group = "";
1684
+ let fullName;
1685
+ // A yarn lock key is `<name>@<descriptor>`. The first "@" that is not the
1686
+ // leading scope marker separates the package name from its descriptor:
1687
+ // asap@~2.0.3 -> name "asap"
1688
+ // @babel/cli@7.10.1 -> name "@babel/cli"
1689
+ // lru-cache@npm:^6.0.0 -> name "lru-cache"
1690
+ // react-is-18@npm:react-is@^18.3.1 -> npm alias, real name "react-is"
1691
+ // lru-cache@patch:lru-cache@npm:... -> patch protocol, name "lru-cache"
1692
+ const sepIdx = l.startsWith("@") ? l.indexOf("@", 1) : l.indexOf("@");
1693
+ if (sepIdx > -1) {
1694
+ fullName = l.slice(0, sepIdx);
1695
+ const descriptor = l.slice(sepIdx + 1);
1696
+ // Only an npm: descriptor placed immediately after the name can alias to a
1697
+ // different real package. Other protocols (patch:, workspace:, file:, ...)
1698
+ // keep the original name; the "@npm:" nested inside a patch locator is not
1699
+ // an alias marker, so we must not treat it as one.
1700
+ if (descriptor.startsWith("npm:")) {
1701
+ const target = descriptor.slice("npm:".length);
1702
+ // An alias target is `name@range`; a plain version is just a `range`.
1703
+ // A leading "@" only marks a scoped package, so look for the separator
1704
+ // beyond it.
1705
+ const aliasSepIdx = target.startsWith("@")
1706
+ ? target.indexOf("@", 1)
1707
+ : target.indexOf("@");
1708
+ if (aliasSepIdx > -1) {
1709
+ // Aliased dependency: use the real package name from the target.
1710
+ fullName = target.slice(0, aliasSepIdx);
1711
+ }
1712
+ }
1713
+ }
1714
+ if (fullName) {
1715
+ const slashIdx = fullName.indexOf("/");
1716
+ if (slashIdx > -1) {
1717
+ group = fullName.slice(0, slashIdx);
1718
+ name = fullName.slice(slashIdx + 1);
1719
+ } else {
1720
+ name = fullName;
1721
+ }
1722
+ }
1723
+ return { group, name };
1724
+ }
1725
+
1726
+ function _resolveYarnDependencyBomRef(
1727
+ packageName,
1728
+ versionRange,
1729
+ identMap,
1730
+ workspacePackages = [],
1731
+ ) {
1732
+ if (!packageName || !versionRange) {
1733
+ return undefined;
1734
+ }
1735
+ let packageNameToUse = packageName;
1736
+ let versionRangeToUse = versionRange;
1737
+ if (versionRange.startsWith("npm:")) {
1738
+ if (versionRange.includes("@")) {
1739
+ versionRangeToUse = versionRange.split("@").splice(-1)[0];
1740
+ packageNameToUse = versionRange
1741
+ .replace("npm:", "")
1742
+ .replace(`@${versionRangeToUse}`, "");
1743
+ } else {
1744
+ versionRangeToUse = versionRange.replace("npm:", "");
1745
+ }
1746
+ }
1747
+ let resolvedVersion =
1748
+ identMap[`${packageName}|${versionRangeToUse}`] ||
1749
+ identMap[`${packageNameToUse}|${versionRangeToUse}`];
1750
+ if (!resolvedVersion) {
1751
+ const packageKeys = Object.keys(identMap).filter((key) => {
1752
+ const [pkg] = key.split("|");
1753
+ return pkg === packageName || pkg === packageNameToUse;
1754
+ });
1755
+ resolvedVersion = identMap[packageKeys[0]];
1756
+ }
1757
+ if (!resolvedVersion && workspacePackages?.length) {
1758
+ const matchingWorkspace = findMatchingNpmWorkspace(
1759
+ workspacePackages,
1760
+ packageNameToUse,
1761
+ );
1762
+ if (matchingWorkspace) {
1763
+ const versionMatch = matchingWorkspace.match(/@([^@]+)$/);
1764
+ if (versionMatch) {
1765
+ resolvedVersion = versionMatch[1];
1766
+ }
1767
+ }
1768
+ }
1769
+ let purlPart = `pkg:npm/${encodeURIComponent(packageNameToUse).replace(/%2F/g, "/")}`;
1770
+ if (resolvedVersion?.length) {
1771
+ purlPart = `${purlPart}@${resolvedVersion}`;
1772
+ }
1773
+ return decodeURIComponent(Purl.parse(purlPart).toString());
1774
+ }
1775
+
1776
+ function _collectYarnRootDependencyRefs(
1777
+ yarnLockFile,
1778
+ identMap,
1779
+ workspacePackages = [],
1780
+ ) {
1781
+ const rootRefs = _createYarnRootDependencyRefs();
1782
+ const packageJsonFile = join(dirname(yarnLockFile), "package.json");
1783
+ if (!safeExistsSync(packageJsonFile)) {
1784
+ return rootRefs;
1785
+ }
1786
+ let packageJson;
1787
+ try {
1788
+ packageJson = JSON.parse(readFileSync(packageJsonFile, "utf-8"));
1789
+ } catch {
1790
+ return rootRefs;
1791
+ }
1792
+ for (const dependencyType of Object.keys(rootRefs)) {
1793
+ for (const [packageName, versionRange] of Object.entries(
1794
+ packageJson[dependencyType] || {},
1795
+ )) {
1796
+ const bomRef = _resolveYarnDependencyBomRef(
1797
+ packageName,
1798
+ String(versionRange),
1799
+ identMap,
1800
+ workspacePackages,
1801
+ );
1802
+ if (bomRef) {
1803
+ rootRefs[dependencyType].add(bomRef);
1804
+ }
1805
+ }
1806
+ }
1807
+ return rootRefs;
1808
+ }
1809
+
1810
+ function _createYarnRootDependencyRefs() {
1811
+ return {
1812
+ dependencies: new Set(),
1813
+ devDependencies: new Set(),
1814
+ optionalDependencies: new Set(),
1815
+ peerDependencies: new Set(),
1816
+ };
1817
+ }
1818
+
1819
+ function _buildDependencyClosure(rootRefs, dependenciesMap) {
1820
+ const closure = new Set();
1821
+ const stack = [...rootRefs];
1822
+ while (stack.length) {
1823
+ const ref = stack.pop();
1824
+ if (!ref || closure.has(ref)) {
1825
+ continue;
1826
+ }
1827
+ closure.add(ref);
1828
+ for (const childRef of dependenciesMap[ref] || []) {
1829
+ stack.push(childRef);
1830
+ }
1831
+ }
1832
+ return closure;
1833
+ }
1834
+
1835
+ function _markYarnDependencyScopeClosures(
1836
+ pkgList,
1837
+ dependenciesList,
1838
+ yarnRootRefs,
1839
+ yarnOptionalDependencyRefs,
1840
+ ) {
1841
+ const dependenciesMap = {};
1842
+ for (const dependency of dependenciesList || []) {
1843
+ if (!dependenciesMap[dependency.ref]) {
1844
+ dependenciesMap[dependency.ref] = [];
1845
+ }
1846
+ dependenciesMap[dependency.ref] = Array.from(
1847
+ new Set([
1848
+ ...dependenciesMap[dependency.ref],
1849
+ ...(dependency.dependsOn || []),
1850
+ ]),
1851
+ );
1852
+ }
1853
+ const runtimeClosure = _buildDependencyClosure(
1854
+ yarnRootRefs.dependencies,
1855
+ dependenciesMap,
1856
+ );
1857
+ const devClosure = _buildDependencyClosure(
1858
+ yarnRootRefs.devDependencies,
1859
+ dependenciesMap,
1860
+ );
1861
+ const optionalClosure = _buildDependencyClosure(
1862
+ new Set([
1863
+ ...yarnRootRefs.optionalDependencies,
1864
+ ...yarnOptionalDependencyRefs,
1865
+ ]),
1866
+ dependenciesMap,
1867
+ );
1868
+ const peerClosure = _buildDependencyClosure(
1869
+ yarnRootRefs.peerDependencies,
1870
+ dependenciesMap,
1871
+ );
1872
+ for (const pkg of pkgList) {
1873
+ const ref = pkg["bom-ref"];
1874
+ if (!ref) {
1875
+ continue;
1876
+ }
1877
+ if (
1878
+ optionalClosure.has(ref) &&
1879
+ (!runtimeClosure.has(ref) ||
1880
+ yarnRootRefs.optionalDependencies.has(ref) ||
1881
+ yarnOptionalDependencyRefs.has(ref))
1882
+ ) {
1883
+ pkg.scope = "optional";
1884
+ setNpmOptionalProperty(pkg);
1885
+ }
1886
+ if (peerClosure.has(ref) && !runtimeClosure.has(ref)) {
1887
+ pkg.scope = "optional";
1888
+ setNpmPeerProperty(pkg);
1889
+ }
1890
+ if (devClosure.has(ref) && !runtimeClosure.has(ref)) {
1891
+ pkg.scope = "optional";
1892
+ setNpmDevelopmentProperty(pkg);
1893
+ }
1894
+ }
1895
+ }
1896
+
1897
+ /**
1898
+ * Parse nodejs yarn lock file
1899
+ *
1900
+ * @param {string} yarnLockFile yarn.lock file
1901
+ * @param {Object} parentComponent parent component
1902
+ * @param {Array[String]} workspacePackages Workspace packages
1903
+ * @param {Object} workspaceSrcFiles Workspace package.json files
1904
+ * @param {Object} _workspaceDirectDeps Direct dependencies of each workspace
1905
+ * @param {Object} depsWorkspaceRefs Workspace references for each dependency
1906
+ */
1907
+ export async function parseYarnLock(
1908
+ yarnLockFile,
1909
+ parentComponent = null,
1910
+ workspacePackages = [],
1911
+ workspaceSrcFiles = {},
1912
+ _workspaceDirectDeps = {},
1913
+ depsWorkspaceRefs = {},
1914
+ ) {
1915
+ let pkgList = [];
1916
+ const dependenciesList = [];
1917
+ const depKeys = {};
1918
+ let yarnRootRefs = _createYarnRootDependencyRefs();
1919
+ const yarnOptionalDependencyRefs = new Set();
1920
+ const npmrcConfig = loadNpmrcConfig(dirname(yarnLockFile));
1921
+ if (safeExistsSync(yarnLockFile)) {
1922
+ const lockData = readFileSync(yarnLockFile, "utf8");
1923
+ let name = "";
1924
+ let name_aliases = [];
1925
+ let group = "";
1926
+ let version = "";
1927
+ let integrity = "";
1928
+ let resolvedUrl = "";
1929
+ let depsMode = false;
1930
+ let optionalDepsMode = false;
1931
+ let purlString = "";
1932
+ let deplist = new Set();
1933
+ const pkgAddedMap = {};
1934
+ // Map to track workspace package PURL replacements from 0.0.0-use.local to actual versions
1935
+ const workspacePurlMap = {};
1936
+ // This would have the keys and the resolved version required to solve the dependency tree
1937
+ const identMap = yarnLockToIdentMap(lockData);
1938
+ yarnRootRefs = _collectYarnRootDependencyRefs(
1939
+ yarnLockFile,
1940
+ identMap,
1941
+ workspacePackages,
1942
+ );
1943
+ lockData.split("\n").forEach((l) => {
1944
+ l = l.replaceAll("\r", "");
1945
+ if (l.startsWith("#")) {
1946
+ return;
1947
+ }
1948
+ if (!l.startsWith(" ") || l.trim() === "") {
1949
+ // Create an entry for the package and reset variables
1950
+ if (
1951
+ name !== "" &&
1952
+ version !== "" &&
1953
+ (integrity !== "" ||
1954
+ version.includes("local") ||
1955
+ (integrity === "" && (depsMode || l.trim() === "")))
1956
+ ) {
1957
+ name_aliases.push({ group, name });
1958
+ // FIXME: What should we do about the dependencies for such aliases
1959
+ for (const ang of name_aliases) {
1960
+ group = ang.group;
1961
+ name = ang.name;
1962
+ let qualifiers = null;
1963
+ let extRefs = [];
1964
+ const isGitDep =
1965
+ resolvedUrl &&
1966
+ (resolvedUrl.startsWith("git+") ||
1967
+ resolvedUrl.startsWith("git://") ||
1968
+ resolvedUrl.startsWith("ssh://") ||
1969
+ resolvedUrl.startsWith("git@") ||
1970
+ resolvedUrl.includes(".git"));
1971
+ if (isGitDep) {
1972
+ qualifiers = buildNpmGitPurlQualifiers(
1973
+ resolvedUrl,
1974
+ group,
1975
+ npmrcConfig,
1976
+ );
1977
+ const gitIntakeRefs = buildNpmGitDistributionIntakeRefs(
1978
+ group,
1979
+ name,
1980
+ version,
1981
+ npmrcConfig,
1982
+ );
1983
+ if (gitIntakeRefs) {
1984
+ extRefs = extRefs.concat(gitIntakeRefs);
1985
+ }
1986
+ }
1987
+ // Create a purl ref for the current package
1988
+ purlString = build({
1989
+ type: "npm",
1990
+ namespace: group || null,
1991
+ name: name,
1992
+ version: version || null,
1993
+ qualifiers: qualifiers || null,
1994
+ });
1995
+ // Skip workspace packages with placeholder version "0.0.0-use.local"
1996
+ // These are yarn's internal placeholders for local workspace packages
1997
+ // The actual workspace packages are already added with their real versions
1998
+ const isPlaceholderVersion = version === "0.0.0-use.local";
1999
+ if (isPlaceholderVersion) {
2000
+ // Try to find matching workspace package by creating the expected PURL format
2001
+ const expectedPurlStart = `pkg:npm/${group ? `${group}/${name}` : name}@`;
2002
+ const actualWorkspacePurl = workspacePackages.find(
2003
+ (wp) =>
2004
+ wp.startsWith(expectedPurlStart) ||
2005
+ wp.includes(
2006
+ `pkg:npm/${encodeForPurl(group ? `${group}/${name}` : name)}@`,
2007
+ ),
2008
+ );
2009
+ // Also check if this package already exists in pkgList with a real version
2010
+ const existingPkg = pkgList.find(
2011
+ (pkg) =>
2012
+ pkg.group === (group || "") &&
2013
+ pkg.name === name &&
2014
+ pkg.version !== "0.0.0-use.local",
2015
+ );
2016
+ // Check if this is a root workspace package by looking at parentComponent
2017
+ const isRootWorkspacePackage =
2018
+ parentComponent &&
2019
+ parentComponent.name === name &&
2020
+ parentComponent.group === (group || "");
2021
+ if (
2022
+ actualWorkspacePurl ||
2023
+ existingPkg ||
2024
+ isRootWorkspacePackage
2025
+ ) {
2026
+ if (actualWorkspacePurl) {
2027
+ // Store mapping from placeholder PURL to actual workspace PURL (both URL-decoded for bom-ref)
2028
+ workspacePurlMap[decodeURIComponent(purlString)] =
2029
+ decodeURIComponent(actualWorkspacePurl);
2030
+ } else if (existingPkg) {
2031
+ // Store mapping to existing package's PURL
2032
+ workspacePurlMap[decodeURIComponent(purlString)] =
2033
+ existingPkg["bom-ref"];
2034
+ } else if (isRootWorkspacePackage) {
2035
+ // Store mapping to parent component's PURL
2036
+ workspacePurlMap[decodeURIComponent(purlString)] =
2037
+ parentComponent["bom-ref"];
2038
+ }
2039
+ continue;
2040
+ }
2041
+ }
2042
+ // Trim duplicates
2043
+ if (!pkgAddedMap[purlString]) {
2044
+ pkgAddedMap[purlString] = true;
2045
+ const properties = [
2046
+ {
2047
+ name: "internal:SrcFile",
2048
+ value: yarnLockFile,
2049
+ },
2050
+ ];
2051
+ const pkgObj = {
2052
+ group: group || "",
2053
+ name: name,
2054
+ version: version,
2055
+ _integrity: integrity,
2056
+ purl: purlString,
2057
+ "bom-ref": decodeURIComponent(purlString),
2058
+ properties,
2059
+ evidence: {
2060
+ identity: {
2061
+ field: "purl",
2062
+ confidence: 1,
2063
+ methods: [
2064
+ {
2065
+ technique: "manifest-analysis",
2066
+ confidence: 1,
2067
+ value: yarnLockFile,
2068
+ },
2069
+ ],
2070
+ },
2071
+ },
2072
+ };
2073
+ if (extRefs && extRefs.length > 0) {
2074
+ pkgObj.externalReferences = extRefs;
2075
+ }
2076
+ pkgList.push(pkgObj);
2077
+ }
2078
+ }
2079
+ // Reset all the variables
2080
+ group = "";
2081
+ name = "";
2082
+ name_aliases = [];
2083
+ version = "";
2084
+ integrity = "";
2085
+ }
2086
+ if (purlString && purlString !== "" && !depKeys[purlString]) {
2087
+ // Map workspace placeholder PURLs to actual workspace PURLs
2088
+ const resolvedRef =
2089
+ workspacePurlMap[decodeURIComponent(purlString)] ||
2090
+ decodeURIComponent(purlString);
2091
+ const resolvedDependsOn = [...deplist]
2092
+ .map((dep) => workspacePurlMap[dep] || dep)
2093
+ .sort();
2094
+ // Create an entry for dependencies
2095
+ dependenciesList.push({
2096
+ ref: resolvedRef,
2097
+ dependsOn: resolvedDependsOn,
2098
+ });
2099
+ depKeys[purlString] = true;
2100
+ deplist = new Set();
2101
+ purlString = "";
2102
+ resolvedUrl = "";
2103
+ depsMode = false;
2104
+ optionalDepsMode = false;
2105
+ }
2106
+ // Collect the group and the name
2107
+ l = l.replace(/["']/g, "");
2108
+ // Deals with lines including aliases
2109
+ // Eg: string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3
2110
+ const fragments = l.split(", ");
2111
+ for (let i = 0; i < fragments.length; i++) {
2112
+ const parsedline = _parseYarnLine(fragments[i]);
2113
+ if (i === 0) {
2114
+ group = parsedline.group;
2115
+ name = parsedline.name;
2116
+ } else {
2117
+ let fullName = parsedline.name;
2118
+ if (parsedline.group?.length) {
2119
+ fullName = `${parsedline.group}/${parsedline.name}`;
2120
+ }
2121
+ if (
2122
+ fullName !== name &&
2123
+ fullName !== `${group}/${name}` &&
2124
+ !name_aliases.includes(fullName)
2125
+ ) {
2126
+ name_aliases.push({
2127
+ group: parsedline.group,
2128
+ name: parsedline.name,
2129
+ });
2130
+ }
2131
+ }
2132
+ }
2133
+ } else if (
2134
+ name !== "" &&
2135
+ (l.startsWith(" dependencies:") ||
2136
+ l.startsWith(' "dependencies:') ||
2137
+ l.startsWith(" optionalDependencies:") ||
2138
+ l.startsWith(' "optionalDependencies:'))
2139
+ ) {
2140
+ if (
2141
+ l.startsWith(" dependencies:") ||
2142
+ l.startsWith(' "dependencies:')
2143
+ ) {
2144
+ depsMode = true;
2145
+ optionalDepsMode = false;
2146
+ } else {
2147
+ depsMode = false;
2148
+ optionalDepsMode = true;
2149
+ }
2150
+ } else if ((depsMode || optionalDepsMode) && l.startsWith(" ")) {
2151
+ // Given "@actions/http-client" "^1.0.11"
2152
+ // We need the resolved version from identMap
2153
+ // Deal with values with space within the quotes. Eg: minimatch "2 || 3"
2154
+ // vinyl-sourcemaps-apply ">=0.1.1 <0.2.0-0"
2155
+ l = l.trim();
2156
+ let splitPattern = ' "';
2157
+ // yarn v7 has a different split pattern
2158
+ if (l.includes('": ')) {
2159
+ splitPattern = '": ';
2160
+ } else if (l.includes(": ")) {
2161
+ splitPattern = ": ";
2162
+ }
2163
+ const tmpA = l.trim().split(splitPattern);
2164
+ if (tmpA && tmpA.length === 2) {
2165
+ let dgroupname = tmpA[0].replace(/"/g, "");
2166
+ if (dgroupname.endsWith(":")) {
2167
+ dgroupname = dgroupname.substring(0, dgroupname.length - 1);
2168
+ }
2169
+ const range = tmpA[1].replace(/["']/g, "");
2170
+ const depBomRef = _resolveYarnDependencyBomRef(
2171
+ dgroupname,
2172
+ range,
2173
+ identMap,
2174
+ workspacePackages,
2175
+ );
2176
+ if (depBomRef) {
2177
+ deplist.add(depBomRef);
2178
+ }
2179
+ if (optionalDepsMode && depBomRef) {
2180
+ yarnOptionalDependencyRefs.add(depBomRef);
2181
+ }
2182
+ }
2183
+ } else if (name !== "") {
2184
+ if (!l.startsWith(" ")) {
2185
+ depsMode = false;
2186
+ optionalDepsMode = false;
2187
+ }
2188
+ l = l.replace(/"/g, "").trim();
2189
+ const parts = l.split(" ");
2190
+ if (l.startsWith("version")) {
2191
+ version = parts[1].replace(/"/g, "");
2192
+ }
2193
+ if (l.startsWith("integrity")) {
2194
+ integrity = parts[1];
2195
+ }
2196
+ // checksum used by yarn 2/3 is hex encoded
2197
+ if (l.startsWith("checksum")) {
2198
+ // in some cases yarn 4 will add a prefix to the checksum, containing the cachekey and compression level
2199
+ // example: 10c0/53c2b231a61a46792b39a0d43bc4f4f77...
2200
+ const checksum = parts[1].split("/").pop();
2201
+ integrity = `sha512-${Buffer.from(checksum, "hex").toString(
2202
+ "base64",
2203
+ )}`;
2204
+ }
2205
+ if (l.startsWith("resolved")) {
2206
+ resolvedUrl = parts[1].replace(/"/g, "");
2207
+ const tmpB = resolvedUrl.split("#");
2208
+ if (tmpB.length > 1) {
2209
+ const digest = tmpB[1].replace(/"/g, "");
2210
+ integrity = `sha256-${digest}`;
2211
+ }
2212
+ }
2213
+ }
2214
+ });
2215
+ }
2216
+ // Add workspace references for yarn workspaces
2217
+ if (depsWorkspaceRefs && Object.keys(depsWorkspaceRefs).length) {
2218
+ for (const apkg of pkgList) {
2219
+ if (!apkg.properties) {
2220
+ apkg.properties = [];
2221
+ }
2222
+ const purlObj = Purl.parse(apkg.purl);
2223
+ purlObj.version = undefined;
2224
+ const purlNoVersion = decodeURIComponent(purlObj.toString());
2225
+ const wsRefs =
2226
+ depsWorkspaceRefs[apkg["bom-ref"]] || depsWorkspaceRefs[purlNoVersion];
2227
+ // There is a workspace reference
2228
+ if (wsRefs?.length) {
2229
+ const wsprops = apkg.properties.filter(
2230
+ (p) => p.name === "internal:workspaceRef",
2231
+ );
2232
+ // workspace properties are already set.
2233
+ if (wsprops.length) {
2234
+ continue;
2235
+ }
2236
+ for (const wref of wsRefs) {
2237
+ // Such a cycle should never happen, but we can't sure
2238
+ if (wref === apkg["bom-ref"]) {
2239
+ continue;
2240
+ }
2241
+ apkg.properties.push({
2242
+ name: "internal:workspaceRef",
2243
+ value: wref,
2244
+ });
2245
+ const purlObj = Purl.parse(apkg.purl);
2246
+ purlObj.version = undefined;
2247
+ const wrefNoVersion = decodeURIComponent(purlObj.toString());
2248
+ const wsrcFile =
2249
+ workspaceSrcFiles[wref] || workspaceSrcFiles[wrefNoVersion];
2250
+ if (wsrcFile) {
2251
+ apkg.properties.push({
2252
+ name: "internal:workspaceSrcFile",
2253
+ value: wsrcFile,
2254
+ });
2255
+ }
2256
+ }
2257
+ }
2258
+ }
2259
+ }
2260
+ // Create components for workspace packages that are referenced but not yet created
2261
+ if (workspacePackages?.length) {
2262
+ for (const workspacePkg of workspacePackages) {
2263
+ const existingComponent = pkgList.find(
2264
+ (pkg) => pkg["bom-ref"] === decodeURIComponent(workspacePkg),
2265
+ );
2266
+ if (!existingComponent) {
2267
+ try {
2268
+ if (DEBUG_MODE) {
2269
+ console.log(`Processing workspace package: "${workspacePkg}"`);
2270
+ }
2271
+ let purlObj;
2272
+ // For workspace packages that already have versions in the PURL format
2273
+ if (
2274
+ workspacePkg.includes("@") &&
2275
+ workspacePkg.lastIndexOf("@") > workspacePkg.indexOf("/")
2276
+ ) {
2277
+ // This is a scoped package with version like "npm:@swc/helpers@0.4.14"
2278
+ const lastAtIndex = workspacePkg.lastIndexOf("@");
2279
+ const purlWithoutVersion = workspacePkg.substring(0, lastAtIndex);
2280
+ const version = workspacePkg.substring(lastAtIndex + 1);
2281
+ // Parse the PURL without version first, then set the version.
2282
+ purlObj = Purl.parse(purlWithoutVersion);
2283
+ purlObj.version = version;
2284
+ } else {
2285
+ // Parse the PURL as-is (no version or simple package)
2286
+ purlObj = Purl.parse(workspacePkg);
2287
+ }
2288
+
2289
+ // Create the workspace component using the properly parsed PURL
2290
+ const workspaceComponent = {
2291
+ group: purlObj.namespace || "",
2292
+ name: purlObj.name,
2293
+ version: purlObj.version,
2294
+ purl: purlObj.toString(),
2295
+ "bom-ref": decodeURIComponent(purlObj.toString()),
2296
+ type: "library",
2297
+ scope: "required",
2298
+ properties: [
2299
+ {
2300
+ name: "internal:SrcFile",
2301
+ value: workspaceSrcFiles[workspacePkg] || "package.json",
2302
+ },
2303
+ {
2304
+ name: "internal:workspaceRef",
2305
+ value: purlObj.toString(),
2306
+ },
2307
+ {
2308
+ name: "cdx:npm:is_workspace",
2309
+ value: workspaceSrcFiles[workspacePkg] ? "true" : "false",
2310
+ },
2311
+ ],
2312
+ evidence: {
2313
+ identity: {
2314
+ field: "purl",
2315
+ confidence: 0.7,
2316
+ methods: [
2317
+ {
2318
+ technique: "manifest-analysis",
2319
+ confidence: 0.7,
2320
+ value: workspaceSrcFiles[workspacePkg] || "package.json",
2321
+ },
2322
+ ],
2323
+ },
2324
+ },
2325
+ };
2326
+
2327
+ if (workspaceSrcFiles[workspacePkg]) {
2328
+ workspaceComponent.properties.push({
2329
+ name: "internal:workspaceSrcFile",
2330
+ value: workspaceSrcFiles[workspacePkg],
2331
+ });
2332
+ }
2333
+ pkgList.push(workspaceComponent);
2334
+ } catch (err) {
2335
+ if (DEBUG_MODE) {
2336
+ console.log(
2337
+ `Error parsing workspace package PURL: ${workspacePkg}`,
2338
+ err.message,
2339
+ );
2340
+ }
2341
+ // Create a fallback component with minimal information that will trigger warnings
2342
+ // Removed property `cdx:invalid-purl` in favour of a lower confidence value.
2343
+ const fallbackComponent = {
2344
+ group: "",
2345
+ name: workspacePkg.includes("/")
2346
+ ? workspacePkg.split("/").pop()
2347
+ : workspacePkg,
2348
+ version: "",
2349
+ purl: workspacePkg,
2350
+ "bom-ref": decodeURIComponent(workspacePkg),
2351
+ type: "library",
2352
+ scope: "required",
2353
+ properties: [
2354
+ {
2355
+ name: "internal:SrcFile",
2356
+ value: workspaceSrcFiles[workspacePkg] || "package.json",
2357
+ },
2358
+ {
2359
+ name: "internal:workspaceRef",
2360
+ value: workspacePkg,
2361
+ },
2362
+ {
2363
+ name: "cdx:npm:is_workspace",
2364
+ value: workspaceSrcFiles[workspacePkg] ? "true" : "false",
2365
+ },
2366
+ ],
2367
+ evidence: {
2368
+ identity: {
2369
+ field: "purl",
2370
+ confidence: 0.1,
2371
+ methods: [
2372
+ {
2373
+ technique: "manifest-analysis",
2374
+ confidence: 0.1,
2375
+ value: workspaceSrcFiles[workspacePkg] || "package.json",
2376
+ },
2377
+ ],
2378
+ },
2379
+ },
2380
+ };
2381
+ pkgList.push(fallbackComponent);
2382
+ if (DEBUG_MODE) {
2383
+ console.log(
2384
+ `Created fallback workspace component with invalid PURL: ${workspacePkg}`,
2385
+ );
2386
+ }
2387
+ }
2388
+ }
2389
+ }
2390
+ }
2391
+
2392
+ _markYarnDependencyScopeClosures(
2393
+ pkgList,
2394
+ dependenciesList,
2395
+ yarnRootRefs,
2396
+ yarnOptionalDependencyRefs,
2397
+ );
2398
+
2399
+ if (shouldFetchPackageMetadata() && pkgList?.length) {
2400
+ if (DEBUG_MODE) {
2401
+ console.log(
2402
+ `About to fetch npm registry metadata for ${pkgList.length} packages in parseYarnLock`,
2403
+ );
2404
+ }
2405
+ pkgList = await getNpmMetadata(pkgList);
2406
+ return {
2407
+ pkgList,
2408
+ dependenciesList,
2409
+ };
2410
+ }
2411
+ return {
2412
+ pkgList,
2413
+ dependenciesList,
2414
+ };
2415
+ }
2416
+
2417
+ /**
2418
+ * Parse nodejs shrinkwrap deps file
2419
+ *
2420
+ * @param {string} swFile shrinkwrap-deps.json file
2421
+ */
2422
+ export async function parseNodeShrinkwrap(swFile) {
2423
+ const pkgList = [];
2424
+ if (safeExistsSync(swFile)) {
2425
+ const lockData = JSON.parse(readFileSync(swFile, "utf8"));
2426
+ const pkgKeys = Object.keys(lockData);
2427
+ for (const k in pkgKeys) {
2428
+ const fullName = pkgKeys[k];
2429
+ const integrity = lockData[fullName];
2430
+ const parts = fullName.split("@");
2431
+ if (parts?.length) {
2432
+ let name = "";
2433
+ let version = "";
2434
+ let group = "";
2435
+ if (parts.length === 2) {
2436
+ name = parts[0];
2437
+ version = parts[1];
2438
+ } else if (parts.length === 3) {
2439
+ if (parts[0] === "") {
2440
+ const gnameparts = parts[1].split("/");
2441
+ group = gnameparts[0];
2442
+ name = gnameparts[1];
2443
+ } else {
2444
+ name = parts[0];
2445
+ }
2446
+ version = parts[2];
2447
+ }
2448
+ pkgList.push({
2449
+ group: group,
2450
+ name: name,
2451
+ version: version,
2452
+ _integrity: integrity,
2453
+ properties: [
2454
+ {
2455
+ name: "internal:SrcFile",
2456
+ value: swFile,
2457
+ },
2458
+ ],
2459
+ evidence: {
2460
+ identity: {
2461
+ field: "purl",
2462
+ confidence: 1,
2463
+ methods: [
2464
+ {
2465
+ technique: "manifest-analysis",
2466
+ confidence: 1,
2467
+ value: swFile,
2468
+ },
2469
+ ],
2470
+ },
2471
+ },
2472
+ });
2473
+ }
2474
+ }
2475
+ }
2476
+ if (shouldFetchPackageMetadata() && pkgList?.length) {
2477
+ if (DEBUG_MODE) {
2478
+ console.log(
2479
+ `About to fetch npm registry metadata for ${pkgList.length} packages in parseNodeShrinkwrap`,
2480
+ );
2481
+ }
2482
+ return await getNpmMetadata(pkgList);
2483
+ }
2484
+ return pkgList;
2485
+ }
2486
+
2487
+ function _markTreeOptional(
2488
+ dbomRef,
2489
+ dependenciesMap,
2490
+ possibleOptionalDeps,
2491
+ visited,
2492
+ ) {
2493
+ if (possibleOptionalDeps[dbomRef] === undefined) {
2494
+ possibleOptionalDeps[dbomRef] = true;
2495
+ }
2496
+ if (dependenciesMap[dbomRef] && !visited[dbomRef]) {
2497
+ visited[dbomRef] = true;
2498
+ for (const eachDep of dependenciesMap[dbomRef]) {
2499
+ if (possibleOptionalDeps[eachDep] !== false) {
2500
+ _markTreeOptional(
2501
+ eachDep,
2502
+ dependenciesMap,
2503
+ possibleOptionalDeps,
2504
+ visited,
2505
+ );
2506
+ }
2507
+ visited[eachDep] = true;
2508
+ }
2509
+ }
2510
+ }
2511
+
2512
+ function _markTreeDevelopment(
2513
+ dbomRef,
2514
+ dependenciesMap,
2515
+ possibleDevelopmentDeps,
2516
+ visited,
2517
+ ) {
2518
+ // Production-required packages set this map entry to false, and that wins
2519
+ // over any later attempt to propagate a development-only marking.
2520
+ if (possibleDevelopmentDeps[dbomRef] === undefined) {
2521
+ possibleDevelopmentDeps[dbomRef] = true;
2522
+ }
2523
+ if (dependenciesMap[dbomRef] && !visited[dbomRef]) {
2524
+ visited[dbomRef] = true;
2525
+ for (const eachDep of dependenciesMap[dbomRef]) {
2526
+ // Undefined means we have not classified this dependency yet, so we
2527
+ // continue propagating the dev-only marking unless it was already proven
2528
+ // to be non-development via a false entry.
2529
+ if (possibleDevelopmentDeps[eachDep] !== false) {
2530
+ _markTreeDevelopment(
2531
+ eachDep,
2532
+ dependenciesMap,
2533
+ possibleDevelopmentDeps,
2534
+ visited,
2535
+ );
2536
+ }
2537
+ }
2538
+ }
2539
+ }
2540
+
2541
+ function _setTreeWorkspaceRef(
2542
+ dependenciesMap,
2543
+ depref,
2544
+ pkgRefMap,
2545
+ wref,
2546
+ wsrcFile,
2547
+ depsWorkspaceRefs,
2548
+ ) {
2549
+ for (const dref of dependenciesMap[depref] || []) {
2550
+ const addedMap = {};
2551
+ const depPkg = pkgRefMap[dref];
2552
+ if (!depPkg) {
2553
+ continue;
2554
+ }
2555
+ const wsprops = depPkg.properties.filter(
2556
+ (p) => p.name === "internal:workspaceRef",
2557
+ );
2558
+ if (wsprops.length) {
2559
+ continue;
2560
+ }
2561
+ depPkg.properties = depPkg.properties || [];
2562
+ for (const prop of depPkg.properties) {
2563
+ addedMap[prop.value] = true;
2564
+ }
2565
+ if (!addedMap[wref]) {
2566
+ depPkg.properties.push({
2567
+ name: "internal:workspaceRef",
2568
+ value: wref,
2569
+ });
2570
+ addedMap[wref] = true;
2571
+ }
2572
+ if (wsrcFile && !addedMap[wsrcFile]) {
2573
+ depPkg.properties.push({
2574
+ name: "internal:workspaceSrcFile",
2575
+ value: wsrcFile,
2576
+ });
2577
+ addedMap[wsrcFile] = true;
2578
+ }
2579
+ depsWorkspaceRefs[dref] = depsWorkspaceRefs[dref] || [];
2580
+ depsWorkspaceRefs[dref] = depsWorkspaceRefs[dref].concat(
2581
+ dependenciesMap[depref] || [],
2582
+ );
2583
+ _setTreeWorkspaceRef(
2584
+ dependenciesMap,
2585
+ dref,
2586
+ pkgRefMap,
2587
+ wref,
2588
+ wsrcFile,
2589
+ depsWorkspaceRefs,
2590
+ );
2591
+ }
2592
+ }
2593
+
2594
+ /**
2595
+ * Strip the peer-dependency resolution that pnpm encodes into a version string.
2596
+ *
2597
+ * pnpm 5 and below append it after an underscore, either as a peer spec or as a hash
2598
+ * of one, while 6 and above wrap it in parentheses:
2599
+ *
2600
+ * 7.26.0_typescript@6.0.2
2601
+ * 2.3.3_5b3b7d3a75edb27abc53579646941536
2602
+ * 3.0.1(ajv@8.14.0)
2603
+ *
2604
+ * An npm version can contain neither an underscore nor a parenthesis, so both are
2605
+ * unambiguous separators. The suffix must not survive into a version or a purl: no
2606
+ * SCA tool can match `2.3.3_5b3b7d...` against an advisory, so a component carrying
2607
+ * one is effectively invisible to vulnerability lookups.
2608
+ *
2609
+ * @param {String} version Version string from a pnpm lock file
2610
+ *
2611
+ * @returns {String} The published version, without the peer-resolution suffix
2612
+ */
2613
+ export function stripPnpmPeerSuffix(version) {
2614
+ // Only a registry version carries this suffix. Git refs, file:/link: paths and
2615
+ // package aliases may legitimately contain an underscore, and they never begin
2616
+ // with a digit.
2617
+ if (typeof version !== "string" || !/^\d/.test(version)) {
2618
+ return version;
2619
+ }
2620
+ return version.split("(")[0].split("_")[0];
2621
+ }
2622
+
2623
+ /**
2624
+ * Resolve a concrete version for a pnpm dependency.
2625
+ *
2626
+ * Link/file references are resolved by reading the target package.json, while
2627
+ * registry versions have their pnpm peer-dependency suffix stripped via
2628
+ * {@link stripPnpmPeerSuffix}.
2629
+ *
2630
+ * @param {string|object} depPkg Dependency version string or package object.
2631
+ * @param {string} relativePath Base path for resolving link/file references.
2632
+ * @returns {Promise<string|undefined>} The resolved published version.
2633
+ */
2634
+ export async function getVersionNumPnpm(depPkg, relativePath) {
2635
+ let version = depPkg;
2636
+ if (typeof version === "object" && depPkg.version) {
2637
+ version = depPkg.version;
2638
+ }
2639
+ // link:../packages/plugin-highlight-ssr
2640
+ if (version.startsWith("link:") || version.startsWith("file:")) {
2641
+ version = version.replace("link:", "").replace("file:", "");
2642
+ const relativePkgJson = relativePath
2643
+ ? join(relativePath, version, "package.json")
2644
+ : join(version.replaceAll("../", "packages/"), "package.json");
2645
+ if (safeExistsSync(relativePkgJson)) {
2646
+ const importedComponentObj = await parsePkgJson(relativePkgJson, true);
2647
+ version = importedComponentObj[0].version;
2648
+ } else if (safeExistsSync(join(version, "package.json"))) {
2649
+ const importedComponentObj = await parsePkgJson(
2650
+ join(version, "package.json"),
2651
+ true,
2652
+ );
2653
+ version = importedComponentObj[0].version;
2654
+ } else if (
2655
+ safeExistsSync(join(version.replaceAll("../", ""), "package.json"))
2656
+ ) {
2657
+ const importedComponentObj = await parsePkgJson(
2658
+ join(version.replaceAll("../", ""), "package.json"),
2659
+ true,
2660
+ );
2661
+ version = importedComponentObj[0].version;
2662
+ }
2663
+ } else {
2664
+ // version: 3.0.1(ajv@8.14.0) or, under lockfile 5, 3.0.1_ajv@8.14.0
2665
+ version = stripPnpmPeerSuffix(version);
2666
+ }
2667
+ return version;
2668
+ }
2669
+
2670
+ /**
2671
+ * Resolve a pnpm dependency to its PURL string.
2672
+ *
2673
+ * Moved here from npmutils.js to break the npmutils ↔ parsers-js cycle.
2674
+ * Only callers are in this file.
2675
+ *
2676
+ * @param {string|object} depPkg Dependency package version or object
2677
+ * @param {string} packageName Package name
2678
+ * @param {object} gitPkgRefs Git package refs map
2679
+ * @param {string} relativePath Relative path for resolution
2680
+ * @param {string} githubServerHost GitHub server host
2681
+ * @param {object} [npmrcConfig={}] npmrc configuration
2682
+ * @returns {Promise<string>} Decoded PURL string
2683
+ */
2684
+ export async function getPnpmDepPurl(
2685
+ depPkg,
2686
+ packageName,
2687
+ gitPkgRefs,
2688
+ relativePath,
2689
+ githubServerHost,
2690
+ npmrcConfig = {},
2691
+ ) {
2692
+ let name = packageName;
2693
+ let group = "";
2694
+ let version;
2695
+ const versionObj = typeof depPkg === "object" ? depPkg : { version: depPkg };
2696
+ if (versionObj?.version?.startsWith(githubServerHost)) {
2697
+ const parts = versionObj.version.split("/");
2698
+ version = parts.pop();
2699
+ name = parts.pop();
2700
+ group = parts.pop();
2701
+ if (group === githubServerHost) {
2702
+ group = "";
2703
+ } else {
2704
+ group = `@${group}`;
2705
+ }
2706
+ gitPkgRefs[versionObj.version] = { group, name, version };
2707
+ } else {
2708
+ version = await getVersionNumPnpm(depPkg, relativePath);
2709
+ const gitEntry =
2710
+ gitPkgRefs[packageName] ||
2711
+ gitPkgRefs[version] ||
2712
+ gitPkgRefs[normalizePnpmLockKey(`${packageName}@${version}`)];
2713
+ if (gitEntry) {
2714
+ group = gitEntry.group;
2715
+ name = gitEntry.name;
2716
+ version = gitEntry.version;
2717
+ const qualifiers =
2718
+ gitEntry.qualifiers ||
2719
+ buildNpmGitPurlQualifiers(gitEntry.vcsUrl, group, npmrcConfig);
2720
+ return decodeURIComponent(
2721
+ build({
2722
+ type: "npm",
2723
+ namespace: group || null,
2724
+ name: name,
2725
+ version: version || null,
2726
+ qualifiers: qualifiers || null,
2727
+ }),
2728
+ );
2729
+ }
2730
+ }
2731
+ return decodeURIComponent(
2732
+ build({
2733
+ type: "npm",
2734
+ namespace: group || null,
2735
+ name: name,
2736
+ version: version || null,
2737
+ }),
2738
+ );
2739
+ }
2740
+
2741
+ /**
2742
+ * Parse pnpm workspace file
2743
+ *
2744
+ * @param {string} workspaceFile pnpm-workspace.yaml
2745
+ * @returns {object} Object containing packages and catalogs
2746
+ */
2747
+ export function parsePnpmWorkspace(workspaceFile) {
2748
+ const workspaceData = readFileSync(workspaceFile, "utf-8");
2749
+ const yamlObj = _load(workspaceData);
2750
+ if (!yamlObj) {
2751
+ return {};
2752
+ }
2753
+ const workspacePackages = Array.isArray(yamlObj.packages)
2754
+ ? yamlObj.packages.filter((n) => typeof n === "string").map((n) => n.trim())
2755
+ : typeof yamlObj.packages === "string"
2756
+ ? [yamlObj.packages.trim()]
2757
+ : [];
2758
+ // Normalize a leading "./" so relative patterns such as "./packages/*" are
2759
+ // treated identically to "packages/*".
2760
+ const stripDotSlash = (n) => n.replace(/^\.\//, "");
2761
+ const excludePackages = workspacePackages
2762
+ .filter((n) => n.startsWith("!"))
2763
+ .map((n) => stripDotSlash(n.replace(/^!/, "")));
2764
+ const packagePatterns = workspacePackages
2765
+ // Drop exclusions ("!"), internal dirs ("__"), and the workspace root
2766
+ // self-reference ("." / "./"). Relative subpaths such as "./packages/*"
2767
+ // and dotdir members such as ".meta-updater" are kept - only the bare
2768
+ // root reference is dropped.
2769
+ .filter((n) => !/^(!|__)/.test(n) && n !== "." && n !== "./")
2770
+ .map(stripDotSlash);
2771
+ const packages = packagePatterns.map((n) =>
2772
+ n.replaceAll("/**", "").replaceAll("/*", ""),
2773
+ );
2774
+ const catalogs = yamlObj.catalog || {};
2775
+ return {
2776
+ excludePackages,
2777
+ packagePatterns,
2778
+ packages,
2779
+ catalogs,
2780
+ };
2781
+ }
2782
+
2783
+ /**
2784
+ * Parses the workspaces field from a package.json file and returns the list of
2785
+ * workspace glob patterns. Handles both array and object (with packages key) formats.
2786
+ *
2787
+ * @param {string} packageJsonFile Path to the package.json file to parse
2788
+ * @returns {Object} Object with a packages array of workspace glob patterns, or an empty object on error
2789
+ */
2790
+ export function parseYarnWorkspace(packageJsonFile) {
2791
+ try {
2792
+ const packageData = JSON.parse(readFileSync(packageJsonFile, "utf-8"));
2793
+ if (!packageData?.workspaces) {
2794
+ return {};
2795
+ }
2796
+
2797
+ let packages = [];
2798
+ // Handle both array and object formats
2799
+ if (Array.isArray(packageData.workspaces)) {
2800
+ packages = packageData.workspaces;
2801
+ } else if (
2802
+ packageData.workspaces.packages &&
2803
+ Array.isArray(packageData.workspaces.packages)
2804
+ ) {
2805
+ packages = packageData.workspaces.packages;
2806
+ }
2807
+ // Filter and normalize package patterns - don't strip the glob patterns as they're needed
2808
+ packages = packages.filter((n) => !/^(!|\.|__)/.test(n));
2809
+ return {
2810
+ packages,
2811
+ };
2812
+ } catch (_err) {
2813
+ return {};
2814
+ }
2815
+ }
2816
+
2817
+ /**
2818
+ * Helper function to find a package path in pnpm node_modules structure
2819
+ *
2820
+ * @param {string} baseDir Base directory containing node_modules
2821
+ * @param {string} packageName Package name (with or without scope)
2822
+ * @param {string} version Package version
2823
+ * @returns {string|null} Path to the package directory or null if not found
2824
+ */
2825
+ export function findPnpmPackagePath(baseDir, packageName, version) {
2826
+ if (!baseDir || !packageName) {
2827
+ return null;
2828
+ }
2829
+
2830
+ const nodeModulesDir = join(baseDir, "node_modules");
2831
+ if (!safeExistsSync(nodeModulesDir)) {
2832
+ return null;
2833
+ }
2834
+
2835
+ // Try direct node_modules lookup first (for symlinked packages)
2836
+ const directPath = join(nodeModulesDir, packageName);
2837
+ if (
2838
+ safeExistsSync(directPath) &&
2839
+ safeExistsSync(join(directPath, "package.json"))
2840
+ ) {
2841
+ return directPath;
2842
+ }
2843
+
2844
+ // Try pnpm's .pnpm directory structure
2845
+ const pnpmDir = join(nodeModulesDir, ".pnpm");
2846
+ if (safeExistsSync(pnpmDir)) {
2847
+ // pnpm stores packages as {name}@{version} in .pnpm directory
2848
+ const encodedName = packageName.replaceAll("/", "%2f");
2849
+ const virtualStoreName = packageName.replaceAll("/", "+");
2850
+ let pnpmPackagePath;
2851
+
2852
+ // Try different formats that pnpm might use
2853
+ const possiblePaths = [
2854
+ join(
2855
+ pnpmDir,
2856
+ `${virtualStoreName}@${version}`,
2857
+ "node_modules",
2858
+ packageName,
2859
+ ),
2860
+ join(pnpmDir, `${encodedName}@${version}`, "node_modules", packageName),
2861
+ join(pnpmDir, `${packageName}@${version}`, "node_modules", packageName),
2862
+ join(pnpmDir, `${virtualStoreName}@${version}`),
2863
+ join(pnpmDir, `${encodedName}@${version}`),
2864
+ join(pnpmDir, `${packageName}@${version}`),
2865
+ ];
2866
+
2867
+ for (const possiblePath of possiblePaths) {
2868
+ if (
2869
+ safeExistsSync(possiblePath) &&
2870
+ safeExistsSync(join(possiblePath, "package.json"))
2871
+ ) {
2872
+ pnpmPackagePath = possiblePath;
2873
+ break;
2874
+ }
2875
+ }
2876
+
2877
+ if (pnpmPackagePath) {
2878
+ return pnpmPackagePath;
2879
+ }
2880
+ }
2881
+
2882
+ return null;
2883
+ }
2884
+
2885
+ /**
2886
+ * pnpm packages with metadata from local node_modules
2887
+ *
2888
+ * @param {Array} pkgList Package list to enhance
2889
+ * @param {string} lockFilePath Path to the pnpm-lock.yaml file
2890
+ * @returns {Array} Enhanced package list
2891
+ */
2892
+ export async function pnpmMetadata(pkgList, lockFilePath) {
2893
+ if (!pkgList?.length || !lockFilePath) {
2894
+ return pkgList;
2895
+ }
2896
+ const baseDir = dirname(lockFilePath);
2897
+ const nodeModulesDir = join(baseDir, "node_modules");
2898
+ if (!safeExistsSync(nodeModulesDir)) {
2899
+ return pkgList;
2900
+ }
2901
+ if (DEBUG_MODE) {
2902
+ console.log(
2903
+ `Metadata for ${pkgList.length} pnpm packages using local node_modules at ${nodeModulesDir}`,
2904
+ );
2905
+ }
2906
+ let enhancedCount = 0;
2907
+ for (const pkg of pkgList) {
2908
+ const packageName = pkg.group ? `${pkg.group}/${pkg.name}` : pkg.name;
2909
+ const packagePath = findPnpmPackagePath(baseDir, packageName, pkg.version);
2910
+ if (!packagePath) {
2911
+ continue;
2912
+ }
2913
+ const packageJsonPath = join(packagePath, "package.json");
2914
+ if (!safeExistsSync(packageJsonPath)) {
2915
+ continue;
2916
+ }
2917
+ try {
2918
+ const localPkgList = await parsePkgJson(packageJsonPath, true, true);
2919
+ if (localPkgList && localPkgList.length === 1) {
2920
+ const localMetadata = localPkgList[0];
2921
+ if (localMetadata && Object.keys(localMetadata).length) {
2922
+ if (!pkg.description && localMetadata.description) {
2923
+ pkg.description = localMetadata.description;
2924
+ }
2925
+ if (!pkg.author && localMetadata.author) {
2926
+ pkg.author = localMetadata.author;
2927
+ }
2928
+ if (!pkg.license && localMetadata.license) {
2929
+ pkg.license = localMetadata.license;
2930
+ }
2931
+ if (!pkg.homepage && localMetadata.homepage) {
2932
+ pkg.homepage = localMetadata.homepage;
2933
+ }
2934
+ if (!pkg.repository && localMetadata.repository) {
2935
+ pkg.repository = localMetadata.repository;
2936
+ }
2937
+ if (!pkg.properties) {
2938
+ pkg.properties = [];
2939
+ }
2940
+ if (localMetadata?.properties?.length) {
2941
+ const seenProperties = new Set(
2942
+ pkg.properties.map(
2943
+ (prop) => `${String(prop?.name)}\u0000${String(prop?.value)}`,
2944
+ ),
2945
+ );
2946
+ for (const prop of localMetadata.properties) {
2947
+ const propertyKey = `${String(prop?.name)}\u0000${String(prop?.value)}`;
2948
+ if (!seenProperties.has(propertyKey)) {
2949
+ pkg.properties.push(prop);
2950
+ seenProperties.add(propertyKey);
2951
+ }
2952
+ }
2953
+ }
2954
+ pkg.properties.push({
2955
+ name: "internal:LocalNodeModulesPath",
2956
+ value: packagePath,
2957
+ });
2958
+ enhancedCount++;
2959
+ }
2960
+ }
2961
+ } catch (error) {
2962
+ // Silently ignore parsing errors for individual packages
2963
+ if (DEBUG_MODE) {
2964
+ console.log(
2965
+ `Failed to parse package.json at ${packageJsonPath}:`,
2966
+ error.message,
2967
+ );
2968
+ }
2969
+ }
2970
+ }
2971
+ if (DEBUG_MODE && enhancedCount > 0) {
2972
+ console.log(
2973
+ `Enhanced metadata for ${enhancedCount} packages from local node_modules`,
2974
+ );
2975
+ }
2976
+ return pkgList;
2977
+ }
2978
+
2979
+ /**
2980
+ * Resolve a pnpm dependency value that is an alias pointing at another lock entry
2981
+ * rather than a plain version.
2982
+ *
2983
+ * pnpm writes such a value in the same shape as its package keys, and that shape
2984
+ * changed between lockfile versions:
2985
+ *
2986
+ * v5 and below: /@wdio/utils/7.26.0_typescript@6.0.2, /string-width/4.2.3
2987
+ * v6 and above: /@wdio/utils@7.26.0, string-width@4.2.3
2988
+ *
2989
+ * The version is returned verbatim, peer-dependency suffix included, because that
2990
+ * is how the aliased package's own lock key is turned into a version elsewhere in
2991
+ * parsePnpmLock - stripping it here would leave the dependency ref pointing at a
2992
+ * component that does not exist.
2993
+ *
2994
+ * @param {String} value Raw dependency value from the lock file
2995
+ *
2996
+ * @returns {Object|undefined} `{name, version}`, or undefined when the value is a
2997
+ * plain version and not an alias
2998
+ */
2999
+ export function parsePnpmAliasRef(value) {
3000
+ if (!value) {
3001
+ return undefined;
3002
+ }
3003
+ const spec = value.startsWith("/") ? value.slice(1) : value;
3004
+ // A leading @ introduces a scope, so the name spans two path segments there
3005
+ const nameSegCount = spec.startsWith("@") ? 2 : 1;
3006
+ const segments = spec.split("/");
3007
+ if (segments.length > nameSegCount) {
3008
+ // v5 style: the version is a path segment of its own
3009
+ return {
3010
+ name: segments.slice(0, nameSegCount).join("/"),
3011
+ version: segments.slice(nameSegCount).join("/"),
3012
+ };
3013
+ }
3014
+ if (segments.length < nameSegCount) {
3015
+ return undefined;
3016
+ }
3017
+ // v6 style: the version is glued to the last name segment with an @. Index 0 is
3018
+ // excluded so that the scope leader of an unversioned /@scope/pkg is not mistaken
3019
+ // for the separator.
3020
+ const lastSegment = segments[nameSegCount - 1];
3021
+ const sepIdx = lastSegment.indexOf("@");
3022
+ if (sepIdx < 1) {
3023
+ return undefined;
3024
+ }
3025
+ segments[nameSegCount - 1] = lastSegment.slice(0, sepIdx);
3026
+ return {
3027
+ name: segments.join("/"),
3028
+ version: lastSegment.slice(sepIdx + 1),
3029
+ };
3030
+ }
3031
+
3032
+ /**
3033
+ * Parse nodejs pnpm lock file
3034
+ *
3035
+ * @param {string} pnpmLock pnpm-lock.yaml file
3036
+ * @param {Object} parentComponent parent component
3037
+ * @param {Array[String]} workspacePackages Workspace packages
3038
+ * @param {Object} workspaceSrcFiles Workspace package.json files
3039
+ * @param {Object} _workspaceCatalogs Workspace catalogs
3040
+ * @param {Object} _workspaceDirectDeps Direct dependencies of each workspace
3041
+ * @param {Object} depsWorkspaceRefs Workspace references for each dependency
3042
+ * @param {string} projectRoot Root path used to relativize pnpm-lock evidence paths
3043
+ */
3044
+ export async function parsePnpmLock(
3045
+ pnpmLock,
3046
+ parentComponent = null,
3047
+ workspacePackages = [],
3048
+ workspaceSrcFiles = {},
3049
+ _workspaceCatalogs = {},
3050
+ _workspaceDirectDeps = {},
3051
+ depsWorkspaceRefs = {},
3052
+ projectRoot = undefined,
3053
+ ) {
3054
+ let pkgList = [];
3055
+ const dependenciesList = [];
3056
+ const pnpmLockDir = dirname(pnpmLock);
3057
+ const pnpmEvidenceRoot = projectRoot ? resolve(projectRoot) : pnpmLockDir;
3058
+ const npmrcConfig = loadNpmrcConfig(projectRoot || pnpmLockDir);
3059
+ const pnpmLockSrcFile = path.isAbsolute(pnpmLock)
3060
+ ? relative(pnpmEvidenceRoot, pnpmLock)
3061
+ : pnpmLock;
3062
+ // For lockfile >= 9, we need to track dev and optional packages manually
3063
+ // See: #1163
3064
+ // Moreover, we have changed >= 9 for >= 6
3065
+ // See: discussion #1359
3066
+ const possibleDevelopmentDeps = {};
3067
+ const possibleOptionalDeps = {};
3068
+ const dependenciesMap = {};
3069
+ let ppurl = "";
3070
+ let lockfileVersion = 0;
3071
+ const parentSubComponents = [];
3072
+ const srcFilesMap = {};
3073
+ const workspacePackageNames = {};
3074
+ const pkgRefMap = {};
3075
+ // Track references to packages that are directly installed from github.com
3076
+ const gitPkgRefs = {};
3077
+ // Track aliases
3078
+ const possibleAliases = {};
3079
+ const possibleAliasesRefs = {};
3080
+ // pnpm could refer to packages from git sources
3081
+ const githubServerHost =
3082
+ readEnvironmentVariable("CDXGEN_GIT_HOST") || "github.com";
3083
+ // Convert workspace package names to an object to help with the lookup
3084
+ for (const w of workspacePackages || []) {
3085
+ workspacePackageNames[w] = true;
3086
+ }
3087
+ if (parentComponent?.name) {
3088
+ ppurl =
3089
+ parentComponent.purl ||
3090
+ build({
3091
+ type: "npm",
3092
+ namespace: parentComponent.group || null,
3093
+ name: parentComponent.name,
3094
+ version: parentComponent.version || null,
3095
+ });
3096
+ }
3097
+ if (safeExistsSync(pnpmLock)) {
3098
+ const lockData = readFileSync(pnpmLock, "utf8");
3099
+ let yamlObj = parseAllDocuments(lockData);
3100
+ if (!yamlObj) {
3101
+ return {};
3102
+ }
3103
+ if (Array.isArray(yamlObj)) {
3104
+ try {
3105
+ yamlObj = yamlObj[yamlObj.length - 1].toJS();
3106
+ } catch (_e) {
3107
+ console.log(`Unable to parse the pnpm lock file ${pnpmLock}.`);
3108
+ return {};
3109
+ }
3110
+ }
3111
+ lockfileVersion = yamlObj.lockfileVersion;
3112
+ try {
3113
+ lockfileVersion = Number.parseFloat(lockfileVersion, 10);
3114
+ } catch (_e) {
3115
+ // ignore parse errors
3116
+ }
3117
+ Object.assign(
3118
+ gitPkgRefs,
3119
+ buildPnpmGitPkgRefs(
3120
+ yamlObj.packages || {},
3121
+ yamlObj.snapshots || {},
3122
+ npmrcConfig,
3123
+ ),
3124
+ );
3125
+ // This logic matches the pnpm list command to include only direct dependencies
3126
+ if (ppurl !== "" && yamlObj?.importers) {
3127
+ // In lock file version 6, direct dependencies is under importers
3128
+ const rootDirectDeps =
3129
+ lockfileVersion >= 6
3130
+ ? yamlObj.importers["."]?.dependencies || {}
3131
+ : yamlObj.dependencies || {};
3132
+ const rootDevDeps =
3133
+ lockfileVersion >= 6
3134
+ ? yamlObj.importers["."]?.devDependencies || {}
3135
+ : {};
3136
+ const rootOptionalDeps =
3137
+ lockfileVersion >= 6
3138
+ ? yamlObj.importers["."]?.optionalDependencies || {}
3139
+ : {};
3140
+ const rootPeerDeps =
3141
+ lockfileVersion >= 6
3142
+ ? yamlObj.importers["."]?.peerDependencies || {}
3143
+ : {};
3144
+ const ddeplist = new Set();
3145
+ // Find the root dev dependencies
3146
+ for (const rdk of Object.keys(rootDevDeps)) {
3147
+ let specifier;
3148
+ if (
3149
+ typeof rootDevDeps[rdk] === "object" &&
3150
+ rootDevDeps[rdk].specifier
3151
+ ) {
3152
+ specifier = rootDevDeps[rdk].specifier;
3153
+ }
3154
+ if (specifier?.includes("npm:")) {
3155
+ possibleAliases[rdk] = specifier;
3156
+ }
3157
+ const dpurl = await getPnpmDepPurl(
3158
+ rootDevDeps[rdk],
3159
+ rdk,
3160
+ gitPkgRefs,
3161
+ undefined,
3162
+ githubServerHost,
3163
+ npmrcConfig,
3164
+ );
3165
+ possibleDevelopmentDeps[dpurl] = true;
3166
+ }
3167
+ // Find the root optional and peer dependencies
3168
+ for (const rdk of Object.keys({ ...rootOptionalDeps, ...rootPeerDeps })) {
3169
+ let specifier;
3170
+ if (
3171
+ typeof rootOptionalDeps[rdk] === "object" &&
3172
+ rootOptionalDeps[rdk].specifier
3173
+ ) {
3174
+ specifier = rootOptionalDeps[rdk].specifier;
3175
+ }
3176
+ if (specifier?.includes("npm:")) {
3177
+ possibleAliases[rdk] = specifier;
3178
+ }
3179
+ const dpurl = await getPnpmDepPurl(
3180
+ rootOptionalDeps[rdk] || rootPeerDeps[rdk],
3181
+ rdk,
3182
+ gitPkgRefs,
3183
+ undefined,
3184
+ githubServerHost,
3185
+ npmrcConfig,
3186
+ );
3187
+ possibleOptionalDeps[dpurl] = true;
3188
+ possibleDevelopmentDeps[dpurl] = false;
3189
+ }
3190
+ // Find the root direct dependencies
3191
+ for (const dk of Object.keys(rootDirectDeps)) {
3192
+ let specifier;
3193
+ if (
3194
+ typeof rootDirectDeps[dk] === "object" &&
3195
+ rootDirectDeps[dk].specifier
3196
+ ) {
3197
+ specifier = rootDirectDeps[dk].specifier;
3198
+ }
3199
+ if (specifier?.includes("npm:")) {
3200
+ possibleAliases[dk] = specifier;
3201
+ }
3202
+ const dpurl = await getPnpmDepPurl(
3203
+ rootDirectDeps[dk],
3204
+ dk,
3205
+ gitPkgRefs,
3206
+ undefined,
3207
+ githubServerHost,
3208
+ npmrcConfig,
3209
+ );
3210
+ ddeplist.add(dpurl);
3211
+ if (lockfileVersion >= 6) {
3212
+ // These are direct dependencies so cannot be optional
3213
+ possibleOptionalDeps[dpurl] = false;
3214
+ }
3215
+ possibleDevelopmentDeps[dpurl] = false;
3216
+ }
3217
+ // pnpm-lock.yaml contains more than root dependencies in importers
3218
+ // we do what we did above but for all the other components
3219
+ for (const importedComponentName of Object.keys(yamlObj?.importers)) {
3220
+ // if component name is '.' continue loop
3221
+ if (importedComponentName === ".") {
3222
+ continue;
3223
+ }
3224
+ const componentDeps =
3225
+ yamlObj?.importers[importedComponentName]["dependencies"] || {};
3226
+ const componentDevDeps =
3227
+ yamlObj?.importers[importedComponentName]["devDependencies"] || {};
3228
+ const componentOptionalDeps =
3229
+ yamlObj?.importers[importedComponentName]["optionalDependencies"] ||
3230
+ {};
3231
+ const componentPeerDeps =
3232
+ yamlObj?.importers[importedComponentName]["peerDependencies"] || {};
3233
+ let compPurl;
3234
+ let pkgSrcFile;
3235
+ let fallbackMode = true;
3236
+ if (
3237
+ safeExistsSync(
3238
+ join(pnpmLockDir, importedComponentName, "package.json"),
3239
+ )
3240
+ ) {
3241
+ pkgSrcFile = join(pnpmLockDir, importedComponentName, "package.json");
3242
+ const importedComponentObj = await parsePkgJson(pkgSrcFile, true);
3243
+ if (importedComponentObj.length) {
3244
+ const version = importedComponentObj[0].version;
3245
+ compPurl = build({
3246
+ type: "npm",
3247
+ namespace: importedComponentObj[0]?.group || null,
3248
+ name: importedComponentObj[0]?.name,
3249
+ version: version || null,
3250
+ });
3251
+ const compRef = decodeURIComponent(compPurl);
3252
+ // Add this package to the root dependency list and parent component
3253
+ ddeplist.add(compRef);
3254
+ const psObj = {
3255
+ group: importedComponentObj[0]?.group,
3256
+ name: importedComponentObj[0]?.name,
3257
+ version,
3258
+ type: "application",
3259
+ purl: compPurl,
3260
+ "bom-ref": compRef,
3261
+ };
3262
+ const purlNoVersion = build({
3263
+ type: "npm",
3264
+ namespace: importedComponentObj[0]?.group || null,
3265
+ name: importedComponentObj[0]?.name,
3266
+ version: version || null,
3267
+ });
3268
+ const matchRef =
3269
+ workspacePackageNames[decodeURIComponent(purlNoVersion)] ||
3270
+ workspacePackageNames[compRef];
3271
+ const matchSrcFile =
3272
+ workspaceSrcFiles[decodeURIComponent(purlNoVersion)] ||
3273
+ workspaceSrcFiles[compRef];
3274
+ if (matchRef || matchSrcFile) {
3275
+ psObj.properties = [
3276
+ { name: "internal:is_workspace", value: "true" },
3277
+ ];
3278
+ }
3279
+ if (matchSrcFile) {
3280
+ psObj.properties.push({
3281
+ name: "internal:SrcFile",
3282
+ value: matchSrcFile,
3283
+ });
3284
+ psObj.properties.push({
3285
+ name: "internal:virtual_path",
3286
+ value: relative(dirname(pnpmLock), dirname(matchSrcFile)),
3287
+ });
3288
+ }
3289
+ parentSubComponents.push(psObj);
3290
+ fallbackMode = false;
3291
+ }
3292
+ }
3293
+ if (fallbackMode) {
3294
+ const name = importedComponentName.split("/");
3295
+ const lastname = name[name.length - 1];
3296
+ // let subpath = name.filter(part => part !== '.' && part !== '..').join('/');
3297
+ const subpath = name
3298
+ .join("/")
3299
+ .replaceAll("../", "")
3300
+ .replaceAll("./", "");
3301
+ compPurl = build({
3302
+ type: "npm",
3303
+ namespace: parentComponent.group || null,
3304
+ name: `${parentComponent.name}/${lastname}`,
3305
+ version: parentComponent.version || "latest" || null,
3306
+ subpath: subpath || null,
3307
+ });
3308
+ }
3309
+ // Find the component optional dependencies
3310
+ const comDepList = new Set();
3311
+ for (const cdk of Object.keys(componentDeps)) {
3312
+ const versionObj = componentDeps[cdk];
3313
+ const depRef = await getPnpmDepPurl(
3314
+ versionObj,
3315
+ cdk,
3316
+ gitPkgRefs,
3317
+ importedComponentName,
3318
+ githubServerHost,
3319
+ npmrcConfig,
3320
+ );
3321
+ // This is a definite dependency of this component
3322
+ comDepList.add(depRef);
3323
+ possibleOptionalDeps[depRef] = false;
3324
+ possibleDevelopmentDeps[depRef] = false;
3325
+ // Track the package.json files
3326
+ if (pkgSrcFile) {
3327
+ if (!srcFilesMap[depRef]) {
3328
+ srcFilesMap[depRef] = [];
3329
+ }
3330
+ srcFilesMap[depRef].push(pkgSrcFile);
3331
+ }
3332
+ }
3333
+ for (const cdk of Object.keys(componentDevDeps)) {
3334
+ const version = await getVersionNumPnpm(componentDevDeps[cdk]);
3335
+ const dpurl = build({
3336
+ type: "npm",
3337
+ namespace: "" || null,
3338
+ name: cdk,
3339
+ version: version || null,
3340
+ });
3341
+ const devDpRef = decodeURIComponent(dpurl);
3342
+ possibleDevelopmentDeps[devDpRef] = true;
3343
+ // This is also a dependency of this component
3344
+ comDepList.add(devDpRef);
3345
+ }
3346
+ for (const cdk of Object.keys({
3347
+ ...componentOptionalDeps,
3348
+ ...componentPeerDeps,
3349
+ })) {
3350
+ const version = await getVersionNumPnpm(
3351
+ componentOptionalDeps[cdk] || componentPeerDeps[cdk],
3352
+ );
3353
+ const dpurl = build({
3354
+ type: "npm",
3355
+ namespace: "" || null,
3356
+ name: cdk,
3357
+ version: version || null,
3358
+ });
3359
+ possibleOptionalDeps[decodeURIComponent(dpurl)] = true;
3360
+ possibleDevelopmentDeps[decodeURIComponent(dpurl)] = false;
3361
+ }
3362
+ dependenciesList.push({
3363
+ ref: decodeURIComponent(compPurl),
3364
+ dependsOn: [...comDepList].sort(),
3365
+ });
3366
+ }
3367
+ dependenciesList.push({
3368
+ ref: decodeURIComponent(ppurl),
3369
+ dependsOn: [...ddeplist].sort(),
3370
+ });
3371
+ }
3372
+ const packages = yamlObj.packages || {};
3373
+ // snapshots is a new key under lockfile version 9
3374
+ const snapshots = yamlObj.snapshots || {};
3375
+ const pkgKeys = { ...Object.keys(packages), ...Object.keys(snapshots) };
3376
+ for (const k in pkgKeys) {
3377
+ // Eg: @babel/code-frame/7.10.1
3378
+ // In lockfileVersion 6, /@babel/code-frame@7.18.6
3379
+ let fullName = pkgKeys[k].replace("/@", "@");
3380
+ // Handle /vite@4.2.1(@types/node@18.15.11) in lockfileVersion 6
3381
+ if (lockfileVersion >= 6 && fullName.includes("(")) {
3382
+ fullName = fullName.split("(")[0];
3383
+ }
3384
+ const parts = fullName.split("/");
3385
+ const packageNode =
3386
+ packages[pkgKeys[k]] ||
3387
+ snapshots[pkgKeys[k]] ||
3388
+ packages[fullName] ||
3389
+ snapshots[fullName];
3390
+ if (!packageNode) {
3391
+ continue;
3392
+ }
3393
+ const resolution =
3394
+ packages[pkgKeys[k]]?.resolution ||
3395
+ snapshots[pkgKeys[k]]?.resolution ||
3396
+ packages[fullName]?.resolution ||
3397
+ snapshots[fullName]?.resolution;
3398
+ const integrity = resolution?.integrity;
3399
+ const tarball = resolution?.tarball;
3400
+ const cpu =
3401
+ packages[pkgKeys[k]]?.cpu ||
3402
+ snapshots[pkgKeys[k]]?.cpu ||
3403
+ packages[fullName]?.cpu ||
3404
+ snapshots[fullName]?.cpu;
3405
+ const os =
3406
+ packages[pkgKeys[k]]?.os ||
3407
+ snapshots[pkgKeys[k]]?.os ||
3408
+ packages[fullName]?.os ||
3409
+ snapshots[fullName]?.os;
3410
+ const libc =
3411
+ packages[pkgKeys[k]]?.libc ||
3412
+ snapshots[pkgKeys[k]]?.libc ||
3413
+ packages[fullName]?.libc ||
3414
+ snapshots[fullName]?.libc;
3415
+ // In lock file version 9, dependencies is under snapshots
3416
+ const deps =
3417
+ packages[pkgKeys[k]]?.dependencies ||
3418
+ snapshots[pkgKeys[k]]?.dependencies ||
3419
+ packages[fullName]?.dependencies ||
3420
+ snapshots[fullName]?.dependencies ||
3421
+ {};
3422
+ const optionalDeps =
3423
+ packages[pkgKeys[k]]?.optionalDependencies ||
3424
+ snapshots[pkgKeys[k]]?.optionalDependencies ||
3425
+ packages[fullName]?.optionalDependencies ||
3426
+ snapshots[fullName]?.optionalDependencies ||
3427
+ {};
3428
+ const _peerDeps =
3429
+ packages[pkgKeys[k]]?.peerDependencies ||
3430
+ snapshots[pkgKeys[k]]?.peerDependencies ||
3431
+ packages[fullName]?.peerDependencies ||
3432
+ snapshots[fullName]?.peerDependencies ||
3433
+ {};
3434
+ // Track the explicit optional dependencies of this package
3435
+ for (const opkgName of Object.keys(optionalDeps)) {
3436
+ let vers = optionalDeps[opkgName];
3437
+ if (vers?.includes("(")) {
3438
+ vers = vers.split("(")[0];
3439
+ }
3440
+ let opurlString;
3441
+ if (vers.includes("@")) {
3442
+ opurlString = Purl.parse(
3443
+ `pkg:npm/${vers.replace(/^@/g, "%40")}`,
3444
+ ).toString();
3445
+ } else {
3446
+ opurlString = npmPurl(opkgName, vers);
3447
+ }
3448
+ const obomRef = decodeURIComponent(opurlString);
3449
+ if (possibleOptionalDeps[obomRef] === undefined) {
3450
+ possibleOptionalDeps[obomRef] = true;
3451
+ }
3452
+ if (possibleAliases[opkgName]) {
3453
+ possibleAliasesRefs[obomRef] = opkgName;
3454
+ }
3455
+ }
3456
+ let scope =
3457
+ packageNode.dev === true || packageNode.optional === true
3458
+ ? "optional"
3459
+ : undefined;
3460
+ // In v9, a package can be declared optional in more places :(
3461
+ if (
3462
+ lockfileVersion >= 9 &&
3463
+ (packages[pkgKeys[k]]?.optional === true ||
3464
+ snapshots[pkgKeys[k]]?.optional === true ||
3465
+ packages[fullName]?.optional === true ||
3466
+ snapshots[fullName]?.optional === true)
3467
+ ) {
3468
+ scope = "optional";
3469
+ }
3470
+ if (parts?.length) {
3471
+ let name = "";
3472
+ let version = "";
3473
+ let group = "";
3474
+ let srcUrl;
3475
+ const hasBin = packageNode?.hasBin;
3476
+ const deprecatedMessage = packageNode?.deprecated;
3477
+ const parsedGitKey =
3478
+ parsePnpmGitLockKey(fullName) || parsePnpmGitLockKey(pkgKeys[k]);
3479
+ if (parsedGitKey) {
3480
+ group = parsedGitKey.group;
3481
+ name = parsedGitKey.name;
3482
+ version =
3483
+ gitPkgRefs[parsedGitKey.fullName]?.version ||
3484
+ gitPkgRefs[parsedGitKey.packageName]?.version ||
3485
+ packageNode.version ||
3486
+ resolution?.commit ||
3487
+ "";
3488
+ const repo =
3489
+ resolution?.repo || gitPkgRefs[parsedGitKey.fullName]?.repo || "";
3490
+ const commit =
3491
+ resolution?.commit ||
3492
+ gitPkgRefs[parsedGitKey.fullName]?.commit ||
3493
+ "";
3494
+ if (repo && commit) {
3495
+ srcUrl = `${repo}#${commit}`;
3496
+ } else {
3497
+ srcUrl =
3498
+ gitPkgRefs[parsedGitKey.fullName]?.vcsUrl || parsedGitKey.gitSpec;
3499
+ }
3500
+ } else if (lockfileVersion >= 9 && fullName.includes("@")) {
3501
+ // ci-info@https://codeload.github.com/watson/ci-info/tar.gz/f43f6a1cefff47fb361c88cf4b943fdbcaafe540
3502
+ const possibleHttpParts = fullName.split("@");
3503
+ if (
3504
+ possibleHttpParts[possibleHttpParts.length - 1].startsWith("http")
3505
+ ) {
3506
+ srcUrl = possibleHttpParts[possibleHttpParts.length - 1];
3507
+ name = fullName.replace(`@${srcUrl}`, "");
3508
+ version = srcUrl.split("/").pop();
3509
+ } else if (
3510
+ possibleHttpParts[possibleHttpParts.length - 1].startsWith("file")
3511
+ ) {
3512
+ srcUrl = possibleHttpParts[possibleHttpParts.length - 1];
3513
+ name = fullName.replace(`@${srcUrl}`, "");
3514
+ version = "";
3515
+ } else {
3516
+ group = parts.length > 1 ? parts[0] : "";
3517
+ const tmpA = parts[parts.length - 1].split("@");
3518
+ if (tmpA.length > 1) {
3519
+ name = tmpA[0];
3520
+ version = tmpA[1];
3521
+ }
3522
+ if (version?.includes("(")) {
3523
+ version = version.split("(")[0];
3524
+ }
3525
+ }
3526
+ } else if (
3527
+ lockfileVersion >= 6 &&
3528
+ lockfileVersion < 9 &&
3529
+ fullName.includes("@")
3530
+ ) {
3531
+ const tmpA = parts[parts.length - 1].split("@");
3532
+ group = parts[0];
3533
+ if (parts.length === 2 && tmpA.length > 1) {
3534
+ name = tmpA[0];
3535
+ version = tmpA[1];
3536
+ } else {
3537
+ console.log("Missed", parts, fullName);
3538
+ }
3539
+ } else {
3540
+ if (parts.length === 2) {
3541
+ name = parts[0];
3542
+ version = parts[1];
3543
+ } else if (parts.length === 3) {
3544
+ group = parts[0];
3545
+ name = parts[1];
3546
+ version = parts[2];
3547
+ }
3548
+ }
3549
+ // Let's have some warnings till we fully support pnpm 8
3550
+ if (!name) {
3551
+ if (gitPkgRefs[fullName]) {
3552
+ name = gitPkgRefs[fullName].name;
3553
+ group = gitPkgRefs[fullName].group;
3554
+ version = gitPkgRefs[fullName].version;
3555
+ } else if (parts?.length >= 3 && parts[0] === githubServerHost) {
3556
+ version = parts[parts.length - 1];
3557
+ name = parts[parts.length - 2];
3558
+ group = parts.length === 4 ? `@${parts[parts.length - 3]}` : "";
3559
+ gitPkgRefs[fullName] = {
3560
+ group,
3561
+ name,
3562
+ version,
3563
+ purl: build({
3564
+ type: "npm",
3565
+ namespace: group || null,
3566
+ name: name,
3567
+ version: version || null,
3568
+ }),
3569
+ };
3570
+ } else {
3571
+ console.warn(
3572
+ `Unable to extract name and version for string ${pkgKeys[k]}`,
3573
+ parts,
3574
+ fullName,
3575
+ );
3576
+ continue;
3577
+ }
3578
+ }
3579
+ if (name.indexOf("file:") !== 0) {
3580
+ // The version segment of a lockfile 5 key carries the peer-dependency
3581
+ // resolution, eg /@wdio/utils/7.26.0_typescript@6.0.2. Normalising here,
3582
+ // before the purl exists, keeps `version`, `purl` and `bom-ref` in
3583
+ // agreement and keeps the bom-ref equal to the purl, which trimComponents
3584
+ // and dedupeBom both rely on.
3585
+ version = stripPnpmPeerSuffix(version);
3586
+ const gitEntry =
3587
+ gitPkgRefs[parsedGitKey?.fullName] ||
3588
+ gitPkgRefs[parsedGitKey?.packageName];
3589
+ const purlQualifiers = srcUrl
3590
+ ? buildNpmGitPurlQualifiers(srcUrl, group, npmrcConfig)
3591
+ : null;
3592
+ const purlString = build({
3593
+ type: "npm",
3594
+ namespace: group || null,
3595
+ name: name,
3596
+ version: version || null,
3597
+ qualifiers: purlQualifiers || null,
3598
+ });
3599
+ const bomRef = decodeURIComponent(purlString);
3600
+ if (
3601
+ packageNode.dev === true &&
3602
+ possibleDevelopmentDeps[bomRef] === undefined
3603
+ ) {
3604
+ possibleDevelopmentDeps[bomRef] = true;
3605
+ }
3606
+ const isBaseOptional = possibleOptionalDeps[bomRef];
3607
+ // optionalDependencies are tracked separately because they may still
3608
+ // be runtime-relevant and should keep the CycloneDX optional scope.
3609
+ // packageNode.dev captures explicit dev-only packages from the lock
3610
+ // entry, while possibleDevelopmentDeps lets that marking propagate to
3611
+ // transitive dependencies discovered through the dependency graph.
3612
+ const isBaseDevelopment =
3613
+ packageNode.dev === true ||
3614
+ possibleDevelopmentDeps[bomRef] === true;
3615
+ const deplist = [];
3616
+ for (let dpkgName of Object.keys(deps)) {
3617
+ let vers = deps[dpkgName];
3618
+ if (vers?.includes("(")) {
3619
+ vers = vers.split("(")[0];
3620
+ }
3621
+ // A value beginning with a "/" is never a version - it is an alias
3622
+ // pointing at another lock entry, which pnpm writes in its package-key
3623
+ // shape. The dual-published *-cjs shims are the common source of these,
3624
+ // and from lockfile v6 they lose the leading slash, so those are trusted
3625
+ // as aliases on the strength of the name alone.
3626
+ // Eg: '@wdio/utils-cjs': /@wdio/utils/7.26.0_typescript@6.0.2
3627
+ // string-width-cjs: string-width@4.2.3
3628
+ const aliasRef =
3629
+ vers?.startsWith("/") || dpkgName.endsWith("-cjs")
3630
+ ? parsePnpmAliasRef(vers)
3631
+ : undefined;
3632
+ if (aliasRef) {
3633
+ dpkgName = aliasRef.name;
3634
+ vers = aliasRef.version;
3635
+ }
3636
+ // A dependency value carries the same peer-resolution suffix as the lock
3637
+ // key it points at, eg jest-cli: 25.5.1_jest-resolve@25.5.1. Stripping it
3638
+ // here is what keeps this ref equal to that package's bom-ref.
3639
+ vers = stripPnpmPeerSuffix(vers);
3640
+ if (vers.includes("file:") || vers.includes("link:")) {
3641
+ vers = await getVersionNumPnpm(vers.split("@").pop());
3642
+ }
3643
+ // With overrides version could be like this: @nolyfill/side-channel@1.0.29
3644
+ // An alias has already been split into its name and version, and its
3645
+ // version legitimately contains an @ when it carries a peer suffix, so
3646
+ // it must not be re-split here.
3647
+ if (!aliasRef && vers.includes("@")) {
3648
+ const overrideVersion = vers.split("@").pop();
3649
+ dpkgName = vers
3650
+ .replace(`@${overrideVersion}`, "")
3651
+ .replace(/^\//, "");
3652
+ vers = overrideVersion;
3653
+ }
3654
+ const dpurlString = npmPurl(dpkgName, vers);
3655
+ const dbomRef = decodeURIComponent(dpurlString);
3656
+ deplist.push(dbomRef);
3657
+ // If the base package is optional, make the dependencies optional too
3658
+ // We need to repeat the optional detection down the line to find these new packages
3659
+ if (isBaseOptional && possibleOptionalDeps[dbomRef] === undefined) {
3660
+ possibleOptionalDeps[dbomRef] = true;
3661
+ scope = "optional";
3662
+ _markTreeOptional(
3663
+ dbomRef,
3664
+ dependenciesMap,
3665
+ possibleOptionalDeps,
3666
+ {},
3667
+ );
3668
+ }
3669
+ if (
3670
+ isBaseDevelopment &&
3671
+ possibleDevelopmentDeps[dbomRef] === undefined
3672
+ ) {
3673
+ possibleDevelopmentDeps[dbomRef] = true;
3674
+ _markTreeDevelopment(
3675
+ dbomRef,
3676
+ dependenciesMap,
3677
+ possibleDevelopmentDeps,
3678
+ {},
3679
+ );
3680
+ }
3681
+ }
3682
+ if (!dependenciesMap[bomRef]) {
3683
+ dependenciesMap[bomRef] = [];
3684
+ }
3685
+
3686
+ dependenciesMap[bomRef] = dependenciesMap[bomRef].concat(deplist);
3687
+ const properties = [
3688
+ {
3689
+ name: "internal:SrcFile",
3690
+ value: pnpmLockSrcFile,
3691
+ },
3692
+ ];
3693
+ if (hasBin) {
3694
+ properties.push({
3695
+ name: "cdx:npm:has_binary",
3696
+ value: "true",
3697
+ });
3698
+ }
3699
+ if (deprecatedMessage) {
3700
+ properties.push({
3701
+ name: "cdx:npm:deprecation_notice",
3702
+ value: deprecatedMessage,
3703
+ });
3704
+ }
3705
+ const binary_metadata = { os, cpu, libc };
3706
+ Object.entries(binary_metadata).forEach(([key, value]) => {
3707
+ if (!value) return;
3708
+ properties.push({
3709
+ name: `cdx:npm:${key}`,
3710
+ value: Array.isArray(value) ? value.join(", ") : value,
3711
+ });
3712
+ });
3713
+ if (srcFilesMap[decodeURIComponent(purlString)]) {
3714
+ for (const sf of srcFilesMap[decodeURIComponent(purlString)]) {
3715
+ properties.push({
3716
+ name: "cdx:npm:package_json",
3717
+ value: sf,
3718
+ });
3719
+ }
3720
+ }
3721
+ const purlNoVersion = build({
3722
+ type: "npm",
3723
+ namespace: group || null,
3724
+ name: name,
3725
+ });
3726
+ let packageType = "library";
3727
+ const theBomRef = decodeURIComponent(purlString);
3728
+ if (
3729
+ workspacePackageNames[decodeURIComponent(purlNoVersion)] ||
3730
+ workspacePackageNames[theBomRef]
3731
+ ) {
3732
+ properties.push({
3733
+ name: "internal:is_workspace",
3734
+ value: "true",
3735
+ });
3736
+ packageType = "application";
3737
+ const wsSrcFile =
3738
+ workspaceSrcFiles[decodeURIComponent(purlNoVersion)] ||
3739
+ workspaceSrcFiles[theBomRef];
3740
+ if (wsSrcFile) {
3741
+ properties.push({
3742
+ name: "internal:virtual_path",
3743
+ value: relative(dirname(pnpmLock), dirname(wsSrcFile)),
3744
+ });
3745
+ }
3746
+ }
3747
+ // Capture all the workspaces that directly depend on this package and their source file
3748
+ for (const wref of Array.from(
3749
+ depsWorkspaceRefs[purlNoVersion] ||
3750
+ depsWorkspaceRefs[purlString] ||
3751
+ [],
3752
+ )) {
3753
+ // This cycle shouldn't happen, but we can't be sure
3754
+ if (wref === purlString) {
3755
+ continue;
3756
+ }
3757
+ properties.push({
3758
+ name: "internal:workspaceRef",
3759
+ value: wref,
3760
+ });
3761
+ if (workspaceSrcFiles[wref]) {
3762
+ properties.push({
3763
+ name: "internal:workspaceSrcFile",
3764
+ value: workspaceSrcFiles[wref],
3765
+ });
3766
+ }
3767
+ // Add workspaceRef to the dependent components as well
3768
+ for (const dref of dependenciesMap[theBomRef]) {
3769
+ if (!depsWorkspaceRefs[dref]) {
3770
+ depsWorkspaceRefs[dref] = [];
3771
+ }
3772
+ if (!depsWorkspaceRefs[dref].includes(wref)) {
3773
+ depsWorkspaceRefs[dref].push(wref);
3774
+ }
3775
+ if (dependenciesMap[dref]) {
3776
+ for (const l2ref of dependenciesMap[dref]) {
3777
+ if (!depsWorkspaceRefs[l2ref]) {
3778
+ depsWorkspaceRefs[l2ref] = [];
3779
+ }
3780
+ if (!depsWorkspaceRefs[l2ref].includes(wref)) {
3781
+ depsWorkspaceRefs[l2ref].push(wref);
3782
+ }
3783
+ }
3784
+ }
3785
+ }
3786
+ }
3787
+ const thePkg = {
3788
+ group: group,
3789
+ name: name,
3790
+ version: version,
3791
+ purl: purlString,
3792
+ "bom-ref": theBomRef,
3793
+ type: packageType,
3794
+ scope,
3795
+ _integrity: integrity,
3796
+ properties,
3797
+ evidence: {
3798
+ identity: {
3799
+ field: "purl",
3800
+ confidence: 1,
3801
+ methods: [
3802
+ {
3803
+ technique: "manifest-analysis",
3804
+ confidence: 1,
3805
+ value: pnpmLockSrcFile,
3806
+ },
3807
+ ],
3808
+ },
3809
+ },
3810
+ };
3811
+ if (tarball) {
3812
+ thePkg.externalReferences = [
3813
+ {
3814
+ type: "distribution",
3815
+ url: tarball,
3816
+ },
3817
+ ];
3818
+ } else if (gitEntry?.externalReferences?.length) {
3819
+ thePkg.externalReferences = gitEntry.externalReferences;
3820
+ }
3821
+ if (possibleOptionalDeps[thePkg["bom-ref"]]) {
3822
+ setNpmOptionalProperty(thePkg);
3823
+ }
3824
+ // Don't add internal workspace packages to the components list
3825
+ if (thePkg.type !== "application") {
3826
+ if (pkgRefMap[theBomRef]) {
3827
+ continue;
3828
+ }
3829
+ pkgList.push(thePkg);
3830
+ }
3831
+ pkgRefMap[thePkg["bom-ref"]] = thePkg;
3832
+ }
3833
+ }
3834
+ }
3835
+ }
3836
+ // We need to repeat optional packages detection
3837
+ if (Object.keys(possibleOptionalDeps).length) {
3838
+ for (const apkg of pkgList) {
3839
+ if (!apkg.scope) {
3840
+ if (possibleOptionalDeps[apkg["bom-ref"]]) {
3841
+ apkg.scope = "optional";
3842
+ _markTreeOptional(
3843
+ apkg["bom-ref"],
3844
+ dependenciesMap,
3845
+ possibleOptionalDeps,
3846
+ {},
3847
+ );
3848
+ }
3849
+ }
3850
+ }
3851
+ }
3852
+ // Repeat development dependency detection after the dependency graph is fully
3853
+ // built, since a single package iteration can encounter a dev-only component
3854
+ // before its own dependency list has been captured in dependenciesMap.
3855
+ for (const dependencyRef of Object.keys(possibleDevelopmentDeps)) {
3856
+ if (possibleDevelopmentDeps[dependencyRef] === true) {
3857
+ _markTreeDevelopment(
3858
+ dependencyRef,
3859
+ dependenciesMap,
3860
+ possibleDevelopmentDeps,
3861
+ {},
3862
+ );
3863
+ }
3864
+ }
3865
+
3866
+ // Problem: We might have over aggressively marked a package as optional even it is both required and optional
3867
+ // The below loops ensure required packages continue to stay required
3868
+ // See #1184
3869
+ const requiredDependencies = {};
3870
+ const requiredDependencyStack = [];
3871
+ // Initialize the required dependency stack
3872
+ for (const dependency in possibleOptionalDeps) {
3873
+ if (possibleOptionalDeps[dependency] === false) {
3874
+ requiredDependencyStack.push(dependency);
3875
+ }
3876
+ }
3877
+
3878
+ // Walk the required dependency stack iteratively and mark it as required
3879
+ while (requiredDependencyStack.length > 0) {
3880
+ const requiredDependencyRef = requiredDependencyStack.pop();
3881
+ if (!requiredDependencies[requiredDependencyRef]) {
3882
+ requiredDependencies[requiredDependencyRef] = true;
3883
+ if (dependenciesMap[requiredDependencyRef]) {
3884
+ for (const subDependencyRef of dependenciesMap[requiredDependencyRef]) {
3885
+ requiredDependencyStack.push(subDependencyRef);
3886
+ }
3887
+ }
3888
+ }
3889
+ }
3890
+ // Ensure any required dependency is not scoped optionally
3891
+ for (const apkg of pkgList) {
3892
+ if (requiredDependencies[apkg["bom-ref"]]) {
3893
+ apkg.scope = undefined;
3894
+ }
3895
+ if (
3896
+ !requiredDependencies[apkg["bom-ref"]] &&
3897
+ possibleDevelopmentDeps[apkg["bom-ref"]]
3898
+ ) {
3899
+ if (!apkg.scope) {
3900
+ apkg.scope = "optional";
3901
+ }
3902
+ setNpmDevelopmentProperty(apkg);
3903
+ }
3904
+ if (possibleAliasesRefs[apkg["bom-ref"]]) {
3905
+ apkg.properties.push({
3906
+ name: "cdx:pnpm:alias",
3907
+ value: possibleAliasesRefs[apkg["bom-ref"]],
3908
+ });
3909
+ }
3910
+ // There are no workspaces so exit early
3911
+ if (!Object.keys(workspacePackageNames).length) {
3912
+ continue;
3913
+ }
3914
+ const purlNoVersion = decodeURIComponent(
3915
+ build({ type: "npm", namespace: apkg.group || null, name: apkg.name }),
3916
+ );
3917
+ const wsRefs =
3918
+ depsWorkspaceRefs[apkg["bom-ref"]] || depsWorkspaceRefs[purlNoVersion];
3919
+ // There is a workspace reference
3920
+ if (wsRefs?.length) {
3921
+ const wsprops = apkg.properties.filter(
3922
+ (p) => p.name === "internal:workspaceRef",
3923
+ );
3924
+ // workspace properties are already set.
3925
+ if (wsprops.length) {
3926
+ continue;
3927
+ }
3928
+ for (const wref of wsRefs) {
3929
+ // Such a cycle should never happen, but we can't sure
3930
+ if (wref === apkg["bom-ref"]) {
3931
+ continue;
3932
+ }
3933
+ apkg.properties.push({
3934
+ name: "internal:workspaceRef",
3935
+ value: wref,
3936
+ });
3937
+ const purlObj = Purl.parse(apkg.purl);
3938
+ purlObj.version = undefined;
3939
+ const wrefNoVersion = decodeURIComponent(purlObj.toString());
3940
+ const wsrcFile =
3941
+ workspaceSrcFiles[wref] || workspaceSrcFiles[wrefNoVersion];
3942
+ if (wsrcFile) {
3943
+ apkg.properties.push({
3944
+ name: "internal:workspaceSrcFile",
3945
+ value: wsrcFile,
3946
+ });
3947
+ }
3948
+ // Repeat for the children
3949
+ _setTreeWorkspaceRef(
3950
+ dependenciesMap,
3951
+ apkg["bom-ref"],
3952
+ pkgRefMap,
3953
+ wref,
3954
+ wsrcFile,
3955
+ depsWorkspaceRefs,
3956
+ );
3957
+ }
3958
+ }
3959
+ }
3960
+
3961
+ if (Object.keys(dependenciesMap).length) {
3962
+ for (const aref of Object.keys(dependenciesMap)) {
3963
+ dependenciesList.push({
3964
+ ref: aref,
3965
+ dependsOn: [...new Set(dependenciesMap[aref])].sort(),
3966
+ });
3967
+ }
3968
+ }
3969
+
3970
+ // Enhance metadata from local node_modules if available
3971
+ if (pkgList?.length) {
3972
+ pkgList = await pnpmMetadata(pkgList, pnpmLock);
3973
+ }
3974
+
3975
+ if (shouldFetchPackageMetadata() && pkgList?.length) {
3976
+ if (DEBUG_MODE) {
3977
+ console.log(
3978
+ `About to fetch npm registry metadata for ${pkgList.length} packages in parsePnpmLock`,
3979
+ );
3980
+ }
3981
+ pkgList = await getNpmMetadata(pkgList);
3982
+ return {
3983
+ pkgList,
3984
+ dependenciesList,
3985
+ parentSubComponents,
3986
+ };
3987
+ }
3988
+ return {
3989
+ pkgList,
3990
+ dependenciesList,
3991
+ parentSubComponents,
3992
+ };
3993
+ }
3994
+
3995
+ /**
3996
+ * Parse bower json file
3997
+ *
3998
+ * @param {string} bowerJsonFile bower.json file
3999
+ */
4000
+ export async function parseBowerJson(bowerJsonFile) {
4001
+ const pkgList = [];
4002
+ if (safeExistsSync(bowerJsonFile)) {
4003
+ try {
4004
+ const pkgData = JSON.parse(readFileSync(bowerJsonFile, "utf8"));
4005
+ const pkgIdentifier = parsePackageJsonName(pkgData.name);
4006
+ pkgList.push({
4007
+ name: pkgIdentifier.fullName || pkgData.name,
4008
+ group: pkgIdentifier.scope || "",
4009
+ version: pkgData.version || "",
4010
+ description: pkgData.description || "",
4011
+ license: pkgData.license || "",
4012
+ properties: [
4013
+ {
4014
+ name: "internal:SrcFile",
4015
+ value: bowerJsonFile,
4016
+ },
4017
+ ],
4018
+ evidence: {
4019
+ identity: {
4020
+ field: "purl",
4021
+ confidence: 1,
4022
+ methods: [
4023
+ {
4024
+ technique: "manifest-analysis",
4025
+ confidence: 1,
4026
+ value: bowerJsonFile,
4027
+ },
4028
+ ],
4029
+ },
4030
+ },
4031
+ });
4032
+ } catch (_err) {
4033
+ // continue regardless of error
4034
+ }
4035
+ }
4036
+ if (shouldFetchPackageMetadata() && pkgList?.length) {
4037
+ if (DEBUG_MODE) {
4038
+ console.log(
4039
+ `About to fetch npm registry metadata for ${pkgList.length} packages in parseBowerJson`,
4040
+ );
4041
+ }
4042
+ return await getNpmMetadata(pkgList);
4043
+ }
4044
+ return pkgList;
4045
+ }
4046
+
4047
+ /**
4048
+ * Parse minified js file
4049
+ *
4050
+ * @param {string} minJsFile min.js file
4051
+ */
4052
+ export async function parseMinJs(minJsFile) {
4053
+ const pkgList = [];
4054
+ if (safeExistsSync(minJsFile)) {
4055
+ try {
4056
+ const rawData = readFileSync(minJsFile, { encoding: "utf-8" });
4057
+ const tmpA = rawData.split("\n");
4058
+ tmpA.forEach((l) => {
4059
+ if ((l.startsWith("/*!") || l.startsWith(" * ")) && l.length < 500) {
4060
+ let delimiter = " * ";
4061
+ if (!l.includes(delimiter) && l.includes("/*!")) {
4062
+ delimiter = "/*!";
4063
+ }
4064
+ if (!l.includes(delimiter) && l.includes(" - ")) {
4065
+ delimiter = " - ";
4066
+ }
4067
+ const tmpPV = l.split(delimiter);
4068
+ if (!tmpPV || tmpPV.length < 2) {
4069
+ return;
4070
+ }
4071
+ // Eg: jQuery v3.6.0
4072
+ const pkgNameVer = tmpPV[1]
4073
+ .replace("/*!", "")
4074
+ .replace(" * ", "")
4075
+ .trim();
4076
+ const tmpB = pkgNameVer.includes(" - ")
4077
+ ? pkgNameVer.split(" - ")
4078
+ : pkgNameVer.split(" ");
4079
+ if (tmpB && tmpB.length > 1) {
4080
+ // Fix #223 - lowercase parsed package name
4081
+ const name = tmpB[0].replace(/ /g, "-").trim().toLowerCase();
4082
+ if (name === "@license" || name === "license") {
4083
+ return;
4084
+ }
4085
+ if (name.startsWith("@") && !name.includes("/")) {
4086
+ return;
4087
+ }
4088
+ if (
4089
+ [
4090
+ "copyright",
4091
+ "author",
4092
+ "licensed",
4093
+ "minified",
4094
+ "vendor",
4095
+ "build",
4096
+ ].includes(name.toLowerCase())
4097
+ ) {
4098
+ return;
4099
+ }
4100
+ const pkgIdentifier = parsePackageJsonName(name);
4101
+ if (pkgIdentifier.fullName !== "") {
4102
+ pkgList.push({
4103
+ name: pkgIdentifier.fullName,
4104
+ group: pkgIdentifier.scope || "",
4105
+ version: tmpB[1].replace(/^v/, "") || "",
4106
+ properties: [
4107
+ {
4108
+ name: "internal:SrcFile",
4109
+ value: minJsFile,
4110
+ },
4111
+ ],
4112
+ evidence: {
4113
+ identity: {
4114
+ field: "purl",
4115
+ confidence: 0.25,
4116
+ methods: [
4117
+ {
4118
+ technique: "filename",
4119
+ confidence: 0.25,
4120
+ value: minJsFile,
4121
+ },
4122
+ ],
4123
+ },
4124
+ },
4125
+ });
4126
+ }
4127
+ }
4128
+ }
4129
+ });
4130
+ } catch (_err) {
4131
+ // continue regardless of error
4132
+ }
4133
+ }
4134
+ if (shouldFetchPackageMetadata() && pkgList?.length) {
4135
+ if (DEBUG_MODE) {
4136
+ console.log(
4137
+ `About to fetch npm registry metadata for ${pkgList.length} packages in parseMinJs`,
4138
+ );
4139
+ }
4140
+ return await getNpmMetadata(pkgList);
4141
+ }
4142
+ return pkgList;
4143
+ }
4144
+
4145
+ // taken from a very old package https://github.com/keithamus/parse-packagejson-name/blob/master/index.js
4146
+ /**
4147
+ * Parse a package.json `name` field (or a plain string) and extract its scope,
4148
+ * full name, project name, and module name components.
4149
+ *
4150
+ * @param {string|Object} name The package name string or an object with a `name` property
4151
+ * @returns {{ scope: string|null, fullName: string, projectName: string|null, moduleName: string|null }}
4152
+ */
4153
+ export function parsePackageJsonName(name) {
4154
+ const nameRegExp = /^(?:@([^/]+)\/)?(([^.]+)(?:\.(.*))?)$/;
4155
+ const returnObject = {
4156
+ scope: null,
4157
+ fullName: "",
4158
+ projectName: "",
4159
+ moduleName: "",
4160
+ };
4161
+ const safeName = name?.name ?? name ?? "";
4162
+ const match = safeName.match(nameRegExp);
4163
+ if (match) {
4164
+ returnObject.scope =
4165
+ (match[1] && name.includes("@") ? `@${match[1]}` : match[1]) || null;
4166
+ returnObject.fullName = match[2] || match[0];
4167
+ returnObject.projectName = match[3] === match[2] ? null : match[3];
4168
+ returnObject.moduleName = match[4] || match[2] || null;
4169
+ }
4170
+ return returnObject;
4171
+ }
4172
+
4173
+ /**
4174
+ * Helper to split a command line string into an array of arguments,
4175
+ * respecting single and double quotes.
4176
+ *
4177
+ * @param {String} commandString The full command line string
4178
+ * @returns {Array<String>} Array of tokens
4179
+ */
4180
+ export function splitCommandArgs(commandString) {
4181
+ const args = [];
4182
+ let current = "";
4183
+ let inQuote = false;
4184
+ let quoteChar = "";
4185
+ let escaped = false;
4186
+
4187
+ for (let i = 0; i < commandString.length; i++) {
4188
+ const char = commandString[i];
4189
+ if (escaped) {
4190
+ current += char;
4191
+ escaped = false;
4192
+ continue;
4193
+ }
4194
+ if (char === "\\") {
4195
+ escaped = true;
4196
+ continue;
4197
+ }
4198
+ if (inQuote) {
4199
+ if (char === quoteChar) {
4200
+ inQuote = false;
4201
+ } else {
4202
+ current += char;
4203
+ }
4204
+ } else {
4205
+ if (char === '"' || char === "'") {
4206
+ inQuote = true;
4207
+ quoteChar = char;
4208
+ } else if (char === " " || char === "\t") {
4209
+ if (current.length > 0) {
4210
+ args.push(current);
4211
+ current = "";
4212
+ }
4213
+ } else {
4214
+ current += char;
4215
+ }
4216
+ }
4217
+ }
4218
+ if (current.length > 0) {
4219
+ args.push(current);
4220
+ }
4221
+ return args;
4222
+ }