@cdxgen/cdxgen 13.0.1 → 13.2.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 (286) hide show
  1. package/README.md +30 -16
  2. package/bin/audit.js +6 -0
  3. package/bin/cdxgen.js +141 -7
  4. package/bin/evinse.js +3 -1
  5. package/data/component-tags.json +8 -1
  6. package/data/crypto-oid.json +88 -12
  7. package/data/frameworks-list.json +86 -1
  8. package/data/remediations.json +1167 -0
  9. package/data/rules/ai-bom.yaml +78 -0
  10. package/data/rules/asar-archives.yaml +1 -1
  11. package/data/rules/build-fidelity.yaml +782 -0
  12. package/data/rules/cargo-rust.yaml +132 -0
  13. package/data/rules/mcp-servers.yaml +122 -0
  14. package/data/rules/package-integrity.yaml +13 -13
  15. package/lib/audit/index.js +48 -4
  16. package/lib/audit/scoring.js +2 -8
  17. package/lib/audit/targets.js +6 -1
  18. package/lib/cli/bomAssembly.js +32 -1
  19. package/lib/cli/cliOptions.js +94 -7
  20. package/lib/cli/index.js +268 -49
  21. package/lib/cli/jsBom.js +91 -5
  22. package/lib/cli/jvmBom.js +1515 -1314
  23. package/lib/cli/managedBom.js +249 -28
  24. package/lib/cli/nativeBom.js +986 -10
  25. package/lib/core/activity.js +120 -2
  26. package/lib/core/buildLedger.js +799 -0
  27. package/lib/core/deferredExit.js +141 -0
  28. package/lib/core/env.js +108 -2
  29. package/lib/core/fs.js +60 -0
  30. package/lib/core/logger.js +12 -99
  31. package/lib/core/severity.js +16 -0
  32. package/lib/core/syncFileWriter.js +106 -0
  33. package/lib/core/userAgent.js +151 -0
  34. package/lib/ecosystems/cargoCache.js +364 -0
  35. package/lib/ecosystems/caxa.js +13 -5
  36. package/lib/ecosystems/cppEvidence.js +40 -2
  37. package/lib/ecosystems/ecosystems.js +250 -91
  38. package/lib/ecosystems/golem.js +11 -1
  39. package/lib/ecosystems/gradleutils.js +498 -17
  40. package/lib/ecosystems/kosi.js +1256 -0
  41. package/lib/ecosystems/parsers-crystal.js +179 -0
  42. package/lib/ecosystems/parsers-dotnet.js +69 -39
  43. package/lib/ecosystems/parsers-elm.js +816 -0
  44. package/lib/ecosystems/parsers-gleam.js +1 -1
  45. package/lib/ecosystems/parsers-js.js +26 -0
  46. package/lib/ecosystems/parsers-julia.js +268 -0
  47. package/lib/ecosystems/parsers-jvm.js +9 -0
  48. package/lib/ecosystems/parsers-lua.js +218 -0
  49. package/lib/ecosystems/parsers-misc.js +188 -2
  50. package/lib/ecosystems/parsers-nim.js +221 -0
  51. package/lib/ecosystems/parsers-opam.js +180 -0
  52. package/lib/ecosystems/parsers-perl.js +277 -0
  53. package/lib/ecosystems/parsers-python.js +172 -36
  54. package/lib/ecosystems/parsers-r.js +148 -0
  55. package/lib/ecosystems/parsers-rebar.js +411 -0
  56. package/lib/ecosystems/parsers-rust.js +944 -19
  57. package/lib/ecosystems/parsers-spack.js +204 -0
  58. package/lib/ecosystems/parsers-stack.js +264 -0
  59. package/lib/ecosystems/parsers-terraform.js +194 -0
  60. package/lib/ecosystems/pypiClassifiers.js +326 -0
  61. package/lib/ecosystems/pythonutils.js +49 -1
  62. package/lib/ecosystems/rubyutils.js +13 -0
  63. package/lib/ecosystems/rusi.js +126 -6
  64. package/lib/evinser/evinser.js +141 -13
  65. package/lib/helpers/agenticToolutils.js +925 -0
  66. package/lib/helpers/remote/dependency-track.js +81 -14
  67. package/lib/inventory/agentFormulationParser.js +57 -4
  68. package/lib/inventory/aiCollector.js +504 -10
  69. package/lib/inventory/aiInventory.js +12 -0
  70. package/lib/inventory/analyzer.js +488 -30
  71. package/lib/inventory/astWorker.js +6 -0
  72. package/lib/inventory/auditCategories.js +17 -0
  73. package/lib/inventory/cbomutils.js +30 -0
  74. package/lib/inventory/cdxrs.js +7 -2
  75. package/lib/inventory/communityAiConfigParser.js +215 -2
  76. package/lib/inventory/depsUtils.js +72 -0
  77. package/lib/inventory/dosai.js +508 -22
  78. package/lib/inventory/envcontext.js +215 -24
  79. package/lib/inventory/evidenceUtils.js +13 -0
  80. package/lib/inventory/fetchBatch.js +131 -2
  81. package/lib/inventory/fetchRate.js +74 -11
  82. package/lib/inventory/forgeEnricher.js +3 -2
  83. package/lib/inventory/jvmToolEnv.js +594 -0
  84. package/lib/inventory/mcpConfigParser.js +166 -2
  85. package/lib/inventory/mcpDiscovery.js +11 -0
  86. package/lib/inventory/mcpServerManifest.js +389 -0
  87. package/lib/inventory/mcpbBundle.js +419 -0
  88. package/lib/inventory/osinfo.js +1 -0
  89. package/lib/inventory/plugins.js +3 -0
  90. package/lib/inventory/protobom.js +54 -88
  91. package/lib/inventory/protobomLoader.js +4 -0
  92. package/lib/inventory/rustFormulationParser.js +1 -1
  93. package/lib/inventory/source.js +10 -4
  94. package/lib/inventory/stylePackageRefs.js +41 -0
  95. package/lib/inventory/svelteUtils.js +524 -0
  96. package/lib/inventory/toolRequirements.js +903 -0
  97. package/lib/managers/binary.js +24 -0
  98. package/lib/managers/docker.js +26 -5
  99. package/lib/parsers/huggingfaceUtils.js +15 -7
  100. package/lib/parsers/onnx.js +294 -0
  101. package/lib/parsers/safetensors.js +114 -0
  102. package/lib/server/server.js +1 -1
  103. package/lib/stages/postgen/annotationBuilder.js +97 -0
  104. package/lib/stages/postgen/auditBom.js +76 -79
  105. package/lib/stages/postgen/introspection/annotate.js +320 -0
  106. package/lib/stages/postgen/introspection/commandFacts.js +293 -0
  107. package/lib/stages/postgen/introspection/emit.js +192 -0
  108. package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
  109. package/lib/stages/postgen/introspection/markers.js +250 -0
  110. package/lib/stages/postgen/introspection/reflect.js +1214 -0
  111. package/lib/stages/postgen/introspection/report.js +1208 -0
  112. package/lib/stages/postgen/introspection/score.js +1067 -0
  113. package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
  114. package/lib/stages/postgen/parentEdge.js +156 -0
  115. package/lib/stages/postgen/postgen.js +284 -3
  116. package/lib/stages/postgen/ruleEngine.js +17 -0
  117. package/lib/stages/pregen/envAudit.js +42 -2
  118. package/lib/stages/pregen/pregen.js +1171 -6
  119. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
  120. package/lib/validator/bomValidator.js +11 -2
  121. package/package.json +26 -26
  122. package/types/lib/audit/index.d.ts.map +1 -1
  123. package/types/lib/audit/scoring.d.ts +2 -8
  124. package/types/lib/audit/scoring.d.ts.map +1 -1
  125. package/types/lib/audit/targets.d.ts.map +1 -1
  126. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  127. package/types/lib/cli/cliOptions.d.ts +2 -1
  128. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  129. package/types/lib/cli/index.d.ts +10 -2
  130. package/types/lib/cli/index.d.ts.map +1 -1
  131. package/types/lib/cli/jsBom.d.ts +23 -0
  132. package/types/lib/cli/jsBom.d.ts.map +1 -1
  133. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  134. package/types/lib/cli/managedBom.d.ts +33 -0
  135. package/types/lib/cli/managedBom.d.ts.map +1 -1
  136. package/types/lib/cli/nativeBom.d.ts +88 -8
  137. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  138. package/types/lib/core/activity.d.ts.map +1 -1
  139. package/types/lib/core/buildLedger.d.ts +272 -0
  140. package/types/lib/core/buildLedger.d.ts.map +1 -0
  141. package/types/lib/core/deferredExit.d.ts +84 -0
  142. package/types/lib/core/deferredExit.d.ts.map +1 -0
  143. package/types/lib/core/env.d.ts +43 -1
  144. package/types/lib/core/env.d.ts.map +1 -1
  145. package/types/lib/core/fs.d.ts +21 -0
  146. package/types/lib/core/fs.d.ts.map +1 -1
  147. package/types/lib/core/logger.d.ts +2 -11
  148. package/types/lib/core/logger.d.ts.map +1 -1
  149. package/types/lib/core/severity.d.ts +11 -0
  150. package/types/lib/core/severity.d.ts.map +1 -0
  151. package/types/lib/core/syncFileWriter.d.ts +33 -0
  152. package/types/lib/core/syncFileWriter.d.ts.map +1 -0
  153. package/types/lib/core/userAgent.d.ts +56 -0
  154. package/types/lib/core/userAgent.d.ts.map +1 -0
  155. package/types/lib/ecosystems/cargoCache.d.ts +75 -0
  156. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
  157. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  158. package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
  159. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  160. package/types/lib/ecosystems/ecosystems.d.ts +15 -1
  161. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  162. package/types/lib/ecosystems/gradleutils.d.ts +53 -0
  163. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  164. package/types/lib/ecosystems/kosi.d.ts +73 -0
  165. package/types/lib/ecosystems/kosi.d.ts.map +1 -0
  166. package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
  167. package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
  168. package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
  169. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  170. package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
  171. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
  172. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  173. package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
  174. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
  175. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  176. package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
  177. package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
  178. package/types/lib/ecosystems/parsers-misc.d.ts +23 -0
  179. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  180. package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
  181. package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
  182. package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
  183. package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
  184. package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
  185. package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
  186. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  187. package/types/lib/ecosystems/parsers-r.d.ts +35 -0
  188. package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
  189. package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
  190. package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
  191. package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
  192. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  193. package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
  194. package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
  195. package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
  196. package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
  197. package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
  198. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
  199. package/types/lib/ecosystems/pypiClassifiers.d.ts +80 -0
  200. package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
  201. package/types/lib/ecosystems/pythonutils.d.ts +21 -1
  202. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  203. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  204. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  205. package/types/lib/evinser/evinser.d.ts +34 -1
  206. package/types/lib/evinser/evinser.d.ts.map +1 -1
  207. package/types/lib/helpers/agenticToolutils.d.ts +87 -0
  208. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
  209. package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
  210. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  211. package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
  212. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  213. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  214. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  215. package/types/lib/inventory/auditCategories.d.ts +12 -0
  216. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  217. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  218. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  219. package/types/lib/inventory/depsUtils.d.ts.map +1 -1
  220. package/types/lib/inventory/dosai.d.ts +43 -0
  221. package/types/lib/inventory/dosai.d.ts.map +1 -1
  222. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  223. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  224. package/types/lib/inventory/fetchBatch.d.ts +43 -0
  225. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  226. package/types/lib/inventory/fetchRate.d.ts +9 -6
  227. package/types/lib/inventory/fetchRate.d.ts.map +1 -1
  228. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  229. package/types/lib/inventory/jvmToolEnv.d.ts +190 -0
  230. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
  231. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  232. package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
  233. package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
  234. package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
  235. package/types/lib/inventory/mcpbBundle.d.ts +19 -0
  236. package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
  237. package/types/lib/inventory/osinfo.d.ts.map +1 -1
  238. package/types/lib/inventory/plugins.d.ts.map +1 -1
  239. package/types/lib/inventory/protobom.d.ts +18 -13
  240. package/types/lib/inventory/protobom.d.ts.map +1 -1
  241. package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
  242. package/types/lib/inventory/source.d.ts.map +1 -1
  243. package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
  244. package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
  245. package/types/lib/inventory/svelteUtils.d.ts +124 -0
  246. package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
  247. package/types/lib/inventory/toolRequirements.d.ts +216 -0
  248. package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
  249. package/types/lib/managers/binary.d.ts.map +1 -1
  250. package/types/lib/managers/docker.d.ts.map +1 -1
  251. package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
  252. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  253. package/types/lib/parsers/onnx.d.ts +28 -0
  254. package/types/lib/parsers/onnx.d.ts.map +1 -0
  255. package/types/lib/parsers/safetensors.d.ts +22 -0
  256. package/types/lib/parsers/safetensors.d.ts.map +1 -0
  257. package/types/lib/stages/postgen/annotationBuilder.d.ts +53 -0
  258. package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
  259. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  260. package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
  261. package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
  262. package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
  263. package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
  264. package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
  265. package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
  266. package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
  267. package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
  268. package/types/lib/stages/postgen/introspection/markers.d.ts +58 -0
  269. package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
  270. package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
  271. package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
  272. package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
  273. package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
  274. package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
  275. package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
  276. package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
  277. package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
  278. package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
  279. package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
  280. package/types/lib/stages/postgen/postgen.d.ts +35 -2
  281. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  282. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  283. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  284. package/types/lib/stages/pregen/pregen.d.ts +131 -0
  285. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  286. package/types/lib/validator/bomValidator.d.ts.map +1 -1
@@ -0,0 +1,179 @@
1
+ import { readFileSync } from "node:fs";
2
+
3
+ import { parse as loadYaml } from "yaml";
4
+
5
+ import { tryBuildPurl } from "../inventory/purl.js";
6
+
7
+ /**
8
+ * Crystal parser for `shard.yml` and `shard.lock`.
9
+ *
10
+ * Shards resolves dependencies declared in `shard.yml` and records the
11
+ * resolved set in `shard.lock`, which pins each dependency's version and
12
+ * source repository. Both files are YAML and are read with the standard
13
+ * parser.
14
+ *
15
+ * No `crystal` purl type is registered, so shards are identified as generic
16
+ * packages carrying a `cdx:purl:proposedType=crystal` property, following
17
+ * the convention used for nix and zig. This keeps the BOM valid today and
18
+ * allows a mechanical migration if a type is registered upstream.
19
+ */
20
+
21
+ /**
22
+ * Parse a Crystal project from `shard.yml` and optional `shard.lock`.
23
+ *
24
+ * @param {string} shardYmlFile Path to `shard.yml`
25
+ * @param {string} [shardLockFile] Path to `shard.lock`, if present
26
+ * @returns {{ pkgList: object[], dependencies: object[], parentComponent: object, rootInputs?: string[] }}
27
+ */
28
+ export function parseShardsProject(shardYmlFile, shardLockFile) {
29
+ const manifest = readYamlFile(shardYmlFile);
30
+
31
+ const parentComponent = {};
32
+ if (typeof manifest?.name === "string" && manifest.name) {
33
+ parentComponent.type = "application";
34
+ parentComponent.name = manifest.name;
35
+ if (typeof manifest.version === "string" && manifest.version) {
36
+ parentComponent.version = manifest.version;
37
+ }
38
+ parentComponent.description =
39
+ typeof manifest.description === "string" && manifest.description
40
+ ? manifest.description
41
+ : `Crystal shard: ${manifest.name}`;
42
+ parentComponent.properties = [
43
+ { name: "internal:SrcFile", value: shardYmlFile },
44
+ ];
45
+ if (typeof manifest.crystal === "string" && manifest.crystal) {
46
+ parentComponent.properties.push({
47
+ name: "cdx:crystal:languageVersion",
48
+ value: manifest.crystal,
49
+ });
50
+ }
51
+ }
52
+
53
+ const declared =
54
+ manifest?.dependencies && typeof manifest.dependencies === "object"
55
+ ? Object.keys(manifest.dependencies)
56
+ : [];
57
+ const devDeclared =
58
+ manifest?.development_dependencies &&
59
+ typeof manifest.development_dependencies === "object"
60
+ ? Object.keys(manifest.development_dependencies)
61
+ : [];
62
+
63
+ let lock;
64
+ if (shardLockFile) {
65
+ lock = readYamlFile(shardLockFile);
66
+ }
67
+ const lockedShards =
68
+ lock?.shards && typeof lock.shards === "object" ? lock.shards : undefined;
69
+ if (!lockedShards) {
70
+ // Lockless fallback: emit declared dependencies without resolved versions.
71
+ const pkgList = [
72
+ ...declared.map((name) =>
73
+ crystalPackage(name, undefined, {
74
+ direct: true,
75
+ dev: false,
76
+ srcFile: shardYmlFile,
77
+ }),
78
+ ),
79
+ ...devDeclared.map((name) =>
80
+ crystalPackage(name, undefined, {
81
+ direct: true,
82
+ dev: true,
83
+ srcFile: shardYmlFile,
84
+ }),
85
+ ),
86
+ ];
87
+ return {
88
+ pkgList,
89
+ dependencies: [],
90
+ parentComponent,
91
+ rootInputs: pkgList.map((p) => p["bom-ref"]),
92
+ };
93
+ }
94
+
95
+ const directNames = new Set(declared);
96
+ const devNames = new Set(devDeclared);
97
+ const pkgList = [];
98
+ const refByName = new Map();
99
+ for (const [name, entry] of Object.entries(lockedShards)) {
100
+ const version =
101
+ typeof entry?.version === "string" && entry.version
102
+ ? entry.version
103
+ : undefined;
104
+ const pkg = crystalPackage(name, version, {
105
+ direct: directNames.has(name),
106
+ dev: devNames.has(name),
107
+ srcFile: shardLockFile,
108
+ });
109
+ pkgList.push(pkg);
110
+ refByName.set(name, pkg["bom-ref"]);
111
+ }
112
+
113
+ // shard.lock records no dependency graph between shards, so the root links
114
+ // to the declared set and every other shard stands on its own version pin.
115
+ const rootInputs = [...directNames, ...devNames]
116
+ .map((name) => refByName.get(name))
117
+ .filter(Boolean);
118
+ return { pkgList, dependencies: [], parentComponent, rootInputs };
119
+ }
120
+
121
+ /**
122
+ * Build a component-like package record for a Crystal shard.
123
+ *
124
+ * @param {string} name Shard name
125
+ * @param {string|undefined} version Resolved version, if known
126
+ * @param {object} opts Extra context (`direct`, `dev`, `srcFile`)
127
+ * @returns {object} Package record
128
+ */
129
+ function crystalPackage(name, version, opts) {
130
+ const purl = tryBuildPurl({
131
+ type: "generic",
132
+ name,
133
+ version: version || undefined,
134
+ });
135
+ const properties = [
136
+ { name: "internal:SrcFile", value: opts.srcFile },
137
+ { name: "cdx:purl:proposedType", value: "crystal" },
138
+ ];
139
+ if (opts.direct) {
140
+ properties.push({
141
+ name: "cdx:crystal:dependency",
142
+ value: opts.dev ? "direct-dev" : "direct",
143
+ });
144
+ } else {
145
+ properties.push({ name: "cdx:crystal:dependency", value: "transitive" });
146
+ }
147
+ if (opts.dev) {
148
+ properties.push({ name: "cdx:crystal:scope", value: "development" });
149
+ }
150
+ const pkg = {
151
+ name,
152
+ ...(version ? { version } : {}),
153
+ type: "library",
154
+ scope: opts.dev ? "optional" : "required",
155
+ properties,
156
+ };
157
+ if (purl) {
158
+ pkg.purl = purl;
159
+ pkg["bom-ref"] = decodeURIComponent(purl);
160
+ } else {
161
+ pkg["bom-ref"] = `library:${name}:${version || ""}`;
162
+ }
163
+ return pkg;
164
+ }
165
+
166
+ /**
167
+ * Read and parse a YAML file, warning instead of throwing.
168
+ *
169
+ * @param {string} filePath File to read
170
+ * @returns {object|undefined} Parsed value
171
+ */
172
+ function readYamlFile(filePath) {
173
+ try {
174
+ return loadYaml(readFileSync(filePath, "utf-8"));
175
+ } catch (error) {
176
+ console.warn(`Failed to parse ${filePath}: ${error.message}`);
177
+ return undefined;
178
+ }
179
+ }
@@ -13,6 +13,34 @@ import { findLicenseId } from "../inventory/spdx.js";
13
13
  import { xml2js } from "../parsers/xml.js";
14
14
  import { extractPackageInfoFromHintPath } from "./dotnetutils.js";
15
15
 
16
+ /**
17
+ * Apply a version to a NuGet component so that the `version` field and the purl
18
+ * always agree.
19
+ *
20
+ * A .NET manifest can state a version that is not a version: an MSBuild property
21
+ * such as `$(JsonVersion)`, a range such as `[3.13.3,4.0)`, a wildcard such as
22
+ * `2.0.*`, or nothing at all. Such a declaration cannot go into a purl, so a
23
+ * component that kept it in `version` while its purl carried none described one
24
+ * package two ways. A scanner range matching on the versionless purl then reports
25
+ * every advisory published against the package name.
26
+ *
27
+ * The declaration is not lost: callers record it as `cdx:nuget:declared_version_range`.
28
+ *
29
+ * @param {Object} pkg Component to update in place. `name` must already be set.
30
+ * @param {String} [version] Version as stated or resolved, concrete or not
31
+ *
32
+ * @returns {Object} The same component
33
+ */
34
+ export function applyNugetVersion(pkg, version) {
35
+ const concrete = concreteVersion(version);
36
+ if (concrete) {
37
+ pkg.version = concrete;
38
+ } else {
39
+ delete pkg.version;
40
+ }
41
+ return applyPurl(pkg, nugetPurl(pkg.name, concrete));
42
+ }
43
+
16
44
  /**
17
45
  * Method to parse .nupkg files
18
46
  *
@@ -140,16 +168,22 @@ export function parseNuspecData(nupkgFile, nuspecData) {
140
168
  continue;
141
169
  }
142
170
  groupPkg.name = agroupdep.id;
171
+ let declaredRange;
143
172
  if (agroupdep?.version) {
144
173
  let versionStr = agroupdep.version;
145
174
  // version could have square brackets around them
146
175
  if (versionStr.startsWith("[") && versionStr.endsWith("]")) {
147
176
  versionStr = versionStr.replace(/[\[\]]/g, "");
148
177
  }
149
- groupPkg.version = versionStr;
150
- applyPurl(groupPkg, nugetPurl(groupPkg.name, versionStr));
178
+ // A nuspec dependency states the range the package accepts, not the
179
+ // version installed. Anything that is not a single version stays a
180
+ // declaration so the component and its purl agree - #4359.
181
+ if (!concreteVersion(versionStr)) {
182
+ declaredRange = agroupdep.version;
183
+ }
184
+ applyNugetVersion(groupPkg, versionStr);
151
185
  } else {
152
- applyPurl(groupPkg, nugetPurl(groupPkg.name));
186
+ applyNugetVersion(groupPkg);
153
187
  }
154
188
  groupPkg.scope = "optional";
155
189
  groupPkg.properties = [
@@ -158,6 +192,12 @@ export function parseNuspecData(nupkgFile, nuspecData) {
158
192
  value: nupkgFile,
159
193
  },
160
194
  ];
195
+ if (declaredRange) {
196
+ groupPkg.properties.push({
197
+ name: "cdx:nuget:declared_version_range",
198
+ value: declaredRange,
199
+ });
200
+ }
161
201
  if (targetFramework) {
162
202
  groupPkg.properties.push({
163
203
  name: "cdx:dotnet:target_framework",
@@ -243,20 +283,15 @@ export function parseCsPkgData(pkgData, pkgFile, pkgNameVersions = {}) {
243
283
  // so the declaration stays visible, and the version is resolved from a
244
284
  // more precise manifest so the purl names the package that is present.
245
285
  let declaredRange;
246
- if (!pkg.version || pkg.version.includes("$(")) {
286
+ if (!concreteVersion(pkg.version)) {
247
287
  confidence = 0.5;
288
+ declaredRange = pkg.version;
248
289
  // Backfill from a version already resolved by a more precise manifest.
249
290
  // Concrete versions are never overridden - a disagreement means both
250
291
  // versions must be tracked.
251
- pkg.version = pkgNameVersions[pkg.name] || undefined;
252
- } else if (/[[\](),*]/.test(pkg.version)) {
253
- confidence = 0.5;
254
- if (pkgNameVersions[pkg.name]) {
255
- declaredRange = pkg.version;
256
- pkg.version = pkgNameVersions[pkg.name];
257
- }
292
+ pkg.version = pkgNameVersions[pkg.name];
258
293
  }
259
- applyPurl(pkg, nugetPurl(pkg.name, pkg.version));
294
+ applyNugetVersion(pkg, pkg.version);
260
295
  if (pkgFile) {
261
296
  pkg.properties = [
262
297
  {
@@ -712,23 +747,17 @@ export function parseCsProjData(
712
747
  const exactPin = pkg.version.match(/^\[([^,[\]()]+)\]$/);
713
748
  if (exactPin) {
714
749
  pkg.version = exactPin[1];
715
- } else if (
716
- pkg.version.includes("$(") ||
717
- /[*[\](),]/.test(pkg.version)
718
- ) {
719
- const resolved =
750
+ } else if (!concreteVersion(pkg.version)) {
751
+ declaredRange = pkg.version;
752
+ pkg.version =
720
753
  pkgNameVersions[pkg.name] ||
721
754
  centralVersions[pkg.name.toLowerCase()];
722
- if (resolved) {
723
- declaredRange = pkg.version;
724
- pkg.version = resolved;
725
- }
726
755
  }
727
756
  }
728
757
  // A version that could not be resolved from any source must not be
729
758
  // stringified into the purl - `@undefined` is not a version and makes the
730
759
  // component unmatchable. Mirrors parseCsPkgData.
731
- applyPurl(pkg, nugetPurl(pkg.name, pkg.version));
760
+ applyNugetVersion(pkg, pkg.version);
732
761
  if (projFile) {
733
762
  pkg.properties = [
734
763
  {
@@ -821,26 +850,27 @@ export function parseCsProjData(
821
850
  });
822
851
  }
823
852
  const version = hintVersion ?? assemblyVersion;
824
- if (version) {
825
- pkg.version = version;
826
- applyPurl(pkg, nugetPurl(pkg.name, version));
827
- } else {
828
- applyPurl(pkg, nugetPurl(pkg.name));
829
- if (
830
- pkg.name.startsWith("System.") ||
853
+ // An assembly reference that states no version anywhere - no HintPath to
854
+ // read one from and no `Version=` on the Include - leaves the component
855
+ // versionless in both fields. The registry's latest release is not a
856
+ // stand-in: `<Reference>` resolves an assembly from a targeting pack, a
857
+ // lib folder or the GAC, and the project does not say which package it
858
+ // came from. See #4359.
859
+ applyNugetVersion(pkg, version);
860
+ if (
861
+ !pkg.version &&
862
+ (pkg.name.startsWith("System.") ||
831
863
  pkg.name.startsWith("Mono.") ||
832
- pkg.name.startsWith("Microsoft.")
833
- ) {
834
- // If this is a System package, then track the target frameworks
835
- for (const tf of projectTargetFrameworks) {
836
- pkg.properties.push({
837
- name: "cdx:dotnet:target_framework",
838
- value: tf,
839
- });
840
- }
864
+ pkg.name.startsWith("Microsoft."))
865
+ ) {
866
+ // If this is a System package, then track the target frameworks
867
+ for (const tf of projectTargetFrameworks) {
868
+ pkg.properties.push({
869
+ name: "cdx:dotnet:target_framework",
870
+ value: tf,
871
+ });
841
872
  }
842
873
  }
843
- pkg["bom-ref"] = pkg.purl;
844
874
  if (assemblyName) {
845
875
  pkg.properties.push({
846
876
  name: "cdx:dotnet:assembly_name",