@cdxgen/cdxgen 13.1.0 → 13.3.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 (283) hide show
  1. package/README.md +32 -15
  2. package/bin/cdxgen.js +243 -122
  3. package/bin/evinse.js +7 -0
  4. package/bin/sign.js +88 -14
  5. package/bin/tracebom.js +64 -4
  6. package/bin/validate.js +29 -7
  7. package/bin/verify.js +79 -39
  8. package/data/crypto-oid.json +88 -12
  9. package/data/remediations.json +47 -0
  10. package/data/rules/ai-bom.yaml +78 -0
  11. package/data/rules/cargo-rust.yaml +132 -0
  12. package/data/rules/mcp-servers.yaml +122 -0
  13. package/lib/audit/targets.js +6 -1
  14. package/lib/cli/bomAssembly.js +117 -15
  15. package/lib/cli/cliOptions.js +93 -1
  16. package/lib/cli/index.js +235 -5
  17. package/lib/cli/jsBom.js +108 -26
  18. package/lib/cli/jvmBom.js +68 -12
  19. package/lib/cli/managedBom.js +264 -41
  20. package/lib/cli/nativeBom.js +923 -15
  21. package/lib/core/activity.js +120 -2
  22. package/lib/core/env.js +44 -7
  23. package/lib/core/fs.js +174 -6
  24. package/lib/core/metadataProperties.js +97 -0
  25. package/lib/core/projectConfig.js +31 -4
  26. package/lib/core/userAgent.js +151 -0
  27. package/lib/ecosystems/bunutils.js +269 -101
  28. package/lib/ecosystems/cargoCache.js +364 -0
  29. package/lib/ecosystems/caxa.js +447 -7
  30. package/lib/ecosystems/cmakeResolver.js +66 -0
  31. package/lib/ecosystems/cppEvidence.js +104 -33
  32. package/lib/ecosystems/ecosystems.js +260 -99
  33. package/lib/ecosystems/golem.js +11 -1
  34. package/lib/ecosystems/gradleutils.js +185 -9
  35. package/lib/ecosystems/jsEvidence.js +19 -20
  36. package/lib/ecosystems/kosi.js +1263 -0
  37. package/lib/ecosystems/npmutils.js +110 -9
  38. package/lib/ecosystems/parsers-crystal.js +179 -0
  39. package/lib/ecosystems/parsers-dotnet.js +120 -49
  40. package/lib/ecosystems/parsers-elm.js +816 -0
  41. package/lib/ecosystems/parsers-gleam.js +1 -1
  42. package/lib/ecosystems/parsers-go.js +47 -9
  43. package/lib/ecosystems/parsers-js.js +171 -50
  44. package/lib/ecosystems/parsers-julia.js +268 -0
  45. package/lib/ecosystems/parsers-lua.js +218 -0
  46. package/lib/ecosystems/parsers-misc.js +919 -161
  47. package/lib/ecosystems/parsers-nim.js +221 -0
  48. package/lib/ecosystems/parsers-opam.js +180 -0
  49. package/lib/ecosystems/parsers-perl.js +277 -0
  50. package/lib/ecosystems/parsers-python.js +27 -15
  51. package/lib/ecosystems/parsers-r.js +148 -0
  52. package/lib/ecosystems/parsers-rebar.js +411 -0
  53. package/lib/ecosystems/parsers-rust.js +954 -33
  54. package/lib/ecosystems/parsers-spack.js +204 -0
  55. package/lib/ecosystems/parsers-stack.js +264 -0
  56. package/lib/ecosystems/parsers-terraform.js +194 -0
  57. package/lib/ecosystems/rubyutils.js +1426 -166
  58. package/lib/ecosystems/rusi.js +158 -7
  59. package/lib/evinser/evinser.js +547 -129
  60. package/lib/evinser/swiftsem.js +1914 -302
  61. package/lib/helpers/agenticToolutils.js +925 -0
  62. package/lib/helpers/bomSigner.js +652 -221
  63. package/lib/inventory/agentFormulationParser.js +57 -4
  64. package/lib/inventory/aiCollector.js +504 -10
  65. package/lib/inventory/aiInventory.js +12 -0
  66. package/lib/inventory/analyzer.js +488 -30
  67. package/lib/inventory/astWorker.js +6 -0
  68. package/lib/inventory/atomUtils.js +568 -31
  69. package/lib/inventory/auditCategories.js +8 -0
  70. package/lib/inventory/cbomutils.js +30 -0
  71. package/lib/inventory/cdxrs.js +110 -14
  72. package/lib/inventory/communityAiConfigParser.js +215 -2
  73. package/lib/inventory/depsUtils.js +183 -11
  74. package/lib/inventory/display.js +4 -5
  75. package/lib/inventory/dosai.js +627 -13
  76. package/lib/inventory/dynamic.js +39 -1
  77. package/lib/inventory/envcontext.js +9 -6
  78. package/lib/inventory/evidenceUtils.js +26 -5
  79. package/lib/inventory/fetchBatch.js +190 -19
  80. package/lib/inventory/fetchRate.js +74 -11
  81. package/lib/inventory/forgeEnricher.js +3 -2
  82. package/lib/inventory/jvmToolEnv.js +6 -5
  83. package/lib/inventory/mcpConfigParser.js +166 -2
  84. package/lib/inventory/mcpDiscovery.js +11 -0
  85. package/lib/inventory/mcpServerManifest.js +389 -0
  86. package/lib/inventory/mcpbBundle.js +419 -0
  87. package/lib/inventory/plugins.js +3 -0
  88. package/lib/inventory/protobom.js +62 -89
  89. package/lib/inventory/protobomLoader.js +4 -0
  90. package/lib/inventory/purl.js +152 -3
  91. package/lib/inventory/rustFormulationParser.js +1 -1
  92. package/lib/inventory/sbomDocument.js +2 -70
  93. package/lib/inventory/source.js +47 -23
  94. package/lib/inventory/stylePackageRefs.js +41 -0
  95. package/lib/inventory/svelteUtils.js +524 -0
  96. package/lib/inventory/traceRunner.js +210 -21
  97. package/lib/managers/binary.js +115 -24
  98. package/lib/managers/docker.js +440 -20
  99. package/lib/managers/oci.js +39 -1
  100. package/lib/parsers/cmakeLists.js +452 -0
  101. package/lib/parsers/huggingfaceUtils.js +15 -7
  102. package/lib/parsers/largeJson.js +454 -0
  103. package/lib/parsers/npmrc.js +19 -1
  104. package/lib/parsers/onnx.js +294 -0
  105. package/lib/parsers/safetensors.js +114 -0
  106. package/lib/stages/postgen/introspection/markers.js +29 -6
  107. package/lib/stages/postgen/postgen.js +237 -16
  108. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
  109. package/lib/validator/bomValidator.js +6 -16
  110. package/lib/validator/index.js +48 -10
  111. package/lib/validator/reporters/console.js +3 -0
  112. package/lib/validator/reporters/json.js +1 -0
  113. package/lib/validator/reporters/sarif.js +7 -0
  114. package/package.json +27 -27
  115. package/types/lib/audit/targets.d.ts.map +1 -1
  116. package/types/lib/cli/bomAssembly.d.ts +2 -1
  117. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  118. package/types/lib/cli/cliOptions.d.ts +33 -1
  119. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  120. package/types/lib/cli/index.d.ts +2 -2
  121. package/types/lib/cli/index.d.ts.map +1 -1
  122. package/types/lib/cli/jsBom.d.ts +23 -0
  123. package/types/lib/cli/jsBom.d.ts.map +1 -1
  124. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  125. package/types/lib/cli/managedBom.d.ts +33 -0
  126. package/types/lib/cli/managedBom.d.ts.map +1 -1
  127. package/types/lib/cli/nativeBom.d.ts +88 -8
  128. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  129. package/types/lib/core/activity.d.ts.map +1 -1
  130. package/types/lib/core/env.d.ts +21 -0
  131. package/types/lib/core/env.d.ts.map +1 -1
  132. package/types/lib/core/fs.d.ts +55 -0
  133. package/types/lib/core/fs.d.ts.map +1 -1
  134. package/types/lib/core/metadataProperties.d.ts +39 -0
  135. package/types/lib/core/metadataProperties.d.ts.map +1 -0
  136. package/types/lib/core/projectConfig.d.ts +6 -2
  137. package/types/lib/core/projectConfig.d.ts.map +1 -1
  138. package/types/lib/core/userAgent.d.ts +56 -0
  139. package/types/lib/core/userAgent.d.ts.map +1 -0
  140. package/types/lib/ecosystems/bunutils.d.ts +12 -5
  141. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  142. package/types/lib/ecosystems/cargoCache.d.ts +75 -0
  143. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
  144. package/types/lib/ecosystems/caxa.d.ts +14 -0
  145. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  146. package/types/lib/ecosystems/cmakeResolver.d.ts +23 -0
  147. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  148. package/types/lib/ecosystems/cppEvidence.d.ts +2 -2
  149. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  150. package/types/lib/ecosystems/ecosystems.d.ts +15 -1
  151. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  152. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  153. package/types/lib/ecosystems/gradleutils.d.ts +40 -4
  154. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  155. package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
  156. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  157. package/types/lib/ecosystems/kosi.d.ts +86 -0
  158. package/types/lib/ecosystems/kosi.d.ts.map +1 -0
  159. package/types/lib/ecosystems/npmutils.d.ts +42 -0
  160. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  161. package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
  162. package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
  163. package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
  164. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  165. package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
  166. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
  167. package/types/lib/ecosystems/parsers-go.d.ts +10 -0
  168. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  169. package/types/lib/ecosystems/parsers-js.d.ts +10 -4
  170. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  171. package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
  172. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
  173. package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
  174. package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
  175. package/types/lib/ecosystems/parsers-misc.d.ts +90 -3
  176. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  177. package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
  178. package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
  179. package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
  180. package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
  181. package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
  182. package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
  183. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  184. package/types/lib/ecosystems/parsers-r.d.ts +35 -0
  185. package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
  186. package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
  187. package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
  188. package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
  189. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  190. package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
  191. package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
  192. package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
  193. package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
  194. package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
  195. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
  196. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  197. package/types/lib/ecosystems/rubyutils.d.ts +143 -2
  198. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  199. package/types/lib/ecosystems/rusi.d.ts +13 -0
  200. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  201. package/types/lib/evinser/evinser.d.ts +81 -2
  202. package/types/lib/evinser/evinser.d.ts.map +1 -1
  203. package/types/lib/evinser/swiftsem.d.ts +280 -15
  204. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  205. package/types/lib/helpers/agenticToolutils.d.ts +87 -0
  206. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
  207. package/types/lib/helpers/bomSigner.d.ts +106 -13
  208. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  209. package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
  210. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  211. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  212. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  213. package/types/lib/inventory/atomUtils.d.ts +145 -3
  214. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  215. package/types/lib/inventory/auditCategories.d.ts +4 -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/cdxrs.d.ts +8 -3
  219. package/types/lib/inventory/cdxrs.d.ts.map +1 -1
  220. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  221. package/types/lib/inventory/depsUtils.d.ts +15 -0
  222. package/types/lib/inventory/depsUtils.d.ts.map +1 -1
  223. package/types/lib/inventory/display.d.ts.map +1 -1
  224. package/types/lib/inventory/dosai.d.ts +87 -1
  225. package/types/lib/inventory/dosai.d.ts.map +1 -1
  226. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  227. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  228. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  229. package/types/lib/inventory/fetchBatch.d.ts +34 -0
  230. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  231. package/types/lib/inventory/fetchRate.d.ts +9 -6
  232. package/types/lib/inventory/fetchRate.d.ts.map +1 -1
  233. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  234. package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
  235. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
  236. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  237. package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
  238. package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
  239. package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
  240. package/types/lib/inventory/mcpbBundle.d.ts +19 -0
  241. package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
  242. package/types/lib/inventory/plugins.d.ts.map +1 -1
  243. package/types/lib/inventory/protobom.d.ts +18 -13
  244. package/types/lib/inventory/protobom.d.ts.map +1 -1
  245. package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
  246. package/types/lib/inventory/purl.d.ts +71 -2
  247. package/types/lib/inventory/purl.d.ts.map +1 -1
  248. package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
  249. package/types/lib/inventory/source.d.ts.map +1 -1
  250. package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
  251. package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
  252. package/types/lib/inventory/svelteUtils.d.ts +124 -0
  253. package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
  254. package/types/lib/inventory/traceRunner.d.ts +47 -1
  255. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  256. package/types/lib/managers/binary.d.ts +30 -1
  257. package/types/lib/managers/binary.d.ts.map +1 -1
  258. package/types/lib/managers/docker.d.ts +29 -0
  259. package/types/lib/managers/docker.d.ts.map +1 -1
  260. package/types/lib/managers/oci.d.ts +14 -0
  261. package/types/lib/managers/oci.d.ts.map +1 -1
  262. package/types/lib/parsers/cmakeLists.d.ts +76 -0
  263. package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
  264. package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
  265. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  266. package/types/lib/parsers/largeJson.d.ts +52 -0
  267. package/types/lib/parsers/largeJson.d.ts.map +1 -0
  268. package/types/lib/parsers/npmrc.d.ts +5 -0
  269. package/types/lib/parsers/npmrc.d.ts.map +1 -1
  270. package/types/lib/parsers/onnx.d.ts +28 -0
  271. package/types/lib/parsers/onnx.d.ts.map +1 -0
  272. package/types/lib/parsers/safetensors.d.ts +22 -0
  273. package/types/lib/parsers/safetensors.d.ts.map +1 -0
  274. package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
  275. package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
  276. package/types/lib/stages/postgen/postgen.d.ts +33 -0
  277. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  278. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  279. package/types/lib/validator/index.d.ts +10 -1
  280. package/types/lib/validator/index.d.ts.map +1 -1
  281. package/types/lib/validator/reporters/console.d.ts.map +1 -1
  282. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  283. package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
@@ -215,6 +215,112 @@ export function propagateRequiredScopeFromDependencies(
215
215
  return components;
216
216
  }
217
217
 
218
+ function isTypeOnlyExcludedComponent(component) {
219
+ if (component?.scope !== "excluded") {
220
+ return false;
221
+ }
222
+ return (
223
+ isNpmTypesOnlyComponent(component) ||
224
+ (component.properties || []).some(
225
+ (property) =>
226
+ property.name === "cdx:npm:package:type-only" &&
227
+ property.value === "true",
228
+ )
229
+ );
230
+ }
231
+
232
+ /**
233
+ * Scopes the dependencies of type-only npm packages as optional.
234
+ *
235
+ * A package imported only for its TypeScript types, like an `@types` package,
236
+ * is erased at compile time, and so is what it depends on unless a runtime
237
+ * package depends on it too. A component below a type-only package becomes
238
+ * optional when no dependency path from a root of the graph reaches it without
239
+ * passing through a type-only package. A component that already has a scope,
240
+ * from its manifest or from usage evidence, keeps it.
241
+ *
242
+ * @param {Object[]} components CycloneDX component objects
243
+ * @param {Object[]} dependencies CycloneDX dependency entries
244
+ * @returns {Object[]} The same component array with scopes updated in place
245
+ */
246
+ export function markTypeOnlyDependenciesOptional(
247
+ components = [],
248
+ dependencies = [],
249
+ ) {
250
+ if (!components?.length || !dependencies?.length) {
251
+ return components;
252
+ }
253
+ const componentByRef = new Map();
254
+ const typeOnlyRefs = new Set();
255
+ for (const component of components) {
256
+ for (const ref of [component?.["bom-ref"], component?.purl]) {
257
+ if (!ref) {
258
+ continue;
259
+ }
260
+ const normalizedRef = normalizeBomRef(ref);
261
+ componentByRef.set(normalizedRef, component);
262
+ if (isTypeOnlyExcludedComponent(component)) {
263
+ typeOnlyRefs.add(normalizedRef);
264
+ }
265
+ }
266
+ }
267
+ if (!typeOnlyRefs.size) {
268
+ return components;
269
+ }
270
+ const childrenByRef = new Map();
271
+ const dependentRefs = new Set();
272
+ for (const dependency of dependencies) {
273
+ const ref = normalizeBomRef(dependency?.ref);
274
+ if (!ref) {
275
+ continue;
276
+ }
277
+ if (!childrenByRef.has(ref)) {
278
+ childrenByRef.set(ref, new Set());
279
+ }
280
+ for (const dependsOnRef of dependency.dependsOn || []) {
281
+ const childRef = normalizeBomRef(dependsOnRef);
282
+ if (childRef && childRef !== ref) {
283
+ childrenByRef.get(ref).add(childRef);
284
+ dependentRefs.add(childRef);
285
+ }
286
+ }
287
+ }
288
+ const walk = (startRefs) => {
289
+ const seen = new Set();
290
+ const stack = [...startRefs];
291
+ while (stack.length) {
292
+ const ref = stack.pop();
293
+ if (seen.has(ref)) {
294
+ continue;
295
+ }
296
+ seen.add(ref);
297
+ for (const childRef of childrenByRef.get(ref) || []) {
298
+ if (!typeOnlyRefs.has(childRef)) {
299
+ stack.push(childRef);
300
+ }
301
+ }
302
+ }
303
+ return seen;
304
+ };
305
+ // Every ref nothing depends on is a root, so a partial graph errs towards
306
+ // keeping a component rather than scoping it optional.
307
+ const runtimeRefs = walk(
308
+ [...childrenByRef.keys()].filter(
309
+ (ref) => !dependentRefs.has(ref) && !typeOnlyRefs.has(ref),
310
+ ),
311
+ );
312
+ for (const ref of walk(typeOnlyRefs)) {
313
+ if (typeOnlyRefs.has(ref) || runtimeRefs.has(ref)) {
314
+ continue;
315
+ }
316
+ const component = componentByRef.get(ref);
317
+ if (component && !component.scope) {
318
+ component.scope = "optional";
319
+ }
320
+ }
321
+ return components;
322
+ }
323
+
218
324
  function serviceIdentityKey(service) {
219
325
  if (service?.["bom-ref"]) {
220
326
  return service["bom-ref"].toLowerCase();
@@ -395,6 +501,71 @@ export function mergeServices(services, newServices) {
395
501
  return Array.from(serviceMap.values());
396
502
  }
397
503
 
504
+ // Descriptive fields, each listed with the raw spellings that collectors set
505
+ // before component conversion. Conversion prefers the converted `licenses`
506
+ // over a raw `license`, and a raw `author` over `authors`, so a field is only
507
+ // filled in, under the spelling the duplicate uses, when the retained
508
+ // component has no spelling of it.
509
+ const DESCRIPTIVE_FIELDS = [
510
+ ["description"],
511
+ ["publisher"],
512
+ ["copyright"],
513
+ ["authors", "author"],
514
+ ["licenses", "license"],
515
+ ];
516
+
517
+ const hasFieldValue = (value) =>
518
+ value !== undefined &&
519
+ value !== null &&
520
+ value !== "" &&
521
+ !(Array.isArray(value) && !value.length);
522
+
523
+ /**
524
+ * Carry the descriptive metadata of a duplicate sighting over to the retained
525
+ * component. A lockfile is usually parsed first and knows only the identity,
526
+ * while a manifest seen later, such as a gemspec, knows the description,
527
+ * authors, licenses, and project links (discussion 4389).
528
+ *
529
+ * The retained sighting keeps its own values. Lists are not unioned because
530
+ * two sightings spell the same fact differently: one license as an SPDX id and
531
+ * as a name, or as an expression that must never share the `licenses` array
532
+ * with license objects. External references are unioned, since each one is a
533
+ * distinct link.
534
+ *
535
+ * @param {object} existingComponent Retained component, updated in place
536
+ * @param {object} comp Duplicate sighting of the same component
537
+ */
538
+ function mergeDescriptiveFields(existingComponent, comp) {
539
+ for (const spellings of DESCRIPTIVE_FIELDS) {
540
+ if (
541
+ spellings.some((spelling) => hasFieldValue(existingComponent[spelling]))
542
+ ) {
543
+ continue;
544
+ }
545
+ const spelling = spellings.find((aspelling) =>
546
+ hasFieldValue(comp[aspelling]),
547
+ );
548
+ if (spelling) {
549
+ existingComponent[spelling] = comp[spelling];
550
+ }
551
+ }
552
+ if (!comp.externalReferences?.length) {
553
+ return;
554
+ }
555
+ // A copy, as the array may be shared with another sighting
556
+ const existingRefs = [...(existingComponent.externalReferences || [])];
557
+ for (const newref of comp.externalReferences) {
558
+ if (
559
+ !existingRefs.some(
560
+ (ref) => ref.type === newref.type && ref.url === newref.url,
561
+ )
562
+ ) {
563
+ existingRefs.push(newref);
564
+ }
565
+ }
566
+ existingComponent.externalReferences = existingRefs;
567
+ }
568
+
398
569
  /**
399
570
  * Trim duplicate components by retaining all the properties
400
571
  *
@@ -403,18 +574,21 @@ export function mergeServices(services, newServices) {
403
574
  * @returns {Array} Filtered components
404
575
  */
405
576
  export function trimComponents(components) {
406
- const keyCache = {};
407
- const filteredComponents = [];
577
+ // The keys come from the scanned manifests. In a plain object, a key such as
578
+ // `__proto__` would resolve to Object.prototype and the merge below would
579
+ // write to it.
580
+ const keyCache = new Map();
408
581
  for (const comp of components) {
409
582
  const key = (
410
583
  comp.purl ||
411
584
  comp["bom-ref"] ||
412
585
  comp.name + comp.version
413
586
  ).toLowerCase();
414
- if (!keyCache[key]) {
415
- keyCache[key] = comp;
587
+ if (!keyCache.has(key)) {
588
+ keyCache.set(key, comp);
416
589
  } else {
417
- const existingComponent = keyCache[key];
590
+ const existingComponent = keyCache.get(key);
591
+ mergeDescriptiveFields(existingComponent, comp);
418
592
  // We need to retain any properties that differ
419
593
  if (comp.properties) {
420
594
  if (existingComponent.properties) {
@@ -530,10 +704,7 @@ export function trimComponents(components) {
530
704
  }
531
705
  }
532
706
  }
533
- for (const akey of Object.keys(keyCache)) {
534
- filteredComponents.push(keyCache[akey]);
535
- }
536
- return filteredComponents;
707
+ return Array.from(keyCache.values());
537
708
  }
538
709
 
539
710
  /**
@@ -603,7 +774,8 @@ export function isPartialTree(dependencies, componentsCount = 1) {
603
774
  let isCbom = false;
604
775
  let parentsWithChildsCount = 0;
605
776
  for (const adep of dependencies) {
606
- if (adep?.dependsOn.length > 0) {
777
+ // Producers such as protobuf omit empty dependsOn lists
778
+ if (adep?.dependsOn?.length > 0) {
607
779
  parentsWithChildsCount++;
608
780
  }
609
781
  if (!isCbom && adep?.provides?.length > 0) {
@@ -640,7 +812,7 @@ export function recomputeScope(pkgList, dependencies) {
640
812
  }
641
813
  for (const adep of dependencies) {
642
814
  if (requiredPkgs[adep.ref]) {
643
- for (const ado of adep.dependsOn) {
815
+ for (const ado of adep.dependsOn || []) {
644
816
  requiredPkgs[ado] = true;
645
817
  }
646
818
  }
@@ -1,4 +1,3 @@
1
- import { readFileSync } from "node:fs";
2
1
  import path from "node:path";
3
2
  import process from "node:process";
4
3
 
@@ -11,6 +10,7 @@ import {
11
10
  import { safeExistsSync } from "../core/fs.js";
12
11
  import { toCamel } from "../core/paths.js";
13
12
  import { detectAiModelVariants } from "./aiModelVariants.js";
13
+ import { readReachablesSlices } from "./atomUtils.js";
14
14
  import { formatOccurrenceEvidence } from "./evidenceUtils.js";
15
15
  import {
16
16
  formatHbomHardwareClassSummary,
@@ -1407,12 +1407,11 @@ export function printReachables(sliceArtefacts) {
1407
1407
  return;
1408
1408
  }
1409
1409
  const purlCounts = {};
1410
- const reachablesSlices = JSON.parse(
1411
- readFileSync(reachablesSlicesFile, "utf-8"),
1412
- );
1410
+ const reachablesSlices = readReachablesSlices(reachablesSlicesFile);
1411
+ // undefined when the slices file was unreadable; readReachablesSlices warned.
1413
1412
  const rflows = Array.isArray(reachablesSlices)
1414
1413
  ? reachablesSlices
1415
- : reachablesSlices.reachables || [];
1414
+ : reachablesSlices?.reachables || [];
1416
1415
  for (const areachable of rflows) {
1417
1416
  const purls = areachable.purls || [];
1418
1417
  for (const apurl of purls) {