@cdxgen/cdxgen 13.0.0 → 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.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Deprecated re-export barrel.
3
3
  *
4
- * `utils.js` declares nothing of its own. It re-exports the 261 public names that
4
+ * `utils.js` declares nothing of its own. It re-exports every public name that
5
5
  * used to live here so existing imports keep working for one major version. Each
6
6
  * implementation, and its JSDoc, now lives in the leaf module it was moved to
7
7
  * (`purl.js`, `spdx.js`, `deps.js`, `ecosystems.js`, `parsers-*.js`, `core-*.js`,
@@ -191,11 +191,13 @@ import {
191
191
  import { getMavenCommand, getMillCommand } from "./gradleutils.js";
192
192
  import { createNpmWorkspacePurl } from "./npmutils.js";
193
193
  import {
194
+ getCentralPackageVersions,
194
195
  getPropertyGroupTextNodes,
195
196
  parseCsPkgData,
196
197
  parseCsPkgLockData,
197
198
  parseCsProjAssetsData,
198
199
  parseCsProjData,
200
+ parseDirectoryPackagesProps,
199
201
  parseNupkg,
200
202
  parseNuspecData,
201
203
  parsePaketLockData,
@@ -220,12 +222,14 @@ import {
220
222
  parsePackageJsonName,
221
223
  parsePkgJson,
222
224
  parsePkgLock,
225
+ parsePnpmAliasRef,
223
226
  parsePnpmLock,
224
227
  parsePnpmWorkspace,
225
228
  parseYarnLock,
226
229
  parseYarnWorkspace,
227
230
  pnpmMetadata,
228
231
  splitCommandArgs,
232
+ stripPnpmPeerSuffix,
229
233
  yarnLockToIdentMap,
230
234
  } from "./parsers-js.js";
231
235
  import {
@@ -369,6 +373,7 @@ export {
369
373
  getAllFiles,
370
374
  getAllFilesWithIgnore,
371
375
  getAtomCommand,
376
+ getCentralPackageVersions,
372
377
  getCratesMetadata,
373
378
  getDartMetadata,
374
379
  getDefaultBomAuditCategories,
@@ -465,6 +470,7 @@ export {
465
470
  parseCsProjAssetsData,
466
471
  parseCsProjData,
467
472
  parseCUsageSlice,
473
+ parseDirectoryPackagesProps,
468
474
  parseEdnData,
469
475
  parseFlakeLock,
470
476
  parseFlakeNix,
@@ -503,6 +509,7 @@ export {
503
509
  parsePixiTomlFile,
504
510
  parsePkgJson,
505
511
  parsePkgLock,
512
+ parsePnpmAliasRef,
506
513
  parsePnpmLock,
507
514
  parsePnpmWorkspace,
508
515
  parsePodfileLock,
@@ -565,6 +572,7 @@ export {
565
572
  shouldFetchVCS,
566
573
  shouldRunPredictiveBomAudit,
567
574
  splitCommandArgs,
575
+ stripPnpmPeerSuffix,
568
576
  TABLE_BORDER_STYLE,
569
577
  TIMEOUT_MS,
570
578
  toCamel,
@@ -31,6 +31,7 @@ import {
31
31
  safeExistsSync,
32
32
  safeSpawnSync,
33
33
  } from "../core/fs.js";
34
+ import { diagnosticStream } from "../core/logger.js";
34
35
  import { dirNameStr, isMac, isWin } from "../core/paths.js";
35
36
  import { applyPurl, tryBuildPurl } from "./purl.js";
36
37
 
@@ -919,9 +920,12 @@ export function bundleInstallWithDocker(rubyVersion, cdxgenGemHome, filePath) {
919
920
  "install",
920
921
  ];
921
922
  console.log(`Performing bundle install with: ${ociCmd}`);
923
+ // The container's own output is a diagnostic. Inheriting stdout would let it
924
+ // interleave with the BOM document when the caller asked for `-o -`, so the
925
+ // child's stdout is pointed at the diagnostic stream instead.
922
926
  const result = safeSpawnSync(ociCmd, ociArgs, {
923
927
  shell: isWin,
924
- stdio: "inherit",
928
+ stdio: ["inherit", diagnosticStream, "inherit"],
925
929
  });
926
930
  if (result.error || result.status !== 0) {
927
931
  return false;
@@ -86,6 +86,112 @@ export const OS_DISTRO_ALIAS = {
86
86
  "red hat enterprise linux 10": "rhel-10",
87
87
  };
88
88
 
89
+ /**
90
+ * Canonical purl namespace for distro vendors that are published under more
91
+ * than one spelling. Both the os-release path (`getDistroInfo`) and the trivy
92
+ * passthrough in `lib/managers/binary.js` route through this single map so the
93
+ * two paths cannot drift apart again.
94
+ *
95
+ * Azure Linux 2.0 is CBL-Mariner 2.0 — Microsoft renamed the product — and the
96
+ * OSV feed publishes `pkg:rpm/azure-linux/<name>` for every generation
97
+ * (ecosystems "Azure Linux:2" and "Azure Linux:3"), so the historical
98
+ * spellings azurelinux, cbl-mariner and mariner all canonicalise to
99
+ * "azure-linux". rhel/ol/amzn/opensuse-* are the cases previously inlined in
100
+ * getDistroInfo(); alma/rocky-linux were previously inlined in binary.js.
101
+ *
102
+ * Amazon Linux canonicalises to "amazon", the spelling the vulnerability feeds
103
+ * publish (AppThreat vuln-list `amazon`): the os-release ID is `amzn` and
104
+ * trivy emits `amazon`, and the previous `amazonlinux` mapping here matched
105
+ * neither, so live-host Amazon Linux purls matched nothing at all.
106
+ */
107
+ export const OS_NAMESPACE_ALIAS = {
108
+ alma: "almalinux",
109
+ amazonlinux: "amazon",
110
+ amzn: "amazon",
111
+ azurelinux: "azure-linux",
112
+ "cbl-mariner": "azure-linux",
113
+ mariner: "azure-linux",
114
+ ol: "oracle",
115
+ "opensuse-leap": "opensuse",
116
+ "opensuse-tumbleweed": "opensuse",
117
+ rhel: "redhat",
118
+ "rocky-linux": "rocky",
119
+ };
120
+
121
+ /**
122
+ * Vendor prefixes that `distro` qualifier values are canonicalised for, so one
123
+ * release has exactly one qualifier value no matter which side produced it.
124
+ *
125
+ * trivy stamps the qualifier from its own distro table while cdxgen stamps it
126
+ * from os-release, and the two disagree: an Oracle Linux 9.8 image came out
127
+ * with `oracle-9.8` on 99 components and `ol-9.8` on 43, and a RHEL 9.8 image
128
+ * with namespace `redhat` but qualifier `rhel-9.8`. Canonicalising both sides
129
+ * through this map collapses each release onto the vendor its purl namespace
130
+ * already uses.
131
+ *
132
+ * SUSE is deliberately absent. `sles-15.6`, `opensuse-leap-15.6` and
133
+ * `opensuse-tumbleweed-<snapshot>` are the values consumers derive the release
134
+ * channel from (vulnerability-db maps them to per-release channels), and both
135
+ * sides already agree on them; folding Leap and Tumbleweed onto a bare
136
+ * `opensuse-` vendor would destroy exactly the distinction that matters.
137
+ *
138
+ * Keys are matched longest-first so `cbl-mariner-2.0` resolves against the
139
+ * full vendor segment rather than a shorter prefix.
140
+ */
141
+ export const OS_DISTRO_QUALIFIER_ALIAS = {
142
+ alma: "almalinux",
143
+ amazonlinux: "amazon",
144
+ amzn: "amazon",
145
+ azurelinux: "azure-linux",
146
+ "cbl-mariner": "azure-linux",
147
+ mariner: "azure-linux",
148
+ ol: "oracle",
149
+ rhel: "redhat",
150
+ "rocky-linux": "rocky",
151
+ };
152
+
153
+ // A distro qualifier reaches a purl verbatim, so anything outside the purl
154
+ // qualifier charset makes the whole purl — and with it the whole BOM —
155
+ // invalid. trivy emits Amazon Linux as
156
+ // `amazon-2023.12.20260727+(Amazon Linux)`, which failed cdxgen's own schema
157
+ // validation and cost the entire SBOM.
158
+ const SAFE_DISTRO_QUALIFIER = /^[A-Za-z0-9._-]+$/;
159
+
160
+ /**
161
+ * Whether a `distro` qualifier value is safe to place in a purl unencoded.
162
+ *
163
+ * @param {string} qualifier - distro qualifier value
164
+ * @returns {boolean} true when the value contains only purl-safe characters
165
+ */
166
+ export function isCleanDistroQualifier(qualifier) {
167
+ return !!qualifier && SAFE_DISTRO_QUALIFIER.test(qualifier);
168
+ }
169
+
170
+ const OS_DISTRO_QUALIFIER_ALIAS_KEYS = Object.keys(
171
+ OS_DISTRO_QUALIFIER_ALIAS,
172
+ ).sort((a, b) => b.length - a.length);
173
+
174
+ /**
175
+ * Canonicalise the vendor prefix of a `distro` qualifier value
176
+ * (e.g. "cbl-mariner-2.0" or "mariner-2.0" -> "azure-linux-2.0") so a release
177
+ * has exactly one qualifier spelling regardless of which tool emitted it.
178
+ * Values without a known vendor prefix are returned unchanged.
179
+ *
180
+ * @param {string} qualifier - distro qualifier value such as "azurelinux-3.0"
181
+ * @returns {string} canonical qualifier value
182
+ */
183
+ export function canonicalDistroQualifier(qualifier) {
184
+ if (!qualifier) {
185
+ return qualifier;
186
+ }
187
+ for (const key of OS_DISTRO_QUALIFIER_ALIAS_KEYS) {
188
+ if (qualifier.startsWith(`${key}-`)) {
189
+ return `${OS_DISTRO_QUALIFIER_ALIAS[key]}${qualifier.slice(key.length)}`;
190
+ }
191
+ }
192
+ return qualifier;
193
+ }
194
+
89
195
  // ---------------------------------------------------------------------------
90
196
  // Raw os-release cache — keyed by the osRelease file path so the live-host
91
197
  // cache (/etc/os-release) and any container rootfs path are kept separate.
@@ -151,8 +257,10 @@ export function _resetOsReleaseCache() {
151
257
  *
152
258
  * Returns an object with:
153
259
  * - purlType {string} "deb" | "apk" | "rpm"
154
- * - namespace {string} purl namespace (e.g. "ubuntu", "alpine", "fedora")
155
- * - distroId {string} ID + "-" + VERSION_ID (e.g. "ubuntu-22.04")
260
+ * - namespace {string} canonical purl namespace (e.g. "ubuntu", "alpine",
261
+ * "azure-linux" for both Azure Linux and CBL-Mariner)
262
+ * - distroId {string} ID + "-" + VERSION_ID with a canonical vendor
263
+ * prefix (e.g. "ubuntu-22.04", "azure-linux-3.0")
156
264
  * - distroName {string} codename/alias (e.g. "jammy")
157
265
  *
158
266
  * Mirrors the logic in lib/managers/binary.js getOSPackages() so that both
@@ -193,6 +301,9 @@ export function getDistroInfo(root = "/") {
193
301
  case "centos":
194
302
  case "rocky":
195
303
  case "almalinux":
304
+ case "azurelinux":
305
+ case "cbl-mariner":
306
+ case "mariner":
196
307
  case "oracle":
197
308
  case "ol":
198
309
  case "amzn":
@@ -216,19 +327,10 @@ export function getDistroInfo(root = "/") {
216
327
  break;
217
328
  }
218
329
 
219
- // Determine the purl namespace (vendor)
220
- let namespace = rawId;
221
- if (rawId === "rhel") {
222
- namespace = "redhat";
223
- } else if (rawId === "ol") {
224
- namespace = "oracle";
225
- } else if (rawId === "amzn") {
226
- namespace = "amazonlinux";
227
- } else if (rawId === "opensuse-leap" || rawId === "opensuse-tumbleweed") {
228
- namespace = "opensuse";
229
- } else if (rawId === "almalinux") {
230
- namespace = "almalinux";
231
- } else if (!rawId) {
330
+ // Determine the purl namespace (vendor), canonicalising vendors that are
331
+ // published under several spellings (e.g. mariner -> azure-linux)
332
+ let namespace = OS_NAMESPACE_ALIAS[rawId] || rawId;
333
+ if (!rawId) {
232
334
  // Fallback via ID_LIKE
233
335
  if (idLike.includes("rhel") || idLike.includes("centos")) {
234
336
  namespace = "redhat";
@@ -245,8 +347,12 @@ export function getDistroInfo(root = "/") {
245
347
  }
246
348
  }
247
349
 
248
- // Build distroId = ID-VERSION_ID (e.g. "fedora-25", "alpine-3.17")
350
+ // Build distroId = ID-VERSION_ID (e.g. "fedora-25", "alpine-3.17"),
351
+ // canonicalising the vendor prefix so one release has a single qualifier
352
+ // value (e.g. "mariner-2.0" and "azurelinux-3.0" -> "azure-linux-2.0"/
353
+ // "azure-linux-3.0")
249
354
  let distroId = versionId ? `${rawId}-${versionId}` : rawId;
355
+ distroId = canonicalDistroQualifier(distroId);
250
356
 
251
357
  // Special-case Alpine: truncate to major.minor
252
358
  if (purlType === "apk" && versionId) {
@@ -2,6 +2,7 @@ import process from "node:process";
2
2
 
3
3
  import { readEnvironmentVariable } from "../core/activity.js";
4
4
  import { TABLE_BORDER_STYLE } from "../core/env.js";
5
+ import { diagnosticStream } from "../core/logger.js";
5
6
  import {
6
7
  alignText,
7
8
  stringWidth,
@@ -218,7 +219,10 @@ export function table(rows, config = {}) {
218
219
  }
219
220
 
220
221
  /**
221
- * Create a streaming table writer that renders rows to stdout incrementally.
222
+ * Create a streaming table writer that renders rows incrementally.
223
+ *
224
+ * Rows go to the diagnostic stream: a rendered table is human-readable output,
225
+ * and writing it to stdout corrupted the BOM document under `-o -`.
222
226
  *
223
227
  * @param {Object} [config={}] Table configuration (header, columns, border style)
224
228
  * @returns {{write(row: *): void, end(): void}} Writer whose `end()` emits the bottom border
@@ -242,21 +246,21 @@ export function createStream(config = {}) {
242
246
  const topBorder = drawBorder(columns, borderChars, "top");
243
247
  middleBorder = drawBorder(columns, borderChars, "mid");
244
248
  bottomBorder = drawBorder(columns, borderChars, "bottom");
245
- process.stdout.write(`${topBorder}\n`);
249
+ diagnosticStream.write(`${topBorder}\n`);
246
250
  }
247
251
  if (hasRows) {
248
- process.stdout.write(`${middleBorder}\n`);
252
+ diagnosticStream.write(`${middleBorder}\n`);
249
253
  }
250
254
  const safeRow = Array.isArray(row) ? row : [row];
251
255
  const rendered = renderRow(safeRow, columns, borderChars);
252
- process.stdout.write(`${rendered.join("\n")}\n`);
256
+ diagnosticStream.write(`${rendered.join("\n")}\n`);
253
257
  hasRows = true;
254
258
  },
255
259
  end() {
256
260
  if (!columns || closed) {
257
261
  return;
258
262
  }
259
- process.stdout.write(`${bottomBorder}\n`);
263
+ diagnosticStream.write(`${bottomBorder}\n`);
260
264
  closed = true;
261
265
  },
262
266
  };
@@ -37,7 +37,13 @@ import {
37
37
  extractToolRefs,
38
38
  } from "../inventory/evidenceUtils.js";
39
39
  import { createGtfoBinsProperties } from "../inventory/gtfobins.js";
40
- import { OS_DISTRO_ALIAS, readOsRelease } from "../inventory/osinfo.js";
40
+ import {
41
+ canonicalDistroQualifier,
42
+ isCleanDistroQualifier,
43
+ OS_DISTRO_ALIAS,
44
+ OS_NAMESPACE_ALIAS,
45
+ readOsRelease,
46
+ } from "../inventory/osinfo.js";
41
47
  import {
42
48
  collectExecutables,
43
49
  collectSharedLibs,
@@ -57,13 +63,6 @@ import { getDirs } from "./containerutils.js";
57
63
 
58
64
  const isWin = _platform() === "win32";
59
65
  const OS_PURL_TYPES = ["deb", "apk", "rpm", "alpm", "qpkg"];
60
- // Canonical distro vendor namespace. Trivy emits pkg:rpm/alma/... but the
61
- // vulnerability feeds (AppThreat vuln-list / dep-scan) key AlmaLinux under
62
- // "almalinux"; align so the purls match. Rocky is emitted as "rocky" already.
63
- const OS_DISTRO_NAMESPACE_ALIAS = {
64
- alma: "almalinux",
65
- "rocky-linux": "rocky",
66
- };
67
66
  const pluginRuntime = setPluginsPathEnv(resolveCdxgenPlugins());
68
67
  const platform = pluginRuntime.platform;
69
68
  const CDXGEN_PLUGINS_DIR = pluginRuntime.pluginsDir;
@@ -555,6 +554,173 @@ export function executeSourcekitten(args) {
555
554
  return undefined;
556
555
  }
557
556
 
557
+ /**
558
+ * Canonicalise the distro vendor namespace of an OS package component
559
+ * produced by trivy, regardless of whether trivy expressed the vendor via the
560
+ * purl namespace or the component group (it is inconsistent between
561
+ * packages). e.g. pkg:rpm/alma/... -> pkg:rpm/almalinux/... and
562
+ * pkg:rpm/cbl-mariner/... -> pkg:rpm/azure-linux/... so the purls match the
563
+ * vulnerability feeds. Also aligns the ``distro`` qualifier vendor prefix
564
+ * (alma-9.8 -> almalinux-9.8, mariner-2.0 -> azure-linux-2.0), including
565
+ * spellings that disagree with the namespace, and rebuilds ``group`` and
566
+ * ``bom-ref`` from the rewritten purl.
567
+ *
568
+ * The namespace table is OS_NAMESPACE_ALIAS from lib/inventory/osinfo.js so
569
+ * the trivy path and the os-release path (getDistroInfo) cannot drift apart.
570
+ *
571
+ * A `distro` qualifier that is not purl-safe (trivy emits Amazon Linux as
572
+ * `amazon-2023.12.20260727+(Amazon Linux)`) is replaced with the os-release
573
+ * derived value, or dropped when there is none: an unusable qualifier
574
+ * invalidates the purl and fails validation for the whole BOM.
575
+ *
576
+ * @param {Object} comp trivy OS package component (mutated in place)
577
+ * @param {Object} purlObj parsed PackageURL of comp.purl (mutated in place)
578
+ * @param {string} name component package name (basename form)
579
+ * @param {string} group component group derived from the trivy component name
580
+ * @param {string} [distroId] canonical os-release distro id, e.g. "amazon-2023"
581
+ * @returns {string} the canonicalised group
582
+ */
583
+ export function canonicaliseOsPackageNamespace(
584
+ comp,
585
+ purlObj,
586
+ name,
587
+ group,
588
+ distroId,
589
+ ) {
590
+ if (!purlObj || !OS_PURL_TYPES.includes(purlObj.type)) {
591
+ return group;
592
+ }
593
+ const os_ns = purlObj.namespace || group || "";
594
+ const os_ns_alias = OS_NAMESPACE_ALIAS[os_ns];
595
+ const distroQual = purlObj.qualifiers?.distro;
596
+ let canonicalDistroQual = distroQual;
597
+ if (os_ns_alias && distroQual?.startsWith(`${os_ns}-`)) {
598
+ canonicalDistroQual = `${os_ns_alias}-${distroQual.slice(os_ns.length + 1)}`;
599
+ }
600
+ canonicalDistroQual = canonicalDistroQualifier(canonicalDistroQual);
601
+ if (canonicalDistroQual && !isCleanDistroQualifier(canonicalDistroQual)) {
602
+ canonicalDistroQual = isCleanDistroQualifier(distroId)
603
+ ? distroId
604
+ : undefined;
605
+ }
606
+ if (!os_ns_alias && canonicalDistroQual === distroQual) {
607
+ return group;
608
+ }
609
+ group = os_ns_alias || purlObj.namespace || group || "";
610
+ comp.group = group;
611
+ purlObj.namespace = group;
612
+ if (canonicalDistroQual !== distroQual) {
613
+ if (canonicalDistroQual) {
614
+ purlObj.qualifiers.distro = canonicalDistroQual;
615
+ } else {
616
+ delete purlObj.qualifiers.distro;
617
+ }
618
+ }
619
+ comp.purl = build({
620
+ type: purlObj.type,
621
+ namespace: purlObj.namespace || null,
622
+ name: name,
623
+ version: purlObj.version || null,
624
+ qualifiers: purlObj.qualifiers || null,
625
+ subpath: purlObj.subpath || null,
626
+ });
627
+ comp["bom-ref"] = decodeURIComponent(comp.purl);
628
+ return group;
629
+ }
630
+
631
+ /**
632
+ * Repair a `distro` qualifier that cannot survive purl parsing, in place.
633
+ *
634
+ * trivy stamps Amazon Linux with the full pretty version — the qualifier
635
+ * arrives as `distro=amazon-2023.12.20260727+%28Amazon+Linux%29`, whose spaces
636
+ * and parentheses `Purl.parse` rejects outright ("Invalid character in
637
+ * qualifier distro"). The component then bypasses every canonicalisation below
638
+ * (they all need a parsed purl) and reaches the BOM verbatim, where schema
639
+ * validation fails and the *entire* SBOM is discarded — an Amazon Linux scan
640
+ * produces no output at all today.
641
+ *
642
+ * The release is already known from os-release, so the unusable value is
643
+ * swapped for it; with no usable replacement the qualifier is dropped, since a
644
+ * missing qualifier costs one matching hint while an invalid one costs the
645
+ * whole document.
646
+ *
647
+ * @param {Object} comp component whose purl may carry an unusable qualifier
648
+ * @param {string} [distroId] canonical os-release distro id, e.g. "amazon-2023"
649
+ * @returns {Object} the same component
650
+ */
651
+ export function repairOsComponentPurl(comp, distroId) {
652
+ const purl = comp?.purl;
653
+ if (!purl?.includes("distro=")) {
654
+ return comp;
655
+ }
656
+ const match = /([?&]distro=)([^&]*)/.exec(purl);
657
+ if (!match) {
658
+ return comp;
659
+ }
660
+ let decoded = match[2];
661
+ try {
662
+ decoded = decodeURIComponent(match[2].replace(/\+/g, " "));
663
+ } catch (_err) {
664
+ // A value that cannot even be decoded is definitely not purl-safe
665
+ }
666
+ if (isCleanDistroQualifier(decoded)) {
667
+ return comp;
668
+ }
669
+ const replacement = isCleanDistroQualifier(distroId)
670
+ ? `${match[1]}${distroId}`
671
+ : "";
672
+ comp.purl = purl.replace(match[0], replacement).replace(/\?&/, "?");
673
+ if (comp.purl.endsWith("?")) {
674
+ comp.purl = comp.purl.slice(0, -1);
675
+ }
676
+ comp["bom-ref"] = decodeURIComponent(comp.purl);
677
+ return comp;
678
+ }
679
+
680
+ /**
681
+ * Drop source-package shadow components that duplicate a real installed
682
+ * package, in place.
683
+ *
684
+ * A shadow is emitted whenever an rpm/deb declares a source name different
685
+ * from its own (bzip2-libs -> bzip2) so that scanners keying on source names
686
+ * still match. When the source package is *itself* installed at the same
687
+ * version, the shadow's purl is identical to the real component's and the two
688
+ * collapse in trimComponents() — which keeps whichever was seen first and
689
+ * never merges `tags`. Seen-first is trivy's listing order, so the outcome is
690
+ * arbitrary: when the shadow wins, a genuinely installed package is reported
691
+ * with `tags: ["source"]` and identity confidence 0, the very marker the
692
+ * shadow carries so consumers can filter shadows out. That turns a real
693
+ * package into a filtered-away one.
694
+ *
695
+ * Dropping the redundant shadow is loss-free: it duplicates the real
696
+ * component's purl (so nothing that matches on purl changes) and its bom-ref
697
+ * is byte-identical, so no dependency reference can dangle.
698
+ *
699
+ * @param {Array} pkgList component list, mutated in place
700
+ * @returns {Array} the same list, for convenience
701
+ */
702
+ export function dropRedundantSourceComponents(pkgList) {
703
+ if (!Array.isArray(pkgList) || !pkgList.length) {
704
+ return pkgList;
705
+ }
706
+ const installedPurls = new Set();
707
+ for (const comp of pkgList) {
708
+ if (comp?.purl && !comp?.tags?.includes("source")) {
709
+ installedPurls.add(comp.purl);
710
+ }
711
+ }
712
+ if (!installedPurls.size) {
713
+ return pkgList;
714
+ }
715
+ for (let i = pkgList.length - 1; i >= 0; i--) {
716
+ const comp = pkgList[i];
717
+ if (comp?.tags?.includes("source") && installedPurls.has(comp.purl)) {
718
+ pkgList.splice(i, 1);
719
+ }
720
+ }
721
+ return pkgList;
722
+ }
723
+
558
724
  /**
559
725
  * Get the packages installed in the container image filesystem.
560
726
  *
@@ -739,6 +905,11 @@ export async function getOSPackages(src, imageConfig, options = {}) {
739
905
  distro_codename = OS_DISTRO_ALIAS[distro_id];
740
906
  }
741
907
  }
908
+ // Canonicalise the distro qualifier vendor (e.g. "mariner-2.0" ->
909
+ // "azure-linux-2.0") so every spelling of one release yields a single
910
+ // qualifier value. This runs before distro_id is used to overwrite
911
+ // trivy's own qualifier below.
912
+ distro_id = canonicalDistroQualifier(distro_id);
742
913
  const tmpDependencies = {};
743
914
  tools = enrichToolComponents(
744
915
  (Array.isArray(tmpBom?.metadata?.tools)
@@ -768,42 +939,23 @@ export async function getOSPackages(src, imageConfig, options = {}) {
768
939
  comp.group = group;
769
940
  comp.name = name;
770
941
  const modularityLabel = extractModularityLabel(comp);
942
+ repairOsComponentPurl(comp, distro_id);
771
943
  try {
772
944
  purlObj = Purl.parse(comp.purl);
773
945
  purlObj.qualifiers = purlObj.qualifiers || {};
774
946
  } catch (_err) {
775
947
  // continue regardless of error
776
948
  }
777
- // Canonicalise the distro vendor namespace for every OS component,
778
- // regardless of whether trivy expressed the vendor via the purl
779
- // namespace or the component group (it is inconsistent between
780
- // packages). e.g. pkg:rpm/alma/... -> pkg:rpm/almalinux/... so the
781
- // purls match the vulnerability feeds. Also aligns the ``distro``
782
- // qualifier vendor prefix (alma-9.8 -> almalinux-9.8).
783
- if (purlObj && OS_PURL_TYPES.includes(purlObj.type)) {
784
- const os_ns = purlObj.namespace || group || "";
785
- const os_ns_alias = OS_DISTRO_NAMESPACE_ALIAS[os_ns];
786
- if (os_ns_alias) {
787
- group = os_ns_alias;
788
- comp.group = os_ns_alias;
789
- purlObj.namespace = os_ns_alias;
790
- const distroQual = purlObj.qualifiers?.distro;
791
- if (distroQual?.startsWith(`${os_ns}-`)) {
792
- purlObj.qualifiers.distro = `${os_ns_alias}-${distroQual.slice(
793
- os_ns.length + 1,
794
- )}`;
795
- }
796
- comp.purl = build({
797
- type: purlObj.type,
798
- namespace: os_ns_alias || null,
799
- name: name,
800
- version: purlObj.version || null,
801
- qualifiers: purlObj.qualifiers || null,
802
- subpath: purlObj.subpath || null,
803
- });
804
- comp["bom-ref"] = decodeURIComponent(comp.purl);
805
- }
806
- }
949
+ // Canonicalise the distro vendor namespace (see
950
+ // canonicaliseOsPackageNamespace) before any later bom-ref
951
+ // rebuild so the rewritten purl, group and bom-ref stay in sync.
952
+ group = canonicaliseOsPackageNamespace(
953
+ comp,
954
+ purlObj,
955
+ name,
956
+ group,
957
+ distro_id,
958
+ );
807
959
  if (
808
960
  group === "" &&
809
961
  purlObj &&
@@ -812,8 +964,7 @@ export async function getOSPackages(src, imageConfig, options = {}) {
812
964
  try {
813
965
  if (purlObj?.namespace && purlObj.namespace !== "") {
814
966
  group =
815
- OS_DISTRO_NAMESPACE_ALIAS[purlObj.namespace] ||
816
- purlObj.namespace;
967
+ OS_NAMESPACE_ALIAS[purlObj.namespace] || purlObj.namespace;
817
968
  comp.group = group;
818
969
  purlObj.namespace = group;
819
970
  }
@@ -1107,6 +1258,7 @@ export async function getOSPackages(src, imageConfig, options = {}) {
1107
1258
  }
1108
1259
  }
1109
1260
  }
1261
+ dropRedundantSourceComponents(pkgList);
1110
1262
  const rootfsRepositoryInventory = await collectRootfsRepositoryInventory(
1111
1263
  src,
1112
1264
  options,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdxgen/cdxgen",
3
- "version": "13.0.0",
3
+ "version": "13.0.1",
4
4
  "description": "Creates CycloneDX Software Bill of Materials (SBOM) from source or container image",
5
5
  "keywords": [
6
6
  "sbom",
@@ -162,17 +162,17 @@
162
162
  "@bufbuild/protobuf": "2.13.0",
163
163
  "@cdxgen/cdx-hbom": "0.5.1",
164
164
  "@cdxgen/cdx-proto": "2.2.0",
165
- "@cdxgen/cdxgen-plugins-bin": "3.0.3",
166
- "@cdxgen/cdxgen-plugins-bin-darwin-amd64": "3.0.3",
167
- "@cdxgen/cdxgen-plugins-bin-darwin-arm64": "3.0.3",
168
- "@cdxgen/cdxgen-plugins-bin-linux-amd64": "3.0.3",
169
- "@cdxgen/cdxgen-plugins-bin-linux-arm": "3.0.3",
170
- "@cdxgen/cdxgen-plugins-bin-linux-arm64": "3.0.3",
171
- "@cdxgen/cdxgen-plugins-bin-linux-ppc64": "3.0.3",
172
- "@cdxgen/cdxgen-plugins-bin-linuxmusl-amd64": "3.0.3",
173
- "@cdxgen/cdxgen-plugins-bin-linuxmusl-arm64": "3.0.3",
174
- "@cdxgen/cdxgen-plugins-bin-windows-amd64": "3.0.3",
175
- "@cdxgen/cdxgen-plugins-bin-windows-arm64": "3.0.3",
165
+ "@cdxgen/cdxgen-plugins-bin": "3.0.4",
166
+ "@cdxgen/cdxgen-plugins-bin-darwin-amd64": "3.0.4",
167
+ "@cdxgen/cdxgen-plugins-bin-darwin-arm64": "3.0.4",
168
+ "@cdxgen/cdxgen-plugins-bin-linux-amd64": "3.0.4",
169
+ "@cdxgen/cdxgen-plugins-bin-linux-arm": "3.0.4",
170
+ "@cdxgen/cdxgen-plugins-bin-linux-arm64": "3.0.4",
171
+ "@cdxgen/cdxgen-plugins-bin-linux-ppc64": "3.0.4",
172
+ "@cdxgen/cdxgen-plugins-bin-linuxmusl-amd64": "3.0.4",
173
+ "@cdxgen/cdxgen-plugins-bin-linuxmusl-arm64": "3.0.4",
174
+ "@cdxgen/cdxgen-plugins-bin-windows-amd64": "3.0.4",
175
+ "@cdxgen/cdxgen-plugins-bin-windows-arm64": "3.0.4",
176
176
  "@cdxgen/safer-exec": "0.15.0",
177
177
  "body-parser": "2.3.0",
178
178
  "compression": "1.8.1",
@@ -180,8 +180,22 @@ export declare function determinePackageType(pkg: any): any;
180
180
  export declare function processHashes(pkg: any, component: any): void;
181
181
  /**
182
182
  * Adds a hash to component.
183
+ *
184
+ * Registries hand out digests in hex or in the base64 form npm uses for
185
+ * `integrity`, and both are accepted. A digest that is neither, or that decodes
186
+ * to the wrong length for its algorithm, is dropped rather than copied through:
187
+ * CycloneDX constrains `hash.content` to a hex digest, so emitting it anyway
188
+ * fails schema validation for the whole document over one bad package.
183
189
  */
184
190
  export declare function addComponentHash(alg: any, digest: any, component: any): void;
191
+ /**
192
+ * Normalize a digest to the lower-case hex form CycloneDX requires.
193
+ *
194
+ * @param {string} alg Hash algorithm name
195
+ * @param {string} digest Digest in hex or base64
196
+ * @returns {string|undefined} Hex digest, or undefined when it cannot be trusted
197
+ */
198
+ export declare function normalizeHashContent(alg: string, digest: string): string | undefined;
185
199
  /**
186
200
  * Return the BOM in json format including any namespace mapping
187
201
  *
@@ -1 +1 @@
1
- {"version":3,"file":"bomAssembly.d.ts","sourceRoot":"","sources":["../../../lib/cli/bomAssembly.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bomAssembly.d.ts","sourceRoot":"","sources":["../../../lib/cli/bomAssembly.js"],"names":[],"mappings":"AA8DA;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,aAJ7B,MAAM,qBACN,MAAM,EAAE,KACN,OAsBZ,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAFf,MAG8E,CAAC;AAEzF;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,0CAH9B,MAAM;;;;;CAwChB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,YAJ1B,MAAM,KACJ,MAAM,GAAC,SAyBnB,CAAC;AAoEF;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,YALjB,MAAM,YACN,MAAM,KACJ;IAAE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CA+CrD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,SAH3B,MAAM,KACJ,MAQZ,CAAC;AAEF;;;;;;;GAOG;AAGH,eAAO,MAAM,oBAAoB,SALtB,MAAM,KACJ,MAwBZ,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,YAHnB,MAAM,KACJ,MAAM,EAgBlB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,YAHtB,MAAM,UAwBhB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,eAAe,KAAK,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK;;;qBAzJhD,MAAM,EAAE;gBAAU,MAAM,EAAE;;;;EA0gBrD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAH/B,QAAQ,MAGuB,SAyCzC;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAN3B,MAM2B,EAAE,UAAU,EALvC,MAKuC,EAAE,GAAG,EAJ5C,MAI4C,EAAE,KAAK,GAHnD,MAG2D,GAFzD,MAAM,EAAE,CAcpB;AAED;;;;GAIG;AAEH,eAAO,MAAM,cAAc,EAHjB,MAAM,EAGqD,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAFxB,MAEyC,CAAC;AAEpD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAJjC,MAIiC,EAAE,YAAY,EAH/C,MAG+C,GAF7C,MAAM,EAAE,CASpB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAHnC,MAGmC,GAFjC,MAAM,EAAE,CAOpB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAHlC,MAGkC,GAFhC,GAAG,CAAC,MAAM,CAAC,CA6BvB;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAJxC,MAIwC,EAAE,GAAG,EAH7C,MAG6C,GAF3C,OAAO,CAiBnB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,OAAO,KAAA,EACP,UAAU,KAAA,EACV,GAAG,KAAA,EACH,KAAK,KAAA,EACL,OAAO,KAAA,EACP,SAAS,UAAQ,QAiPlB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,KAAA,OA+EvC;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,KAAA,EAAE,SAAS,KAAA,QA6B3C;AAuBD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,KAAA,EAAE,MAAM,KAAA,EAAE,SAAS,KAAA,QAMtD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAJ7B,MAI6B,EAAE,MAAM,EAHrC,MAGqC,GAFnC,MAAM,GAAC,SAAS,CAoB5B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,YAPhB,MAAM,WACN,MAAM,SACN,MAAM,WACN,MAAM,KAEJ,MA6FZ,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAJ1B,MAI0B,EAAE,OAAO,EAHnC,MAGmC,GAFjC,MAAM,GAAC,SAAS,CAI5B;AAiDD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAJzB,MAIyB,EAAE,OAAO,EAHlC,MAGkC,GAFhC,MAAM,GAAC,SAAS,CAyB5B;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAHzC,MAGyC,GAFvC,MAAM,EAAE,CAMpB;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAHxC,MAGwC,GAFtC,MAAM,EAAE,CAMpB;AAED;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC5C,wBAAwB,EALf,MAAM,EAKS,EACxB,wBAAwB,EALf,MAAM,EAKS,GAJb,MAAM,EAAE,CASpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAHxC,MAGwC,GAFtC,MAAM,EAAE,CAyBpB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAHpC,MAGoC,GAFlC,MAAM,GAAC,SAAS,CAS5B;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,wBAAwB,EAHtD,MAAM,EAGgD,GAFpD,OAAO,CAInB;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EALvC,MAAM,EAKiC,EAAE,aAAa,EAJtD,MAIsD,EAAE,OAAO,EAH/D,GAAG,CAAC,MAAM,CAGqD,GAF7D,MAAM,EAAE,CAgBpB;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,QAAQ,EAH9C,MAAM,EAGwC,GAF5C,MAAM,EAAE,CAQpB;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EALvC,MAKuC,EAAE,KAAK,EAJ9C,MAI8C,EAAE,QAAQ,EAHxD,MAAM,EAGkD,GAFtD,MAAM,CAIlB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAJvC,MAIuC,EAAE,aAAa,EAHtD,MAGsD,GAFpD,IAAI,CAyDhB;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAPtB,MAOsB,EAAE,UAAU,OAAA,EAAE,eAAe,EALnD,MAKmD,EAAE,YAAY,OAAA,GAF/D,MAAM,CA8ClB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,YAJjC,MAAM,mBACN,MAAM,KACJ,OAYZ,CAAC"}