@cyclonedx/cdxgen 9.6.0 → 9.6.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.
package/README.md CHANGED
@@ -376,7 +376,7 @@ cdxgen -t os
376
376
 
377
377
  This feature is powered by osquery which is [installed](https://github.com/cyclonedx/cdxgen-plugins-bin/blob/main/build.sh#L8) along with the binary plugins. cdxgen would opportunistically try to detect as many components, apps and extensions as possible using the [default queries](queries.json). The process would take several minutes and result in an SBoM file with thousands of components.
378
378
 
379
- ## Generating component evidence
379
+ ## Generating SaaSBoM and component evidences
380
380
 
381
381
  See [evinse mode](./ADVANCED.md) in the advanced documentation.
382
382
 
package/bin/evinse.js CHANGED
File without changes
package/bin/verify.js CHANGED
File without changes
package/binary.js CHANGED
@@ -6,13 +6,12 @@ import { PackageURL } from "packageurl-js";
6
6
  import { DEBUG_MODE } from "./utils.js";
7
7
 
8
8
  import { fileURLToPath } from "node:url";
9
- import path from "node:path";
10
9
 
11
10
  let url = import.meta.url;
12
11
  if (!url.startsWith("file://")) {
13
12
  url = new URL(`file://${import.meta.url}`).toString();
14
13
  }
15
- const dirName = import.meta ? path.dirname(fileURLToPath(url)) : __dirname;
14
+ const dirName = import.meta ? dirname(fileURLToPath(url)) : __dirname;
16
15
 
17
16
  const isWin = _platform() === "win32";
18
17
 
@@ -181,6 +180,7 @@ const OS_DISTRO_ALIAS = {
181
180
  "ubuntu-19.10": "eoan",
182
181
  "ubuntu-20.04": "focal",
183
182
  "ubuntu-20.10": "groovy",
183
+ "ubuntu-22.04": "jammy",
184
184
  "ubuntu-23.04": "lunar",
185
185
  "debian-14": "forky",
186
186
  "debian-14.5": "forky",
@@ -266,6 +266,7 @@ export const getCargoAuditableInfo = (src) => {
266
266
 
267
267
  export const getOSPackages = (src) => {
268
268
  const pkgList = [];
269
+ const dependenciesList = [];
269
270
  const allTypes = new Set();
270
271
  if (TRIVY_BIN) {
271
272
  let imageType = "image";
@@ -321,6 +322,54 @@ export const getOSPackages = (src) => {
321
322
  rmSync(tempDir, { recursive: true, force: true });
322
323
  }
323
324
  }
325
+ const osReleaseData = {};
326
+ // Let's try to read the os-release file
327
+ if (existsSync(join(src, "usr", "lib", "os-release"))) {
328
+ const osReleaseInfo = readFileSync(
329
+ join(src, "usr", "lib", "os-release"),
330
+ "utf-8"
331
+ );
332
+ if (osReleaseInfo) {
333
+ osReleaseInfo.split("\n").forEach((l) => {
334
+ if (l.includes("=")) {
335
+ const tmpA = l.split("=");
336
+ osReleaseData[tmpA[0]] = tmpA[1].replace(/"/g, "");
337
+ }
338
+ });
339
+ }
340
+ }
341
+ if (DEBUG_MODE) {
342
+ console.log(osReleaseData);
343
+ }
344
+ let distro_codename = osReleaseData["VERSION_CODENAME"] || "";
345
+ let distro_id = osReleaseData["ID"] || "";
346
+ let distro_id_like = osReleaseData["ID_LIKE"] || "";
347
+ let purl_type = "rpm";
348
+ switch (distro_id) {
349
+ case "debian":
350
+ case "ubuntu":
351
+ case "pop":
352
+ purl_type = "deb";
353
+ break;
354
+ default:
355
+ if (distro_id_like.includes("debian")) {
356
+ purl_type = "deb";
357
+ } else if (
358
+ distro_id_like.includes("rhel") ||
359
+ distro_id_like.includes("centos") ||
360
+ distro_id_like.includes("fedora")
361
+ ) {
362
+ purl_type = "rpm";
363
+ }
364
+ break;
365
+ }
366
+ if (osReleaseData["VERSION_ID"]) {
367
+ distro_id = distro_id + "-" + osReleaseData["VERSION_ID"];
368
+ }
369
+ const tmpDependencies = {};
370
+ (tmpBom.dependencies || []).forEach((d) => {
371
+ tmpDependencies[d.ref] = d.dependsOn;
372
+ });
324
373
  if (tmpBom && tmpBom.components) {
325
374
  for (const comp of tmpBom.components) {
326
375
  if (comp.purl) {
@@ -342,11 +391,11 @@ export const getOSPackages = (src) => {
342
391
  ) {
343
392
  continue;
344
393
  }
394
+ const origBomRef = comp["bom-ref"];
345
395
  // Fix the group
346
396
  let group = dirname(comp.name);
347
397
  const name = basename(comp.name);
348
398
  let purlObj = undefined;
349
- let distro_codename = "";
350
399
  if (group === ".") {
351
400
  group = "";
352
401
  }
@@ -360,19 +409,23 @@ export const getOSPackages = (src) => {
360
409
  comp.group = group;
361
410
  purlObj.namespace = group;
362
411
  }
412
+ if (distro_id && distro_id.length) {
413
+ purlObj.qualifiers["distro"] = distro_id;
414
+ }
415
+ if (distro_codename && distro_codename.length) {
416
+ purlObj.qualifiers["distro_name"] = distro_codename;
417
+ }
363
418
  // Bug fix for mageia and oracle linux
419
+ // Type is being returned as none for ubuntu as well!
364
420
  if (purlObj.type === "none") {
365
- purlObj["type"] = "rpm";
421
+ purlObj["type"] = purl_type;
366
422
  purlObj["namespace"] = "";
367
423
  comp.group = "";
368
- distro_codename = undefined;
369
424
  if (comp.purl && comp.purl.includes(".mga")) {
370
425
  purlObj["namespace"] = "mageia";
371
426
  comp.group = "mageia";
372
427
  purlObj.qualifiers["distro"] = "mageia";
373
428
  distro_codename = "mga";
374
- } else if (comp.purl && comp.purl.includes(".el8")) {
375
- purlObj.qualifiers["distro"] = "el8";
376
429
  }
377
430
  comp.purl = new PackageURL(
378
431
  purlObj.type,
@@ -412,25 +465,25 @@ export const getOSPackages = (src) => {
412
465
  );
413
466
  }
414
467
  }
415
- if (distro_codename !== "") {
416
- allTypes.add(distro_codename);
417
- allTypes.add(purlObj.namespace);
418
- purlObj.qualifiers["distro_name"] = distro_codename;
419
- comp.purl = new PackageURL(
420
- purlObj.type,
421
- purlObj.namespace,
422
- name,
423
- purlObj.version,
424
- purlObj.qualifiers,
425
- purlObj.subpath
426
- ).toString();
427
- comp["bom-ref"] = decodeURIComponent(comp.purl);
428
- }
468
+ }
469
+ if (distro_codename !== "") {
470
+ allTypes.add(distro_codename);
471
+ allTypes.add(purlObj.namespace);
472
+ comp.purl = new PackageURL(
473
+ purlObj.type,
474
+ purlObj.namespace,
475
+ name,
476
+ purlObj.version,
477
+ purlObj.qualifiers,
478
+ purlObj.subpath
479
+ ).toString();
480
+ comp["bom-ref"] = decodeURIComponent(comp.purl);
429
481
  }
430
482
  } catch (err) {
431
483
  // continue regardless of error
432
484
  }
433
485
  }
486
+ // Fix licenses
434
487
  if (
435
488
  comp.licenses &&
436
489
  Array.isArray(comp.licenses) &&
@@ -438,6 +491,17 @@ export const getOSPackages = (src) => {
438
491
  ) {
439
492
  comp.licenses = [comp.licenses[0]];
440
493
  }
494
+ // Fix hashes
495
+ if (
496
+ comp.hashes &&
497
+ Array.isArray(comp.hashes) &&
498
+ comp.hashes.length
499
+ ) {
500
+ const hashContent = comp.hashes[0].content;
501
+ if (!hashContent || hashContent.length < 32) {
502
+ delete comp.hashes;
503
+ }
504
+ }
441
505
  const compProperties = comp.properties;
442
506
  let srcName = undefined;
443
507
  let srcVersion = undefined;
@@ -453,6 +517,14 @@ export const getOSPackages = (src) => {
453
517
  }
454
518
  delete comp.properties;
455
519
  pkgList.push(comp);
520
+ const compDeps = retrieveDependencies(
521
+ tmpDependencies,
522
+ origBomRef,
523
+ comp
524
+ );
525
+ if (compDeps) {
526
+ dependenciesList.push(compDeps);
527
+ }
456
528
  // If there is a source package defined include it as well
457
529
  if (srcName && srcVersion && srcName !== comp.name) {
458
530
  const newComp = Object.assign({}, comp);
@@ -474,10 +546,43 @@ export const getOSPackages = (src) => {
474
546
  }
475
547
  }
476
548
  }
477
- return { osPackages: pkgList, allTypes: Array.from(allTypes) };
478
549
  }
479
550
  }
480
- return { osPackages: pkgList, allTypes: Array.from(allTypes) };
551
+ return {
552
+ osPackages: pkgList,
553
+ dependenciesList,
554
+ allTypes: Array.from(allTypes)
555
+ };
556
+ };
557
+
558
+ const retrieveDependencies = (tmpDependencies, origBomRef, comp) => {
559
+ try {
560
+ const tmpDependsOn = tmpDependencies[origBomRef] || [];
561
+ const dependsOn = new Set();
562
+ tmpDependsOn.forEach((d) => {
563
+ try {
564
+ const compPurl = PackageURL.fromString(comp.purl);
565
+ const tmpPurl = PackageURL.fromString(d.replace("none", compPurl.type));
566
+ tmpPurl.type = compPurl.type;
567
+ tmpPurl.namespace = compPurl.namespace;
568
+ if (compPurl.qualifiers) {
569
+ if (compPurl.qualifiers.distro_name) {
570
+ tmpPurl.qualifiers.distro_name = compPurl.qualifiers.distro_name;
571
+ }
572
+ if (compPurl.qualifiers.distro) {
573
+ tmpPurl.qualifiers.distro = compPurl.qualifiers.distro;
574
+ }
575
+ }
576
+ dependsOn.add(decodeURIComponent(tmpPurl.toString()));
577
+ } catch (e) {
578
+ // ignore
579
+ }
580
+ });
581
+ return { ref: comp["bom-ref"], dependsOn: Array.from(dependsOn).sort() };
582
+ } catch (e) {
583
+ // ignore
584
+ }
585
+ return undefined;
481
586
  };
482
587
 
483
588
  export const executeOsQuery = (query) => {
package/index.js CHANGED
@@ -372,7 +372,16 @@ function addMetadata(parentComponent = {}, format = "xml", options = {}) {
372
372
  // We cannot use purl or bom-ref here since they would not match
373
373
  // purl - could have application on one side and a different type
374
374
  // bom-ref could have qualifiers on one side
375
- if (fullName !== parentFullName) {
375
+ // Ignore components that have the same name as the parent component but with latest as the version.
376
+ // These are default components created based on directory names
377
+ if (
378
+ fullName !== parentFullName &&
379
+ !(
380
+ (comp.name === parentComponent.name ||
381
+ comp.name === parentComponent.name + ":latest") &&
382
+ comp.version === "latest"
383
+ )
384
+ ) {
376
385
  if (!comp["bom-ref"]) {
377
386
  comp["bom-ref"] = `pkg:${comp.type}/${fullName}`;
378
387
  }
@@ -380,6 +389,7 @@ function addMetadata(parentComponent = {}, format = "xml", options = {}) {
380
389
  }
381
390
  }
382
391
  } // for
392
+ parentComponent.components = subComponents;
383
393
  }
384
394
  if (format === "json") {
385
395
  metadata.component = parentComponent;
@@ -2199,10 +2209,13 @@ export const createPythonBom = async (path, options) => {
2199
2209
  if (pdmLockFiles && pdmLockFiles.length) {
2200
2210
  poetryFiles = poetryFiles.concat(pdmLockFiles);
2201
2211
  }
2202
- const reqFiles = getAllFiles(
2212
+ let reqFiles = getAllFiles(
2203
2213
  path,
2204
2214
  (options.multiProject ? "**/" : "") + "*requirements*.txt"
2205
2215
  );
2216
+ reqFiles = reqFiles.filter(
2217
+ (f) => !f.includes(join("mercurial", "helptext", "internals"))
2218
+ );
2206
2219
  const reqDirFiles = getAllFiles(
2207
2220
  path,
2208
2221
  (options.multiProject ? "**/" : "") + "requirements/*.txt"
@@ -3910,7 +3923,7 @@ export const mergeDependencies = (
3910
3923
  for (const akey of Object.keys(deps_map)) {
3911
3924
  retlist.push({
3912
3925
  ref: akey,
3913
- dependsOn: Array.from(deps_map[akey])
3926
+ dependsOn: Array.from(deps_map[akey]).sort()
3914
3927
  });
3915
3928
  }
3916
3929
  return retlist;
@@ -4003,7 +4016,7 @@ export const createMultiXBom = async (pathList, options) => {
4003
4016
  ["docker", "oci", "container"].includes(options.projectType) &&
4004
4017
  options.allLayersExplodedDir
4005
4018
  ) {
4006
- const { osPackages, allTypes } = getOSPackages(
4019
+ const { osPackages, dependenciesList, allTypes } = getOSPackages(
4007
4020
  options.allLayersExplodedDir
4008
4021
  );
4009
4022
  if (DEBUG_MODE) {
@@ -4018,6 +4031,17 @@ export const createMultiXBom = async (pathList, options) => {
4018
4031
  componentsXmls = componentsXmls.concat(
4019
4032
  listComponents(options, {}, osPackages, "", "xml")
4020
4033
  );
4034
+ if (dependenciesList && dependenciesList.length) {
4035
+ dependencies = dependencies.concat(dependenciesList);
4036
+ }
4037
+ if (parentComponent && Object.keys(parentComponent).length) {
4038
+ // Make the parent oci image depend on all os components
4039
+ const parentDependsOn = new Set(osPackages.map((p) => p["bom-ref"]));
4040
+ dependencies.splice(0, 0, {
4041
+ ref: parentComponent["bom-ref"],
4042
+ dependsOn: Array.from(parentDependsOn).sort()
4043
+ });
4044
+ }
4021
4045
  }
4022
4046
  if (options.projectType === "os" && options.bomData) {
4023
4047
  bomData = options.bomData;
@@ -4409,7 +4433,12 @@ export const createMultiXBom = async (pathList, options) => {
4409
4433
  // Jar scanning is enabled by default
4410
4434
  // See #330
4411
4435
  bomData = createJarBom(path, options);
4412
- if (bomData && bomData.bomJson && bomData.bomJson.components) {
4436
+ if (
4437
+ bomData &&
4438
+ bomData.bomJson &&
4439
+ bomData.bomJson.components &&
4440
+ bomData.bomJson.components.length
4441
+ ) {
4413
4442
  if (DEBUG_MODE) {
4414
4443
  console.log(
4415
4444
  `Found ${bomData.bomJson.components.length} jar packages at ${path}`
@@ -4430,7 +4459,12 @@ export const createMultiXBom = async (pathList, options) => {
4430
4459
  } // for
4431
4460
  if (options.lastWorkingDir && options.lastWorkingDir !== "") {
4432
4461
  bomData = createJarBom(options.lastWorkingDir, options);
4433
- if (bomData && bomData.bomJson && bomData.bomJson.components) {
4462
+ if (
4463
+ bomData &&
4464
+ bomData.bomJson &&
4465
+ bomData.bomJson.components &&
4466
+ bomData.bomJson.components.length
4467
+ ) {
4434
4468
  if (DEBUG_MODE) {
4435
4469
  console.log(
4436
4470
  `Found ${bomData.bomJson.components.length} jar packages at ${options.lastWorkingDir}`
@@ -4818,8 +4852,26 @@ export const createBom = async (path, options) => {
4818
4852
  purl: "pkg:oci/" + inspectData.RepoDigests[0],
4819
4853
  _integrity: inspectData.RepoDigests[0].replace("sha256:", "sha256-")
4820
4854
  };
4855
+ options.parentComponent["bom-ref"] = options.parentComponent.purl;
4821
4856
  }
4857
+ } else if (inspectData.Id) {
4858
+ options.parentComponent = {
4859
+ name: inspectData.RepoDigests[0].split("@")[0],
4860
+ version: inspectData.RepoDigests[0]
4861
+ .split("@")[1]
4862
+ .replace("sha256:", ""),
4863
+ type: "container",
4864
+ purl: "pkg:oci/" + inspectData.RepoDigests[0],
4865
+ _integrity: inspectData.RepoDigests[0].replace("sha256:", "sha256-")
4866
+ };
4867
+ options.parentComponent["bom-ref"] = options.parentComponent.purl;
4822
4868
  }
4869
+ } else {
4870
+ options.parentComponent = createDefaultParentComponent(
4871
+ path,
4872
+ "container",
4873
+ options
4874
+ );
4823
4875
  }
4824
4876
  // Pass the entire export data about the image layers
4825
4877
  options.exportData = exportData;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "9.6.0",
3
+ "version": "9.6.1",
4
4
  "description": "Creates CycloneDX Software Bill-of-Materials (SBOM) from source or container image",
5
5
  "homepage": "http://github.com/cyclonedx/cdxgen",
6
6
  "author": "Prabhu Subramanian <prabhu@appthreat.com>",
@@ -53,8 +53,8 @@
53
53
  "url": "https://github.com/cyclonedx/cdxgen/issues"
54
54
  },
55
55
  "dependencies": {
56
- "@babel/parser": "^7.22.10",
57
- "@babel/traverse": "^7.22.10",
56
+ "@babel/parser": "^7.22.11",
57
+ "@babel/traverse": "^7.22.11",
58
58
  "ajv": "^8.12.0",
59
59
  "ajv-formats": "^2.1.1",
60
60
  "cheerio": "^1.0.0-rc.12",
@@ -68,7 +68,7 @@
68
68
  "node-stream-zip": "^1.15.0",
69
69
  "packageurl-js": "^1.0.2",
70
70
  "prettify-xml": "^1.2.0",
71
- "properties-reader": "^2.2.0",
71
+ "properties-reader": "^2.3.0",
72
72
  "semver": "^7.5.3",
73
73
  "ssri": "^10.0.4",
74
74
  "table": "^6.8.1",
@@ -80,7 +80,7 @@
80
80
  },
81
81
  "optionalDependencies": {
82
82
  "@appthreat/atom": "^1.1.4",
83
- "@cyclonedx/cdxgen-plugins-bin": "^1.2.0",
83
+ "@cyclonedx/cdxgen-plugins-bin": "^1.3.0",
84
84
  "body-parser": "^1.20.2",
85
85
  "compression": "^1.7.4",
86
86
  "connect": "^3.7.0",
@@ -95,8 +95,8 @@
95
95
  ],
96
96
  "devDependencies": {
97
97
  "caxa": "^3.0.1",
98
- "eslint": "^8.47.0",
99
- "jest": "^29.5.0",
100
- "prettier": "3.0.1"
98
+ "eslint": "^8.48.0",
99
+ "jest": "^29.6.4",
100
+ "prettier": "3.0.2"
101
101
  }
102
102
  }