@cdxgen/cdxgen 13.6.0 → 13.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/README.md +15 -8
  2. package/bin/audit.js +7 -4
  3. package/bin/cdxgen.js +256 -37
  4. package/bin/convert.js +146 -22
  5. package/bin/evinse.js +25 -3
  6. package/bin/repl.js +47 -3
  7. package/bin/sign.js +211 -34
  8. package/bin/tracebom.js +18 -6
  9. package/bin/validate.js +18 -3
  10. package/bin/verify.js +113 -5
  11. package/data/behavior-taxonomy.schema.json +1491 -0
  12. package/data/cyclonedx-2.0-bundled.schema.json +18606 -5528
  13. package/data/cyclonedx-2.0-cryptography-defs.schema.json +857 -0
  14. package/data/pqc-migration.json +239 -0
  15. package/data/remediations.json +52 -0
  16. package/data/rules/cbom-security.yaml +4 -3
  17. package/data/rules/package-integrity.yaml +28 -0
  18. package/lib/audit/index.js +25 -3
  19. package/lib/cli/bomAssembly.js +80 -1
  20. package/lib/cli/cliOptions.js +121 -17
  21. package/lib/cli/index.js +609 -898
  22. package/lib/cli/jsBom.js +3 -0
  23. package/lib/cli/jvmBom.js +365 -40
  24. package/lib/cli/managedBom.js +19 -3
  25. package/lib/cli/nativeBom.js +10 -0
  26. package/lib/core/env.js +26 -0
  27. package/lib/core/fs.js +126 -3
  28. package/lib/core/globPatterns.js +218 -0
  29. package/lib/core/processTree.js +99 -12
  30. package/lib/core/projectConfig.js +332 -25
  31. package/lib/core/toolRegistry.js +15 -1
  32. package/lib/ecosystems/denoutils.js +2 -2
  33. package/lib/ecosystems/golem.js +3 -8
  34. package/lib/ecosystems/gradleutils.js +1082 -6
  35. package/lib/ecosystems/jsEvidence.js +90 -38
  36. package/lib/ecosystems/kosi.js +3 -5
  37. package/lib/ecosystems/npmVersionResolution.js +376 -0
  38. package/lib/ecosystems/parsers-python.js +89 -28
  39. package/lib/ecosystems/parsers-rust.js +117 -15
  40. package/lib/ecosystems/remote/huggingface.js +22 -0
  41. package/lib/ecosystems/rusi.js +2 -4
  42. package/lib/ecosystems/scalasem.js +2 -1
  43. package/lib/evinser/evinser.js +196 -34
  44. package/lib/evinser/scalasem.js +5 -1
  45. package/lib/helpers/agenticToolutils.js +112 -1
  46. package/lib/helpers/bomSigner.js +43 -9
  47. package/lib/helpers/jssSigner.js +1276 -0
  48. package/lib/inventory/agentBlueprint.js +193 -0
  49. package/lib/inventory/agentToolNames.js +145 -0
  50. package/lib/inventory/aiCollector.js +153 -7
  51. package/lib/inventory/aiInventory.js +44 -8
  52. package/lib/inventory/aiProviders.js +324 -0
  53. package/lib/inventory/bomUtils.js +8 -1
  54. package/lib/inventory/ciParsers/githubActionsLock.js +821 -0
  55. package/lib/inventory/citations.js +31 -2
  56. package/lib/inventory/communityAiConfigParser.js +978 -80
  57. package/lib/inventory/cyclonedx20.js +152 -0
  58. package/lib/inventory/cyclonedx20Common.js +286 -0
  59. package/lib/inventory/cyclonedx20Derived.js +159 -0
  60. package/lib/inventory/cyclonedx20Parties.js +552 -0
  61. package/lib/inventory/cyclonedx20Read.js +768 -0
  62. package/lib/inventory/cyclonedx20ReadCommon.js +374 -0
  63. package/lib/inventory/cyclonedx20ReadComponents.js +692 -0
  64. package/lib/inventory/cyclonedx20ReadContext.js +380 -0
  65. package/lib/inventory/cyclonedx20ReadEvidence.js +466 -0
  66. package/lib/inventory/cyclonedx20ReadSections.js +599 -0
  67. package/lib/inventory/cyclonedx20Render.js +766 -0
  68. package/lib/inventory/cyclonedx20RenderComponents.js +716 -0
  69. package/lib/inventory/cyclonedx20RenderDefinitions.js +405 -0
  70. package/lib/inventory/cyclonedx20RenderEvidence.js +726 -0
  71. package/lib/inventory/cyclonedx20RenderHardware.js +203 -0
  72. package/lib/inventory/cyclonedx20RenderPatents.js +174 -0
  73. package/lib/inventory/cyclonedx20RenderRefIds.js +314 -0
  74. package/lib/inventory/cyclonedx20RenderSections.js +499 -0
  75. package/lib/inventory/cyclonedx20Tables.js +594 -0
  76. package/lib/inventory/deps.js +106 -13
  77. package/lib/inventory/dosai.js +236 -1
  78. package/lib/inventory/dosaiParsers.js +3 -6
  79. package/lib/inventory/dotnetRuntime.js +164 -14
  80. package/lib/inventory/ecosystemFormulationParsers.js +979 -0
  81. package/lib/inventory/envcontext.js +132 -38
  82. package/lib/inventory/evidenceUtils.js +2 -1
  83. package/lib/inventory/formulationParsers.js +95 -13
  84. package/lib/inventory/hbom.js +45 -6
  85. package/lib/inventory/hbomLoader.js +166 -0
  86. package/lib/inventory/mcpConfigParser.js +5 -0
  87. package/lib/inventory/occurrenceKey.js +90 -0
  88. package/lib/inventory/pqcMigration.js +426 -0
  89. package/lib/inventory/rustFormulationParser.js +21 -10
  90. package/lib/inventory/source.js +65 -6
  91. package/lib/parsers/huggingfaceManifest.js +9 -1
  92. package/lib/parsers/huggingfaceUtils.js +18 -0
  93. package/lib/parsers/kiroPermissions.js +166 -0
  94. package/lib/server/server.js +36 -34
  95. package/lib/stages/postgen/postgen.js +41 -1
  96. package/lib/stages/postgen/specVersionCompat.js +76 -0
  97. package/lib/validator/bomValidator.js +8 -1
  98. package/lib/validator/complianceEngine.js +30 -3
  99. package/lib/validator/complianceRules.js +1416 -4
  100. package/lib/validator/generated/validate-2.0.mjs +57554 -13851
  101. package/lib/validator/index.js +48 -7
  102. package/lib/validator/reporters/json.js +6 -0
  103. package/package.json +9 -9
  104. package/types/bin/repl.d.ts.map +1 -1
  105. package/types/lib/audit/index.d.ts +6 -2
  106. package/types/lib/audit/index.d.ts.map +1 -1
  107. package/types/lib/cli/bomAssembly.d.ts +16 -0
  108. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  109. package/types/lib/cli/cliOptions.d.ts +6 -0
  110. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  111. package/types/lib/cli/index.d.ts.map +1 -1
  112. package/types/lib/cli/jsBom.d.ts +2 -1
  113. package/types/lib/cli/jsBom.d.ts.map +1 -1
  114. package/types/lib/cli/jvmBom.d.ts +15 -0
  115. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  116. package/types/lib/cli/managedBom.d.ts.map +1 -1
  117. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  118. package/types/lib/core/boundedFile.d.ts +30 -0
  119. package/types/lib/core/boundedFile.d.ts.map +1 -1
  120. package/types/lib/core/env.d.ts +10 -0
  121. package/types/lib/core/env.d.ts.map +1 -1
  122. package/types/lib/core/fs.d.ts +4 -0
  123. package/types/lib/core/fs.d.ts.map +1 -1
  124. package/types/lib/core/globPatterns.d.ts +70 -0
  125. package/types/lib/core/globPatterns.d.ts.map +1 -0
  126. package/types/lib/core/paths.d.ts +9 -0
  127. package/types/lib/core/paths.d.ts.map +1 -1
  128. package/types/lib/core/processTree.d.ts +16 -5
  129. package/types/lib/core/processTree.d.ts.map +1 -1
  130. package/types/lib/core/projectConfig.d.ts +47 -8
  131. package/types/lib/core/projectConfig.d.ts.map +1 -1
  132. package/types/lib/core/toolRegistry.d.ts.map +1 -1
  133. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  134. package/types/lib/ecosystems/gradleutils.d.ts +248 -8
  135. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  136. package/types/lib/ecosystems/jsEvidence.d.ts +14 -1
  137. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  138. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  139. package/types/lib/ecosystems/npmVersionResolution.d.ts +119 -0
  140. package/types/lib/ecosystems/npmVersionResolution.d.ts.map +1 -0
  141. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  142. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  143. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  144. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  145. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  146. package/types/lib/ecosystems/scalasem.d.ts +58 -16
  147. package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
  148. package/types/lib/evinser/evinser.d.ts +61 -9
  149. package/types/lib/evinser/evinser.d.ts.map +1 -1
  150. package/types/lib/evinser/scalasem.d.ts.map +1 -1
  151. package/types/lib/helpers/agenticToolutils.d.ts +53 -19
  152. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -1
  153. package/types/lib/helpers/bomSigner.d.ts +20 -3
  154. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  155. package/types/lib/helpers/jssSigner.d.ts +288 -0
  156. package/types/lib/helpers/jssSigner.d.ts.map +1 -0
  157. package/types/lib/helpers/vsixutils.d.ts +303 -19
  158. package/types/lib/helpers/vsixutils.d.ts.map +1 -1
  159. package/types/lib/inventory/agentBlueprint.d.ts +70 -0
  160. package/types/lib/inventory/agentBlueprint.d.ts.map +1 -0
  161. package/types/lib/inventory/agentToolNames.d.ts +51 -0
  162. package/types/lib/inventory/agentToolNames.d.ts.map +1 -0
  163. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  164. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  165. package/types/lib/inventory/aiProviders.d.ts +85 -0
  166. package/types/lib/inventory/aiProviders.d.ts.map +1 -0
  167. package/types/lib/inventory/bomUtils.d.ts.map +1 -1
  168. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts +101 -0
  169. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts.map +1 -0
  170. package/types/lib/inventory/citations.d.ts +5 -0
  171. package/types/lib/inventory/citations.d.ts.map +1 -1
  172. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  173. package/types/lib/inventory/cyclonedx20.d.ts +99 -0
  174. package/types/lib/inventory/cyclonedx20.d.ts.map +1 -0
  175. package/types/lib/inventory/cyclonedx20Common.d.ts +102 -0
  176. package/types/lib/inventory/cyclonedx20Common.d.ts.map +1 -0
  177. package/types/lib/inventory/cyclonedx20Derived.d.ts +74 -0
  178. package/types/lib/inventory/cyclonedx20Derived.d.ts.map +1 -0
  179. package/types/lib/inventory/cyclonedx20Parties.d.ts +91 -0
  180. package/types/lib/inventory/cyclonedx20Parties.d.ts.map +1 -0
  181. package/types/lib/inventory/cyclonedx20Read.d.ts +51 -0
  182. package/types/lib/inventory/cyclonedx20Read.d.ts.map +1 -0
  183. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts +69 -0
  184. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts.map +1 -0
  185. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts +77 -0
  186. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts.map +1 -0
  187. package/types/lib/inventory/cyclonedx20ReadContext.d.ts +118 -0
  188. package/types/lib/inventory/cyclonedx20ReadContext.d.ts.map +1 -0
  189. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts +40 -0
  190. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts.map +1 -0
  191. package/types/lib/inventory/cyclonedx20ReadSections.d.ts +45 -0
  192. package/types/lib/inventory/cyclonedx20ReadSections.d.ts.map +1 -0
  193. package/types/lib/inventory/cyclonedx20Render.d.ts +53 -0
  194. package/types/lib/inventory/cyclonedx20Render.d.ts.map +1 -0
  195. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts +73 -0
  196. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts.map +1 -0
  197. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts +29 -0
  198. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts.map +1 -0
  199. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts +110 -0
  200. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts.map +1 -0
  201. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts +42 -0
  202. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts.map +1 -0
  203. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts +26 -0
  204. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts.map +1 -0
  205. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts +55 -0
  206. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts.map +1 -0
  207. package/types/lib/inventory/cyclonedx20RenderSections.d.ts +45 -0
  208. package/types/lib/inventory/cyclonedx20RenderSections.d.ts.map +1 -0
  209. package/types/lib/inventory/cyclonedx20Tables.d.ts +134 -0
  210. package/types/lib/inventory/cyclonedx20Tables.d.ts.map +1 -0
  211. package/types/lib/inventory/deps.d.ts +35 -2
  212. package/types/lib/inventory/deps.d.ts.map +1 -1
  213. package/types/lib/inventory/dosai.d.ts +19 -0
  214. package/types/lib/inventory/dosai.d.ts.map +1 -1
  215. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  216. package/types/lib/inventory/dotnetRuntime.d.ts +51 -4
  217. package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -1
  218. package/types/lib/inventory/ecosystemFormulationParsers.d.ts +73 -0
  219. package/types/lib/inventory/ecosystemFormulationParsers.d.ts.map +1 -0
  220. package/types/lib/inventory/envcontext.d.ts +22 -6
  221. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  222. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  223. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  224. package/types/lib/inventory/hbom.d.ts +11 -0
  225. package/types/lib/inventory/hbom.d.ts.map +1 -1
  226. package/types/lib/inventory/hbomLoader.d.ts +54 -0
  227. package/types/lib/inventory/hbomLoader.d.ts.map +1 -1
  228. package/types/lib/inventory/jvmLocalRepos.d.ts +5 -1
  229. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -1
  230. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  231. package/types/lib/inventory/occurrenceKey.d.ts +52 -0
  232. package/types/lib/inventory/occurrenceKey.d.ts.map +1 -0
  233. package/types/lib/inventory/pqcMigration.d.ts +84 -0
  234. package/types/lib/inventory/pqcMigration.d.ts.map +1 -0
  235. package/types/lib/inventory/rustFormulationParser.d.ts +7 -2
  236. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  237. package/types/lib/inventory/scalaCoords.d.ts.map +1 -1
  238. package/types/lib/inventory/scalaDedupe.d.ts +43 -0
  239. package/types/lib/inventory/scalaDedupe.d.ts.map +1 -0
  240. package/types/lib/inventory/source.d.ts +15 -0
  241. package/types/lib/inventory/source.d.ts.map +1 -1
  242. package/types/lib/parsers/huggingfaceManifest.d.ts +10 -1
  243. package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -1
  244. package/types/lib/parsers/huggingfaceUtils.d.ts +12 -0
  245. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  246. package/types/lib/parsers/kiroPermissions.d.ts +62 -0
  247. package/types/lib/parsers/kiroPermissions.d.ts.map +1 -0
  248. package/types/lib/parsers/vscodeExtensionId.d.ts +48 -0
  249. package/types/lib/parsers/vscodeExtensionId.d.ts.map +1 -0
  250. package/types/lib/server/server.d.ts.map +1 -1
  251. package/types/lib/stages/postgen/postgen.d.ts +7 -0
  252. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  253. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  254. package/types/lib/stages/postgen/specVersionCompat.d.ts +25 -0
  255. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  256. package/types/lib/validator/bomValidator.d.ts +5 -1
  257. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  258. package/types/lib/validator/complianceEngine.d.ts +4 -4
  259. package/types/lib/validator/complianceEngine.d.ts.map +1 -1
  260. package/types/lib/validator/complianceRules.d.ts +14 -2
  261. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  262. package/types/lib/validator/index.d.ts +10 -0
  263. package/types/lib/validator/index.d.ts.map +1 -1
  264. package/types/lib/validator/reporters/json.d.ts.map +1 -1
@@ -8,6 +8,7 @@ import { parse as _load } from "yaml";
8
8
  import { DEBUG_MODE } from "../core/activity.js";
9
9
  import { recordDegradation } from "../core/buildLedger.js";
10
10
  import { getAllFiles, safeExistsSync, safeSpawnSync } from "../core/fs.js";
11
+ import { sanitizeBomUrl } from "../core/propertySanitizer.js";
11
12
  import { PYTHON_STD_MODULES } from "../core/state.js";
12
13
  import { parseToml } from "../core/toml.js";
13
14
  import { tryBuildPurl } from "../inventory/purl.js";
@@ -1137,10 +1138,22 @@ export async function parsePyLockData(
1137
1138
  hasWorkspaces = true;
1138
1139
  }
1139
1140
  for (const amember of workspaceMembers) {
1140
- if (amember === parentComponent.name) {
1141
+ if (amember === parentComponent?.name) {
1142
+ continue;
1143
+ }
1144
+ // a lock file read without its pyproject.toml has no parent to hold
1145
+ // the members, which is said once for the whole lock file
1146
+ if (!parentComponent) {
1147
+ if (!workspaceWarningShown) {
1148
+ console.log(
1149
+ `The workspace members in ${lockFile || "the lock file"} are read without a pyproject.toml, so there is no project to list them under.`,
1150
+ );
1151
+ workspaceWarningShown = true;
1152
+ }
1141
1153
  continue;
1142
1154
  }
1143
1155
  if (workspaceComponentMap[amember]) {
1156
+ parentComponent.components = parentComponent.components || [];
1144
1157
  parentComponent.components.push(workspaceComponentMap[amember]);
1145
1158
  } else {
1146
1159
  if (!workspaceWarningShown) {
@@ -1629,14 +1642,40 @@ export async function parsePyLockData(
1629
1642
  }
1630
1643
 
1631
1644
  /**
1632
- * First version clause of a PEP 508 requirement: an operator and the version
1633
- * that follows it.
1645
+ * An exact PEP 440 version clause: `==` or `===` and a version with no
1646
+ * wildcard.
1634
1647
  *
1635
- * `===` (arbitrary equality) is tried before `==`, whitespace may separate the
1636
- * operator from the version, and a version may carry a PEP 440 epoch (`1!2.0`)
1637
- * or a local version label (`2.1.0+cpu`).
1648
+ * Whitespace may separate the operator from the version, and a version may
1649
+ * carry a PEP 440 epoch (`1!2.0`) or a local version label (`2.1.0+cpu`).
1638
1650
  */
1639
- const PEP440_VERSION_CLAUSE = /(===|==|!=|<=|>=|~=|<|>)\s*([0-9A-Za-z.*_+!-]*)/;
1651
+ const PEP440_EXACT_CLAUSE = /^(===?)\s*([0-9A-Za-z._+!-]+)$/;
1652
+
1653
+ /**
1654
+ * Version a PEP 508 requirement pins: the version of its only exact clause.
1655
+ *
1656
+ * A requirement with no exact clause, a wildcard (`==1.2.*`) or more than one
1657
+ * exact clause pins no version, so a range such as `>=1.0,<2` gives null
1658
+ * rather than one of its bounds. Per-requirement options such as
1659
+ * `--config-settings` are not clauses.
1660
+ *
1661
+ * @param {String} versionSpecifiers Version specifiers of the requirement
1662
+ * @returns {String|null} The pinned version, or null
1663
+ */
1664
+ function pinnedRequirementVersion(versionSpecifiers) {
1665
+ const specifiers = versionSpecifiers || "";
1666
+ const optionsIndex = specifiers.indexOf(" --");
1667
+ const clausesSource =
1668
+ optionsIndex === -1 ? specifiers : specifiers.substring(0, optionsIndex);
1669
+ let trimmed = clausesSource.trim();
1670
+ if (trimmed.startsWith("(") && trimmed.endsWith(")")) {
1671
+ trimmed = trimmed.substring(1, trimmed.length - 1);
1672
+ }
1673
+ const exactClauses = trimmed
1674
+ .split(",")
1675
+ .map((clause) => clause.trim().match(PEP440_EXACT_CLAUSE))
1676
+ .filter(Boolean);
1677
+ return exactClauses.length === 1 ? exactClauses[0][2] : null;
1678
+ }
1640
1679
 
1641
1680
  /**
1642
1681
  * Method to parse requirements.txt file. This must be replaced with atom parsedeps.
@@ -1825,7 +1864,7 @@ async function parseReqData(
1825
1864
 
1826
1865
  // Handle extras (e.g., package[extra1,extra2])
1827
1866
  let extras = null;
1828
- const extrasMatch = l.match(/^([a-zA-Z0-9_\-.]+)(\[([^\]]+)])?(.*)$/);
1867
+ const extrasMatch = l.match(/^([a-zA-Z0-9_\-.]+)(\s*\[([^\]]+)])?(.*)$/);
1829
1868
  if (extrasMatch) {
1830
1869
  const [, packageName, , extrasStr, versionSpecifiers] = extrasMatch;
1831
1870
  const name = packageName;
@@ -1836,14 +1875,7 @@ async function parseReqData(
1836
1875
  if (PYTHON_STD_MODULES.includes(name)) {
1837
1876
  continue;
1838
1877
  }
1839
- const versionMatch = versionSpecifiers.match(PEP440_VERSION_CLAUSE);
1840
- let version = null;
1841
- if (versionMatch) {
1842
- version = versionMatch[2].replaceAll("*", "0") || null;
1843
- if (version === "0") {
1844
- version = null;
1845
- }
1846
- }
1878
+ const version = pinnedRequirementVersion(versionSpecifiers);
1847
1879
  const apkg = {
1848
1880
  name,
1849
1881
  version,
@@ -1863,7 +1895,10 @@ async function parseReqData(
1863
1895
  value: extras.join(","),
1864
1896
  });
1865
1897
  }
1866
- if (versionSpecifiers && !versionSpecifiers.trim().startsWith("==")) {
1898
+ if (
1899
+ versionSpecifiers?.trim() &&
1900
+ (!version || !versionSpecifiers.trim().startsWith("=="))
1901
+ ) {
1867
1902
  properties.push({
1868
1903
  name: "cdx:pypi:versionSpecifiers",
1869
1904
  value: versionSpecifiers.trim(),
@@ -1900,12 +1935,7 @@ async function parseReqData(
1900
1935
  if (PYTHON_STD_MODULES.includes(name)) {
1901
1936
  continue;
1902
1937
  }
1903
- const versionMatch = versionSpecifiers.match(PEP440_VERSION_CLAUSE);
1904
- let version = null;
1905
- if (versionMatch) {
1906
- version = versionMatch[2].replaceAll("*", "0") || null;
1907
- if (version === "0") version = null;
1908
- }
1938
+ const version = pinnedRequirementVersion(versionSpecifiers);
1909
1939
  const apkg = {
1910
1940
  name,
1911
1941
  version,
@@ -1916,7 +1946,10 @@ async function parseReqData(
1916
1946
  if (licenses) {
1917
1947
  apkg.licenses = licenses;
1918
1948
  }
1919
- if (versionSpecifiers && !versionSpecifiers.trim().startsWith("==")) {
1949
+ if (
1950
+ versionSpecifiers?.trim() &&
1951
+ (!version || !versionSpecifiers.trim().startsWith("=="))
1952
+ ) {
1920
1953
  properties.push({
1921
1954
  name: "cdx:pypi:versionSpecifiers",
1922
1955
  value: versionSpecifiers.trim(),
@@ -2064,6 +2097,33 @@ function createPixiBomRef(packageData) {
2064
2097
  return `library:${packageData["name"]}:${packageData["version"] || ""}`;
2065
2098
  }
2066
2099
 
2100
+ /**
2101
+ * Return the download url of a pixi.lock package, without credentials, query
2102
+ * or fragment. A private conda channel can carry its access token in a
2103
+ * `/t/<token>/` path segment, so such a url is left out.
2104
+ *
2105
+ * @param {unknown} url Package url from pixi.lock
2106
+ * @returns {string|undefined} Distribution url
2107
+ */
2108
+ function pixiDistributionUrl(url) {
2109
+ if (typeof url !== "string" || !url.trim()) {
2110
+ return undefined;
2111
+ }
2112
+ const sanitized = sanitizeBomUrl(url);
2113
+ try {
2114
+ const parsed = new URL(sanitized);
2115
+ if (
2116
+ !["http:", "https:"].includes(parsed.protocol) ||
2117
+ parsed.pathname.split("/").includes("t")
2118
+ ) {
2119
+ return undefined;
2120
+ }
2121
+ } catch {
2122
+ return undefined;
2123
+ }
2124
+ return sanitized;
2125
+ }
2126
+
2067
2127
  /**
2068
2128
  * Method to parse pixi.lock data
2069
2129
  *
@@ -2153,10 +2213,13 @@ export function parsePixiLockFile(pixiLockFileName, path) {
2153
2213
  *
2154
2214
  */
2155
2215
  const purlTemplate = createPurlTemplate(packageData);
2216
+ const distributionUrl = pixiDistributionUrl(packageData["url"]);
2156
2217
  return {
2157
2218
  name: packageData["name"],
2158
2219
  version: packageData["version"],
2159
2220
  ...(purlTemplate ? { purl: purlTemplate } : {}),
2221
+ // The package download url becomes a distribution external reference.
2222
+ ...(distributionUrl ? { distribution: { url: distributionUrl } } : {}),
2160
2223
  type: "library",
2161
2224
  "bom-ref": createPixiBomRef(packageData),
2162
2225
  // "licenses": [
@@ -2164,10 +2227,8 @@ export function parsePixiLockFile(pixiLockFileName, path) {
2164
2227
  // "id": packageData["license"]
2165
2228
  // }]
2166
2229
  // ],
2167
- supplier: {
2168
- name: packageData["build"],
2169
- url: packageData["url"],
2170
- },
2230
+ // The conda build string is not a supplier, so it stays in
2231
+ // cdx:pixi:build below.
2171
2232
  // "hashes": [
2172
2233
  // {"md5": packageData["md5"]},
2173
2234
  // {"sha256": packageData["sha256"]}
@@ -506,27 +506,55 @@ function resolveCargoWorkspaceDependencyTarget(
506
506
  ) {
507
507
  return workspaceMemberMap.get(resolvedDependencyName);
508
508
  }
509
- const dependencyPath =
510
- dependencyNode &&
511
- typeof dependencyNode === "object" &&
512
- typeof dependencyNode.path === "string" &&
513
- dependencyNode.path
514
- ? resolve(dirname(cargoTomlFile), dependencyNode.path, "Cargo.toml")
515
- : undefined;
516
- if (!dependencyPath || !safeExistsSync(dependencyPath)) {
509
+ if (
510
+ !dependencyNode ||
511
+ typeof dependencyNode !== "object" ||
512
+ typeof dependencyNode.path !== "string" ||
513
+ !dependencyNode.path
514
+ ) {
515
+ return undefined;
516
+ }
517
+ // A member cannot combine `workspace = true` with its own `path`, so a path
518
+ // on such an entry came from [workspace.dependencies], and Cargo reads it
519
+ // relative to the workspace root rather than to the member.
520
+ const pathBaseFile =
521
+ dependencyNode.workspace === true && workspaceContext?.workspaceRootFile
522
+ ? workspaceContext.workspaceRootFile
523
+ : cargoTomlFile;
524
+ const dependencyPath = resolve(
525
+ dirname(pathBaseFile),
526
+ dependencyNode.path,
527
+ "Cargo.toml",
528
+ );
529
+ if (!safeExistsSync(dependencyPath)) {
517
530
  return undefined;
518
531
  }
519
532
  const dependencyCargoData = readCargoTomlData(dependencyPath);
520
- if (!dependencyCargoData) {
533
+ // A path dependency names a package; a virtual workspace root has none.
534
+ if (!dependencyCargoData?.package) {
521
535
  return undefined;
522
536
  }
537
+ // A path crate outside the current workspace inherits `*.workspace = true`
538
+ // fields from its own workspace, which resolveCargoWorkspaceContext finds by
539
+ // walking up from the crate. Handing it the current workspace would give
540
+ // the crate this workspace's version.
541
+ const workspaceRootDir = workspaceContext?.workspaceRootFile
542
+ ? resolve(dirname(workspaceContext.workspaceRootFile))
543
+ : undefined;
544
+ const dependencyDir = dirname(dependencyPath);
545
+ const isInsideWorkspace =
546
+ workspaceRootDir &&
547
+ (dependencyDir === workspaceRootDir ||
548
+ dependencyDir.startsWith(`${workspaceRootDir}${_sep}`));
523
549
  const dependencyIdentity = resolveCargoManifestPackageIdentity(
524
550
  dependencyPath,
525
551
  dependencyCargoData,
526
- {
527
- workspaceRootData: workspaceContext?.workspaceRootData,
528
- workspaceRootFile: workspaceContext?.workspaceRootFile,
529
- },
552
+ isInsideWorkspace
553
+ ? {
554
+ workspaceRootData: workspaceContext.workspaceRootData,
555
+ workspaceRootFile: workspaceContext.workspaceRootFile,
556
+ }
557
+ : {},
530
558
  );
531
559
  if (!dependencyIdentity?.name || !dependencyIdentity?.version) {
532
560
  return undefined;
@@ -656,6 +684,68 @@ function applyCargoDependencySpecMetadata(
656
684
  );
657
685
  }
658
686
 
687
+ /**
688
+ * Version to record for a Cargo.toml dependency whose requirement is the only
689
+ * thing known about it.
690
+ *
691
+ * A Cargo requirement is not a version: `serde = "1.0"` means `^1.0`, and even
692
+ * `"1.0.94"` means `^1.0.94`. The component version is therefore left out
693
+ * unless the whole requirement is an exact `=X.Y.Z` pin (with an optional
694
+ * prerelease and build label), which is the one requirement Cargo resolves to
695
+ * a single version. `=1.0` is not exact, because Cargo reads it as
696
+ * `>=1.0.0,<1.1.0`.
697
+ *
698
+ * When the requirement is not the node's `version` key, it is a `git+`, `path+`
699
+ * or `workspace` fallback from normalizeCargoDependencyVersion, and it is kept
700
+ * as it stands so the dependency stays traceable to its source.
701
+ *
702
+ * @param {Object|String} dependencyNode Dependency entry from the manifest
703
+ * @param {String} requirement Requirement string from normalizeCargoDependencyVersion
704
+ * @returns {String|undefined} The pinned version, or undefined
705
+ */
706
+ function cargoManifestComponentVersion(dependencyNode, requirement) {
707
+ if (
708
+ typeof dependencyNode === "object" &&
709
+ dependencyNode?.version !== requirement
710
+ ) {
711
+ return requirement;
712
+ }
713
+ const pin = requirement
714
+ .trim()
715
+ .match(/^=\s*(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)$/);
716
+ return pin ? pin[1] : undefined;
717
+ }
718
+
719
+ /**
720
+ * Version to record for a Cargo.toml dependency component once the dependency
721
+ * has been resolved to a crate on disk.
722
+ *
723
+ * A path dependency or a workspace dependency always builds against the
724
+ * version in the target crate's own `[package]` section; the requirement next
725
+ * to it (`local = { path = "local", version = "0.2" }`) matters only when the
726
+ * crate is published to a registry. Recording the resolved version keeps one
727
+ * component per crate: the manifest entry and the target's own manifest, which
728
+ * a recursive scan parses as well, then describe the same cargo purl and
729
+ * merge, instead of listing the crate once version-less and once resolved
730
+ * (issue #4719). The `cdx:cargo:path` property keeps the dependency
731
+ * traceable to its source.
732
+ *
733
+ * @param {Object|String} dependencyNode Dependency entry from the manifest
734
+ * @param {String} requirement Requirement string from normalizeCargoDependencyVersion
735
+ * @param {Object|undefined} resolvedTarget Identity of the resolved local crate
736
+ * @returns {String|undefined} The resolved version, or cargoManifestComponentVersion's answer
737
+ */
738
+ function cargoManifestDependencyComponentVersion(
739
+ dependencyNode,
740
+ requirement,
741
+ resolvedTarget,
742
+ ) {
743
+ if (resolvedTarget?.version) {
744
+ return resolvedTarget.version;
745
+ }
746
+ return cargoManifestComponentVersion(dependencyNode, requirement);
747
+ }
748
+
659
749
  function collectCargoManifestDependencyComponents(
660
750
  dependencyEntries,
661
751
  addPackageToList,
@@ -689,9 +779,21 @@ function collectCargoManifestDependencyComponents(
689
779
  continue;
690
780
  }
691
781
  const pkg = {
692
- name: dependencyName,
693
- version,
782
+ // A renamed local dependency (`alias = { path = "…", package = "real" }`)
783
+ // is the crate `real`; the alias only names it inside this manifest and
784
+ // stays on cdx:cargo:package. Keeping the alias with the resolved version
785
+ // would describe a crate that does not exist, or a different one on
786
+ // crates.io.
787
+ name: resolvedWorkspaceTarget?.name || dependencyName,
788
+ version: cargoManifestDependencyComponentVersion(
789
+ dependencyNode,
790
+ version,
791
+ resolvedWorkspaceTarget,
792
+ ),
694
793
  };
794
+ if (resolvedWorkspaceTarget?.group) {
795
+ pkg.group = resolvedWorkspaceTarget.group;
796
+ }
695
797
  applyCargoDependencySpecMetadata(
696
798
  pkg,
697
799
  dependencyNode,
@@ -10,6 +10,10 @@ import {
10
10
  detectAiModelVariants,
11
11
  normalizeDetectedVariants,
12
12
  } from "../../inventory/aiModelVariants.js";
13
+ import {
14
+ modelCreatorEntity,
15
+ modelCreatorFromOrganization,
16
+ } from "../../inventory/aiProviders.js";
13
17
  import { getLicenses } from "../../inventory/spdx.js";
14
18
  import {
15
19
  createHuggingFaceComponentReference,
@@ -26,6 +30,7 @@ import {
26
30
  toHuggingFaceAssetPath,
27
31
  toHuggingFaceAssetUrl,
28
32
  toHuggingFacePurl,
33
+ toHuggingFaceSupplier,
29
34
  } from "../../parsers/huggingfaceUtils.js";
30
35
 
31
36
  const CACHE_MISS = Symbol("huggingface-cache-miss");
@@ -1191,6 +1196,23 @@ export async function fetchHuggingFaceAssetInventory(
1191
1196
  payload?.sha ||
1192
1197
  (revision !== "HEAD" ? revision : payload?.lastModified),
1193
1198
  purl,
1199
+ // The repository namespace published the asset, so it is the
1200
+ // supplier. Its url is the organisation or user page, not the
1201
+ // repository. A model is credited to a creator as manufacturer only
1202
+ // when the namespace is that creator's own organisation, since
1203
+ // re-packagers publish models other organisations made.
1204
+ supplier:
1205
+ slashIndex > 0
1206
+ ? toHuggingFaceSupplier(normalizedRepoId.slice(0, slashIndex))
1207
+ : undefined,
1208
+ manufacturer:
1209
+ normalizedAssetType === "model" && slashIndex > 0
1210
+ ? modelCreatorEntity(
1211
+ modelCreatorFromOrganization(
1212
+ normalizedRepoId.slice(0, slashIndex),
1213
+ ),
1214
+ )
1215
+ : undefined,
1194
1216
  description:
1195
1217
  normalizedAssetType === "dataset"
1196
1218
  ? datasetDescriptionFromStats(payload)
@@ -16,6 +16,7 @@ import {
16
16
  } from "../core/pluginRun.js";
17
17
  import { isProjectToolingAllowed } from "../core/projectTooling.js";
18
18
  import { applyAlgorithmProperties } from "../inventory/cryptoAlgorithmFamily.js";
19
+ import { occurrenceLocationKey } from "../inventory/occurrenceKey.js";
19
20
  import { resolvePluginBinary } from "../inventory/plugins.js";
20
21
 
21
22
  const RUST_LANGUAGES = new Set(["rust", "rs", "rust-lang"]);
@@ -67,10 +68,7 @@ function positionLocation(position) {
67
68
  if (!position?.filename) {
68
69
  return undefined;
69
70
  }
70
- if (position.line && position.line > 0) {
71
- return `${position.filename}#${position.line}`;
72
- }
73
- return position.filename;
71
+ return occurrenceLocationKey(position.filename, position.line);
74
72
  }
75
73
 
76
74
  function purlWithoutVersion(purl) {
@@ -30,6 +30,7 @@ import { isProjectToolingAllowed } from "../core/projectTooling.js";
30
30
  import { parsetoolsFile } from "../inventory/atomUtils.js";
31
31
  import { applyAlgorithmProperties } from "../inventory/cryptoAlgorithmFamily.js";
32
32
  import { getJarClasses } from "../inventory/deps.js";
33
+ import { occurrenceLocationKey } from "../inventory/occurrenceKey.js";
33
34
  import {
34
35
  parseScalaArtifact,
35
36
  scalaCoordinateKey,
@@ -1984,7 +1985,7 @@ function addUsageEvidence(
1984
1985
  let added = false;
1985
1986
  for (const [file, lines] of files) {
1986
1987
  if (depth < lines.length && locations.size < cap) {
1987
- locations.add(`${file}#${lines[depth]}`);
1988
+ locations.add(occurrenceLocationKey(file, lines[depth]));
1988
1989
  added = true;
1989
1990
  }
1990
1991
  }