@cyclonedx/cdxgen 9.1.1 → 9.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.
package/index.js CHANGED
@@ -1,7 +1,5 @@
1
- import Ajv from "ajv";
2
- import addFormats from "ajv-formats";
3
1
  import { platform as _platform, homedir, tmpdir } from "node:os";
4
- import { basename, join, dirname, sep } from "node:path";
2
+ import { basename, join, dirname, sep, resolve } from "node:path";
5
3
  import { parse } from "ssri";
6
4
  import {
7
5
  lstatSync,
@@ -91,7 +89,8 @@ import {
91
89
  parseCsProjAssetsData,
92
90
  parseCsPkgLockData,
93
91
  parseCsPkgData,
94
- parseCsProjData
92
+ parseCsProjData,
93
+ DEBUG_MODE
95
94
  } from "./utils.js";
96
95
  import { spawnSync } from "node:child_process";
97
96
  import { fileURLToPath } from "node:url";
@@ -154,13 +153,6 @@ if (process.env.SWIFT_CMD) {
154
153
  const SBT_CACHE_DIR =
155
154
  process.env.SBT_CACHE_DIR || join(homedir(), ".ivy2", "cache");
156
155
 
157
- // Debug mode flag
158
- const DEBUG_MODE =
159
- process.env.CDXGEN_DEBUG_MODE === "debug" ||
160
- process.env.SCAN_DEBUG_MODE === "debug" ||
161
- process.env.SHIFTLEFT_LOGGING_LEVEL === "debug" ||
162
- process.env.NODE_ENV === "development";
163
-
164
156
  // CycloneDX Hash pattern
165
157
  const HASH_PATTERN =
166
158
  "^([a-fA-F0-9]{32}|[a-fA-F0-9]{40}|[a-fA-F0-9]{64}|[a-fA-F0-9]{96}|[a-fA-F0-9]{128})$";
@@ -168,7 +160,16 @@ const HASH_PATTERN =
168
160
  // Timeout milliseconds. Default 10 mins
169
161
  const TIMEOUT_MS = parseInt(process.env.CDXGEN_TIMEOUT_MS) || 10 * 60 * 1000;
170
162
 
171
- const createDefaultParentComponent = (path) => {
163
+ /**
164
+ * Creates a default parent component based on the directory name.
165
+ *
166
+ * @param {string} path Directory or file name
167
+ * @param {string} type Package type
168
+ * @returns component object
169
+ */
170
+ const createDefaultParentComponent = (path, type = "application") => {
171
+ // Expands any relative path such as dot
172
+ path = resolve(path);
172
173
  // Create a parent component based on the directory name
173
174
  let dirName =
174
175
  existsSync(path) && lstatSync(path).isDirectory()
@@ -182,7 +183,7 @@ const createDefaultParentComponent = (path) => {
182
183
  type: "application"
183
184
  };
184
185
  const ppurl = new PackageURL(
185
- "application",
186
+ type,
186
187
  parentComponent.group,
187
188
  parentComponent.name,
188
189
  parentComponent.version,
@@ -257,6 +258,41 @@ function addDependencies(dependencies) {
257
258
  return deps_list;
258
259
  }
259
260
 
261
+ const addToolsSection = (options, format) => {
262
+ if (options.specVersion === 1.4) {
263
+ if (format === "json") {
264
+ return [
265
+ {
266
+ vendor: "cyclonedx",
267
+ name: "cdxgen",
268
+ version: _version
269
+ }
270
+ ];
271
+ } else {
272
+ return [
273
+ {
274
+ tool: {
275
+ vendor: "cyclonedx",
276
+ name: "cdxgen",
277
+ version: _version
278
+ }
279
+ }
280
+ ];
281
+ }
282
+ }
283
+ return {
284
+ components: [
285
+ {
286
+ group: "@cyclonedx",
287
+ name: "cdxgen",
288
+ version: _version,
289
+ purl: `pkg:npm/%40cyclonedx/cdxgen@${_version}`,
290
+ type: "application",
291
+ "bom-ref": `pkg:npm/@cyclonedx/cdxgen@${_version}`
292
+ }
293
+ ]
294
+ };
295
+ };
260
296
  /**
261
297
  * Function to create metadata block
262
298
  *
@@ -264,20 +300,10 @@ function addDependencies(dependencies) {
264
300
  function addMetadata(parentComponent = {}, format = "xml", options = {}) {
265
301
  // DO NOT fork this project to just change the vendor or author's name
266
302
  // Try to contribute to this project by sending PR or filing issues
303
+ const tools = addToolsSection(options, format);
267
304
  const metadata = {
268
305
  timestamp: new Date().toISOString(),
269
- tools: {
270
- components: [
271
- {
272
- group: "@cyclonedx",
273
- name: "cdxgen",
274
- version: _version,
275
- purl: `pkg:npm/%40cyclonedx/cdxgen@${_version}`,
276
- type: "application",
277
- "bom-ref": `pkg:npm/%40cyclonedx/cdxgen@${_version}`
278
- }
279
- ]
280
- },
306
+ tools,
281
307
  authors: [
282
308
  {
283
309
  author: { name: "Prabhu Subramanian", email: "prabhu@appthreat.com" }
@@ -286,58 +312,35 @@ function addMetadata(parentComponent = {}, format = "xml", options = {}) {
286
312
  supplier: undefined
287
313
  };
288
314
  if (format === "json") {
289
- metadata.tools = {
290
- components: [
291
- {
292
- group: "@cyclonedx",
293
- name: "cdxgen",
294
- version: _version,
295
- purl: `pkg:npm/%40cyclonedx/cdxgen@${_version}`,
296
- type: "application",
297
- "bom-ref": `pkg:npm/%40cyclonedx/cdxgen@${_version}`
298
- }
299
- ]
300
- };
315
+ metadata.tools = tools;
301
316
  metadata.authors = [
302
317
  { name: "Prabhu Subramanian", email: "prabhu@appthreat.com" }
303
318
  ];
304
319
  }
305
- if (
306
- parentComponent &&
307
- Object.keys(parentComponent) &&
308
- Object.keys(parentComponent).length
309
- ) {
310
- const allPComponents = listComponents(
311
- {},
312
- {},
313
- parentComponent,
314
- parentComponent.type,
315
- format
316
- );
317
- if (allPComponents.length) {
318
- const firstPComp = allPComponents[0];
319
- if (format == "xml" && firstPComp.component) {
320
- metadata.component = firstPComp.component;
321
- } else {
322
- // Retain the components of parent component
323
- // Bug #317 fix
324
- if (parentComponent && parentComponent.components) {
325
- firstPComp.components = parentComponent.components;
326
- }
327
- if (firstPComp.evidence) {
328
- delete firstPComp.evidence;
329
- }
330
- metadata.component = firstPComp;
331
- }
332
- } else {
333
- // As a fallback, retain the parent component
334
- if (format === "json") {
335
- if (parentComponent.evidence) {
336
- delete parentComponent.evidence;
320
+ if (parentComponent && Object.keys(parentComponent).length) {
321
+ if (parentComponent) {
322
+ delete parentComponent.evidence;
323
+ delete parentComponent._integrity;
324
+ }
325
+ if (parentComponent && parentComponent.components) {
326
+ for (const comp of parentComponent.components) {
327
+ delete comp.evidence;
328
+ delete comp._integrity;
329
+ if (!comp["bom-ref"] && comp.name && comp.type) {
330
+ let fullName =
331
+ comp.group && comp.group.length
332
+ ? `${comp.group}/${comp.name}`
333
+ : comp.name;
334
+ if (comp.version && comp.version.length) {
335
+ fullName = `${fullName}@${comp.version}`;
336
+ }
337
+ comp["bom-ref"] = `pkg:${comp.type}/${fullName}`;
337
338
  }
338
- metadata.component = parentComponent;
339
339
  }
340
340
  }
341
+ if (format === "json") {
342
+ metadata.component = parentComponent;
343
+ }
341
344
  }
342
345
  if (options) {
343
346
  const mproperties = [];
@@ -599,16 +602,6 @@ function addComponent(
599
602
  if (!ptype && pkg.qualifiers && pkg.qualifiers.type === "jar") {
600
603
  ptype = "maven";
601
604
  }
602
- // Skip @types package for npm
603
- if (
604
- ptype == "npm" &&
605
- (group === "types" ||
606
- group === "@types" ||
607
- !name ||
608
- name.startsWith("@types"))
609
- ) {
610
- return;
611
- }
612
605
  const version = pkg.version;
613
606
  if (!version || ["dummy", "ignore"].includes(version)) {
614
607
  return;
@@ -664,10 +657,10 @@ function addComponent(
664
657
  };
665
658
  if (format === "xml") {
666
659
  component["@type"] = determinePackageType(pkg);
667
- component["@bom-ref"] = purlString;
660
+ component["@bom-ref"] = decodeURIComponent(purlString);
668
661
  } else {
669
662
  component["type"] = determinePackageType(pkg);
670
- component["bom-ref"] = purlString;
663
+ component["bom-ref"] = decodeURIComponent(purlString);
671
664
  }
672
665
  if (
673
666
  component.externalReferences === undefined ||
@@ -680,7 +673,11 @@ function addComponent(
680
673
  // Retain any component properties
681
674
  if (format === "json") {
682
675
  // Retain evidence
683
- if (pkg.evidence && Object.keys(pkg.evidence).length) {
676
+ if (
677
+ options.specVersion >= 1.5 &&
678
+ pkg.evidence &&
679
+ Object.keys(pkg.evidence).length
680
+ ) {
684
681
  component.evidence = pkg.evidence;
685
682
  }
686
683
  if (pkg.properties && pkg.properties.length) {
@@ -887,7 +884,10 @@ const buildBomXml = (
887
884
  const bom = create("bom", {
888
885
  encoding: "utf-8",
889
886
  separateArrayItems: true
890
- }).att("xmlns", "http://cyclonedx.org/schema/bom/1.5");
887
+ }).att(
888
+ "xmlns",
889
+ `http://cyclonedx.org/schema/bom/${"" + (options.specVersion || 1.5)}`
890
+ );
891
891
  bom.att("serialNumber", serialNum);
892
892
  bom.att("version", 1);
893
893
  const metadata = addMetadata(parentComponent, "xml", options);
@@ -934,7 +934,7 @@ const buildBomNSData = (options, pkgInfo, ptype, context) => {
934
934
  allImports = context.allImports;
935
935
  }
936
936
  const nsMapping = context.nsMapping || {};
937
- const dependencies = context.dependencies || [];
937
+ const dependencies = !options.requiredOnly ? context.dependencies || [] : [];
938
938
  const parentComponent =
939
939
  determineParentComponent(options) || context.parentComponent;
940
940
  const metadata = addMetadata(parentComponent, "json", options);
@@ -950,7 +950,7 @@ const buildBomNSData = (options, pkgInfo, ptype, context) => {
950
950
  // CycloneDX 1.5 Json Template
951
951
  const jsonTpl = {
952
952
  bomFormat: "CycloneDX",
953
- specVersion: "1.5",
953
+ specVersion: "" + (options.specVersion || "1.5"),
954
954
  serialNumber: serialNum,
955
955
  version: 1,
956
956
  metadata: metadata,
@@ -1131,11 +1131,17 @@ export const createJavaBom = async (path, options) => {
1131
1131
  "Resolve the above maven error. This could be due to the following:\n"
1132
1132
  );
1133
1133
  if (
1134
- result.stderr &&
1135
- result.stderr.includes(
1136
- "Could not resolve dependencies" ||
1137
- result.stderr.includes("no dependency information available")
1138
- )
1134
+ result.stdout &&
1135
+ (result.stdout.includes("Non-resolvable parent POM") ||
1136
+ result.stdout.includes("points at wrong local POM"))
1137
+ ) {
1138
+ console.log(
1139
+ "1. Check if the pom.xml contains valid settings such `parent.relativePath` to make mvn command work from within the sub-directory."
1140
+ );
1141
+ } else if (
1142
+ result.stdout &&
1143
+ (result.stdout.includes("Could not resolve dependencies") ||
1144
+ result.stdout.includes("no dependency information available"))
1139
1145
  ) {
1140
1146
  console.log(
1141
1147
  "1. Try building the project with 'mvn package -Dmaven.test.skip=true' using the correct version of Java and maven before invoking cdxgen."
@@ -1677,6 +1683,7 @@ export const createNodejsBom = async (path, options) => {
1677
1683
  let manifestFiles = [];
1678
1684
  let dependencies = [];
1679
1685
  let parentComponent = {};
1686
+ const parentSubComponents = [];
1680
1687
  let ppurl = "";
1681
1688
  // Docker mode requires special handling
1682
1689
  if (["docker", "oci", "os"].includes(options.projectType)) {
@@ -1811,8 +1818,14 @@ export const createNodejsBom = async (path, options) => {
1811
1818
  // Parse package-lock.json if available
1812
1819
  const parsedList = await parsePkgLock(f);
1813
1820
  const dlist = parsedList.pkgList;
1814
- parentComponent = dlist.splice(0, 1)[0] || {};
1815
- parentComponent.type = "application";
1821
+ const tmpParentComponent = dlist.splice(0, 1)[0] || {};
1822
+ tmpParentComponent.type = "application";
1823
+ // Create a default parent component based on directory name
1824
+ if (!Object.keys(parentComponent).length) {
1825
+ parentComponent = tmpParentComponent;
1826
+ } else {
1827
+ parentSubComponents.push(tmpParentComponent);
1828
+ }
1816
1829
  if (dlist && dlist.length) {
1817
1830
  pkgList = pkgList.concat(dlist);
1818
1831
  }
@@ -1890,28 +1903,38 @@ export const createNodejsBom = async (path, options) => {
1890
1903
  if (existsSync(packageJsonF)) {
1891
1904
  const pcs = await parsePkgJson(packageJsonF);
1892
1905
  if (pcs.length) {
1893
- parentComponent = pcs[0];
1894
- parentComponent.type = "application";
1906
+ const tmpParentComponent = pcs[0];
1907
+ tmpParentComponent.type = "application";
1908
+ if (!Object.keys(parentComponent).length) {
1909
+ parentComponent = tmpParentComponent;
1910
+ } else {
1911
+ parentSubComponents.push(tmpParentComponent);
1912
+ }
1895
1913
  }
1896
1914
  } else {
1897
1915
  let dirName = dirname(f);
1898
1916
  const tmpA = dirName.split(sep);
1899
1917
  dirName = tmpA[tmpA.length - 1];
1900
- parentComponent = {
1918
+ const tmpParentComponent = {
1901
1919
  group: "",
1902
1920
  name: dirName,
1903
1921
  type: "application"
1904
1922
  };
1905
1923
  ppurl = new PackageURL(
1906
1924
  "npm",
1907
- parentComponent.group,
1908
- parentComponent.name,
1909
- parentComponent.version,
1925
+ tmpParentComponent.group,
1926
+ tmpParentComponent.name,
1927
+ tmpParentComponent.version,
1910
1928
  null,
1911
1929
  null
1912
1930
  ).toString();
1913
- parentComponent["bom-ref"] = ppurl;
1914
- parentComponent["purl"] = ppurl;
1931
+ tmpParentComponent["bom-ref"] = ppurl;
1932
+ tmpParentComponent["purl"] = ppurl;
1933
+ if (!Object.keys(parentComponent).length) {
1934
+ parentComponent = tmpParentComponent;
1935
+ } else {
1936
+ parentSubComponents.push(tmpParentComponent);
1937
+ }
1915
1938
  }
1916
1939
  // Parse yarn.lock if available. This check is after rush.json since
1917
1940
  // rush.js could include yarn.lock :(
@@ -1970,17 +1993,17 @@ export const createNodejsBom = async (path, options) => {
1970
1993
  parentComponent
1971
1994
  });
1972
1995
  }
1973
- // Projects containing just min files or bower
1974
- if (pkgList) {
1975
- return buildBomNSData(options, pkgList, "npm", {
1976
- allImports,
1977
- src: path,
1978
- filename: manifestFiles.join(", "),
1979
- dependencies,
1980
- parentComponent
1981
- });
1996
+ // Retain the components of parent component
1997
+ if (parentSubComponents.length) {
1998
+ parentComponent.components = parentSubComponents;
1982
1999
  }
1983
- return {};
2000
+ return buildBomNSData(options, pkgList, "npm", {
2001
+ allImports,
2002
+ src: path,
2003
+ filename: manifestFiles.join(", "),
2004
+ dependencies,
2005
+ parentComponent
2006
+ });
1984
2007
  };
1985
2008
 
1986
2009
  /**
@@ -1995,7 +2018,7 @@ export const createPythonBom = async (path, options) => {
1995
2018
  let dependencies = [];
1996
2019
  let pkgList = [];
1997
2020
  const tempDir = mkdtempSync(join(tmpdir(), "cdxgen-venv-"));
1998
- const parentComponent = createDefaultParentComponent(path);
2021
+ const parentComponent = createDefaultParentComponent(path, "pypi");
1999
2022
  const pipenvMode = existsSync(join(path, "Pipfile"));
2000
2023
  const poetryFiles = getAllFiles(
2001
2024
  path,
@@ -2103,6 +2126,12 @@ export const createPythonBom = async (path, options) => {
2103
2126
  pkgList = pkgList.concat(pkgMap.pkgList);
2104
2127
  frozen = true;
2105
2128
  }
2129
+ if (pkgMap.dependenciesList) {
2130
+ dependencies = mergeDependencies(
2131
+ dependencies,
2132
+ pkgMap.dependenciesList
2133
+ );
2134
+ }
2106
2135
  }
2107
2136
  // Fallback to parsing manually
2108
2137
  if (!pkgList.length || !frozen) {
@@ -2152,7 +2181,9 @@ export const createPythonBom = async (path, options) => {
2152
2181
  if (retMap.pkgList && retMap.pkgList.length) {
2153
2182
  pkgList = pkgList.concat(retMap.pkgList);
2154
2183
  for (const p of retMap.pkgList) {
2155
- parentDependsOn.push(`pkg:pypi/${p.name}@${p.version}`);
2184
+ if (p.version) {
2185
+ parentDependsOn.push(`pkg:pypi/${p.name}@${p.version}`);
2186
+ }
2156
2187
  }
2157
2188
  }
2158
2189
  if (retMap.dependenciesList) {
@@ -2567,7 +2598,7 @@ export const createDartBom = async (path, options) => {
2567
2598
  * @param path to the project
2568
2599
  * @param options Parse options from the cli
2569
2600
  */
2570
- export const createCppBom = async (path, options) => {
2601
+ export const createCppBom = (path, options) => {
2571
2602
  const conanLockFiles = getAllFiles(
2572
2603
  path,
2573
2604
  (options.multiProject ? "**/" : "") + "conan.lock"
@@ -2736,7 +2767,7 @@ export const createClojureBom = (path, options) => {
2736
2767
  * @param path to the project
2737
2768
  * @param options Parse options from the cli
2738
2769
  */
2739
- export const createHaskellBom = async (path, options) => {
2770
+ export const createHaskellBom = (path, options) => {
2740
2771
  const cabalFiles = getAllFiles(
2741
2772
  path,
2742
2773
  (options.multiProject ? "**/" : "") + "cabal.project.freeze"
@@ -2767,7 +2798,7 @@ export const createHaskellBom = async (path, options) => {
2767
2798
  * @param path to the project
2768
2799
  * @param options Parse options from the cli
2769
2800
  */
2770
- export const createElixirBom = async (path, options) => {
2801
+ export const createElixirBom = (path, options) => {
2771
2802
  const mixFiles = getAllFiles(
2772
2803
  path,
2773
2804
  (options.multiProject ? "**/" : "") + "mix.lock"
@@ -2798,7 +2829,7 @@ export const createElixirBom = async (path, options) => {
2798
2829
  * @param path to the project
2799
2830
  * @param options Parse options from the cli
2800
2831
  */
2801
- export const createGitHubBom = async (path, options) => {
2832
+ export const createGitHubBom = (path, options) => {
2802
2833
  const ghactionFiles = getAllFiles(path, ".github/workflows/" + "*.yml");
2803
2834
  let pkgList = [];
2804
2835
  if (ghactionFiles.length) {
@@ -2826,7 +2857,7 @@ export const createGitHubBom = async (path, options) => {
2826
2857
  * @param path to the project
2827
2858
  * @param options Parse options from the cli
2828
2859
  */
2829
- export const createCloudBuildBom = async (path, options) => {
2860
+ export const createCloudBuildBom = (path, options) => {
2830
2861
  const cbFiles = getAllFiles(path, "cloudbuild.yml");
2831
2862
  let pkgList = [];
2832
2863
  if (cbFiles.length) {
@@ -2947,7 +2978,7 @@ export const createJenkinsBom = async (path, options) => {
2947
2978
  * @param path to the project
2948
2979
  * @param options Parse options from the cli
2949
2980
  */
2950
- export const createHelmBom = async (path, options) => {
2981
+ export const createHelmBom = (path, options) => {
2951
2982
  let pkgList = [];
2952
2983
  const yamlFiles = getAllFiles(
2953
2984
  path,
@@ -2994,7 +3025,7 @@ export const createSwiftBom = (path, options) => {
2994
3025
  if (pkgResolvedFiles.length) {
2995
3026
  for (const f of pkgResolvedFiles) {
2996
3027
  if (!parentComponent || !Object.keys(parentComponent).length) {
2997
- parentComponent = createDefaultParentComponent(f);
3028
+ parentComponent = createDefaultParentComponent(f, "swift");
2998
3029
  }
2999
3030
  if (DEBUG_MODE) {
3000
3031
  console.log("Parsing", f);
@@ -3622,13 +3653,15 @@ export const trimComponents = (components, format) => {
3622
3653
  const filteredComponents = [];
3623
3654
  for (const comp of components) {
3624
3655
  if (format === "xml" && comp.component) {
3625
- if (!keyCache[comp.component.purl]) {
3626
- keyCache[comp.component.purl] = true;
3656
+ const key = comp.component.purl || comp.component["bom-ref"];
3657
+ if (!keyCache[key]) {
3658
+ keyCache[key] = true;
3627
3659
  filteredComponents.push(comp);
3628
3660
  }
3629
3661
  } else {
3630
- if (!keyCache[comp.purl]) {
3631
- keyCache[comp.purl] = true;
3662
+ const key = comp.purl || comp["bom-ref"];
3663
+ if (!keyCache[key]) {
3664
+ keyCache[key] = true;
3632
3665
  filteredComponents.push(comp);
3633
3666
  }
3634
3667
  }
@@ -3674,7 +3707,7 @@ export const dedupeBom = (
3674
3707
  ),
3675
3708
  bomJson: {
3676
3709
  bomFormat: "CycloneDX",
3677
- specVersion: "1.5",
3710
+ specVersion: "" + (options.specVersion || 1.5),
3678
3711
  serialNumber: serialNum,
3679
3712
  version: 1,
3680
3713
  metadata: addMetadata(parentComponent, "json", options),
@@ -3696,7 +3729,8 @@ export const createMultiXBom = async (pathList, options) => {
3696
3729
  let dependencies = [];
3697
3730
  let componentsXmls = [];
3698
3731
  let bomData = undefined;
3699
- let parentComponent = determineParentComponent(options);
3732
+ let parentComponent = determineParentComponent(options) || {};
3733
+ let parentSubComponents = [];
3700
3734
  if (
3701
3735
  ["docker", "oci", "container"].includes(options.projectType) &&
3702
3736
  options.allLayersExplodedDir
@@ -3747,15 +3781,31 @@ export const createMultiXBom = async (pathList, options) => {
3747
3781
  }
3748
3782
  components = components.concat(bomData.bomJson.components);
3749
3783
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
3750
- if (!parentComponent || !Object.keys(parentComponent).length) {
3751
- parentComponent = bomData.parentComponent;
3784
+ if (
3785
+ bomData.parentComponent &&
3786
+ Object.keys(bomData.parentComponent).length
3787
+ ) {
3788
+ parentSubComponents.push(bomData.parentComponent);
3789
+ }
3790
+ if (
3791
+ bomData.parentComponent.components &&
3792
+ bomData.parentComponent.components.length
3793
+ ) {
3794
+ parentSubComponents = parentSubComponents.concat(
3795
+ bomData.parentComponent.components
3796
+ );
3752
3797
  }
3753
3798
  componentsXmls = componentsXmls.concat(
3754
3799
  listComponents(options, {}, bomData.bomJson.components, "npm", "xml")
3755
3800
  );
3756
3801
  }
3757
3802
  bomData = await createJavaBom(path, options);
3758
- if (bomData && bomData.bomJson && bomData.bomJson.components) {
3803
+ if (
3804
+ bomData &&
3805
+ bomData.bomJson &&
3806
+ bomData.bomJson.components &&
3807
+ bomData.bomJson.components.length
3808
+ ) {
3759
3809
  if (DEBUG_MODE) {
3760
3810
  console.log(
3761
3811
  `Found ${bomData.bomJson.components.length} java packages at ${path}`
@@ -3763,15 +3813,23 @@ export const createMultiXBom = async (pathList, options) => {
3763
3813
  }
3764
3814
  components = components.concat(bomData.bomJson.components);
3765
3815
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
3766
- if (!parentComponent || !Object.keys(parentComponent).length) {
3767
- parentComponent = bomData.parentComponent;
3816
+ if (
3817
+ bomData.parentComponent &&
3818
+ Object.keys(bomData.parentComponent).length
3819
+ ) {
3820
+ parentSubComponents.push(bomData.parentComponent);
3768
3821
  }
3769
3822
  componentsXmls = componentsXmls.concat(
3770
3823
  listComponents(options, {}, bomData.bomJson.components, "maven", "xml")
3771
3824
  );
3772
3825
  }
3773
3826
  bomData = await createPythonBom(path, options);
3774
- if (bomData && bomData.bomJson && bomData.bomJson.components) {
3827
+ if (
3828
+ bomData &&
3829
+ bomData.bomJson &&
3830
+ bomData.bomJson.components &&
3831
+ bomData.bomJson.components.length
3832
+ ) {
3775
3833
  if (DEBUG_MODE) {
3776
3834
  console.log(
3777
3835
  `Found ${bomData.bomJson.components.length} python packages at ${path}`
@@ -3779,15 +3837,23 @@ export const createMultiXBom = async (pathList, options) => {
3779
3837
  }
3780
3838
  components = components.concat(bomData.bomJson.components);
3781
3839
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
3782
- if (!parentComponent || !Object.keys(parentComponent).length) {
3783
- parentComponent = bomData.parentComponent;
3840
+ if (
3841
+ bomData.parentComponent &&
3842
+ Object.keys(bomData.parentComponent).length
3843
+ ) {
3844
+ parentSubComponents.push(bomData.parentComponent);
3784
3845
  }
3785
3846
  componentsXmls = componentsXmls.concat(
3786
3847
  listComponents(options, {}, bomData.bomJson.components, "pypi", "xml")
3787
3848
  );
3788
3849
  }
3789
3850
  bomData = await createGoBom(path, options);
3790
- if (bomData && bomData.bomJson && bomData.bomJson.components) {
3851
+ if (
3852
+ bomData &&
3853
+ bomData.bomJson &&
3854
+ bomData.bomJson.components &&
3855
+ bomData.bomJson.components.length
3856
+ ) {
3791
3857
  if (DEBUG_MODE) {
3792
3858
  console.log(
3793
3859
  `Found ${bomData.bomJson.components.length} go packages at ${path}`
@@ -3795,8 +3861,11 @@ export const createMultiXBom = async (pathList, options) => {
3795
3861
  }
3796
3862
  components = components.concat(bomData.bomJson.components);
3797
3863
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
3798
- if (!parentComponent || !Object.keys(parentComponent).length) {
3799
- parentComponent = bomData.parentComponent;
3864
+ if (
3865
+ bomData.parentComponent &&
3866
+ Object.keys(bomData.parentComponent).length
3867
+ ) {
3868
+ parentSubComponents.push(bomData.parentComponent);
3800
3869
  }
3801
3870
  componentsXmls = componentsXmls.concat(
3802
3871
  listComponents(options, {}, bomData.bomJson.components, "golang", "xml")
@@ -3811,8 +3880,11 @@ export const createMultiXBom = async (pathList, options) => {
3811
3880
  }
3812
3881
  components = components.concat(bomData.bomJson.components);
3813
3882
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
3814
- if (!parentComponent || !Object.keys(parentComponent).length) {
3815
- parentComponent = bomData.parentComponent;
3883
+ if (
3884
+ bomData.parentComponent &&
3885
+ Object.keys(bomData.parentComponent).length
3886
+ ) {
3887
+ parentSubComponents.push(bomData.parentComponent);
3816
3888
  }
3817
3889
  componentsXmls = componentsXmls.concat(
3818
3890
  listComponents(options, {}, bomData.bomJson.components, "cargo", "xml")
@@ -3827,8 +3899,11 @@ export const createMultiXBom = async (pathList, options) => {
3827
3899
  }
3828
3900
  components = components.concat(bomData.bomJson.components);
3829
3901
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
3830
- if (!parentComponent || !Object.keys(parentComponent).length) {
3831
- parentComponent = bomData.parentComponent;
3902
+ if (
3903
+ bomData.parentComponent &&
3904
+ Object.keys(bomData.parentComponent).length
3905
+ ) {
3906
+ parentSubComponents.push(bomData.parentComponent);
3832
3907
  }
3833
3908
  componentsXmls = componentsXmls.concat(
3834
3909
  listComponents(
@@ -3849,8 +3924,11 @@ export const createMultiXBom = async (pathList, options) => {
3849
3924
  }
3850
3925
  components = components.concat(bomData.bomJson.components);
3851
3926
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
3852
- if (!parentComponent || !Object.keys(parentComponent).length) {
3853
- parentComponent = bomData.parentComponent;
3927
+ if (
3928
+ bomData.parentComponent &&
3929
+ Object.keys(bomData.parentComponent).length
3930
+ ) {
3931
+ parentSubComponents.push(bomData.parentComponent);
3854
3932
  }
3855
3933
  componentsXmls = componentsXmls.concat(
3856
3934
  listComponents(options, {}, bomData.bomJson.components, "gem", "xml")
@@ -3865,8 +3943,11 @@ export const createMultiXBom = async (pathList, options) => {
3865
3943
  }
3866
3944
  components = components.concat(bomData.bomJson.components);
3867
3945
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
3868
- if (!parentComponent || !Object.keys(parentComponent).length) {
3869
- parentComponent = bomData.parentComponent;
3946
+ if (
3947
+ bomData.parentComponent &&
3948
+ Object.keys(bomData.parentComponent).length
3949
+ ) {
3950
+ parentSubComponents.push(bomData.parentComponent);
3870
3951
  }
3871
3952
  componentsXmls = componentsXmls.concat(
3872
3953
  listComponents(options, {}, bomData.bomJson.components, "nuget", "xml")
@@ -3881,14 +3962,17 @@ export const createMultiXBom = async (pathList, options) => {
3881
3962
  }
3882
3963
  components = components.concat(bomData.bomJson.components);
3883
3964
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
3884
- if (!parentComponent || !Object.keys(parentComponent).length) {
3885
- parentComponent = bomData.parentComponent;
3965
+ if (
3966
+ bomData.parentComponent &&
3967
+ Object.keys(bomData.parentComponent).length
3968
+ ) {
3969
+ parentSubComponents.push(bomData.parentComponent);
3886
3970
  }
3887
3971
  componentsXmls = componentsXmls.concat(
3888
3972
  listComponents(options, {}, bomData.bomJson.components, "pub", "xml")
3889
3973
  );
3890
3974
  }
3891
- bomData = await createHaskellBom(path, options);
3975
+ bomData = createHaskellBom(path, options);
3892
3976
  if (bomData && bomData.bomJson && bomData.bomJson.components) {
3893
3977
  if (DEBUG_MODE) {
3894
3978
  console.log(
@@ -3897,8 +3981,11 @@ export const createMultiXBom = async (pathList, options) => {
3897
3981
  }
3898
3982
  components = components.concat(bomData.bomJson.components);
3899
3983
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
3900
- if (!parentComponent || !Object.keys(parentComponent).length) {
3901
- parentComponent = bomData.parentComponent;
3984
+ if (
3985
+ bomData.parentComponent &&
3986
+ Object.keys(bomData.parentComponent).length
3987
+ ) {
3988
+ parentSubComponents.push(bomData.parentComponent);
3902
3989
  }
3903
3990
  componentsXmls = componentsXmls.concat(
3904
3991
  listComponents(
@@ -3910,7 +3997,7 @@ export const createMultiXBom = async (pathList, options) => {
3910
3997
  )
3911
3998
  );
3912
3999
  }
3913
- bomData = await createElixirBom(path, options);
4000
+ bomData = createElixirBom(path, options);
3914
4001
  if (bomData && bomData.bomJson && bomData.bomJson.components) {
3915
4002
  if (DEBUG_MODE) {
3916
4003
  console.log(
@@ -3919,14 +4006,17 @@ export const createMultiXBom = async (pathList, options) => {
3919
4006
  }
3920
4007
  components = components.concat(bomData.bomJson.components);
3921
4008
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
3922
- if (!parentComponent || !Object.keys(parentComponent).length) {
3923
- parentComponent = bomData.parentComponent;
4009
+ if (
4010
+ bomData.parentComponent &&
4011
+ Object.keys(bomData.parentComponent).length
4012
+ ) {
4013
+ parentSubComponents.push(bomData.parentComponent);
3924
4014
  }
3925
4015
  componentsXmls = componentsXmls.concat(
3926
4016
  listComponents(options, {}, bomData.bomJson.components, "hex", "xml")
3927
4017
  );
3928
4018
  }
3929
- bomData = await createCppBom(path, options);
4019
+ bomData = createCppBom(path, options);
3930
4020
  if (bomData && bomData.bomJson && bomData.bomJson.components) {
3931
4021
  if (DEBUG_MODE) {
3932
4022
  console.log(
@@ -3935,8 +4025,11 @@ export const createMultiXBom = async (pathList, options) => {
3935
4025
  }
3936
4026
  components = components.concat(bomData.bomJson.components);
3937
4027
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
3938
- if (!parentComponent || !Object.keys(parentComponent).length) {
3939
- parentComponent = bomData.parentComponent;
4028
+ if (
4029
+ bomData.parentComponent &&
4030
+ Object.keys(bomData.parentComponent).length
4031
+ ) {
4032
+ parentSubComponents.push(bomData.parentComponent);
3940
4033
  }
3941
4034
  componentsXmls = componentsXmls.concat(
3942
4035
  listComponents(options, {}, bomData.bomJson.components, "conan", "xml")
@@ -3951,8 +4044,11 @@ export const createMultiXBom = async (pathList, options) => {
3951
4044
  }
3952
4045
  components = components.concat(bomData.bomJson.components);
3953
4046
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
3954
- if (!parentComponent || !Object.keys(parentComponent).length) {
3955
- parentComponent = bomData.parentComponent;
4047
+ if (
4048
+ bomData.parentComponent &&
4049
+ Object.keys(bomData.parentComponent).length
4050
+ ) {
4051
+ parentSubComponents.push(bomData.parentComponent);
3956
4052
  }
3957
4053
  componentsXmls = componentsXmls.concat(
3958
4054
  listComponents(
@@ -3964,7 +4060,7 @@ export const createMultiXBom = async (pathList, options) => {
3964
4060
  )
3965
4061
  );
3966
4062
  }
3967
- bomData = await createGitHubBom(path, options);
4063
+ bomData = createGitHubBom(path, options);
3968
4064
  if (bomData && bomData.bomJson && bomData.bomJson.components) {
3969
4065
  if (DEBUG_MODE) {
3970
4066
  console.log(
@@ -3973,14 +4069,17 @@ export const createMultiXBom = async (pathList, options) => {
3973
4069
  }
3974
4070
  components = components.concat(bomData.bomJson.components);
3975
4071
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
3976
- if (!parentComponent || !Object.keys(parentComponent).length) {
3977
- parentComponent = bomData.parentComponent;
4072
+ if (
4073
+ bomData.parentComponent &&
4074
+ Object.keys(bomData.parentComponent).length
4075
+ ) {
4076
+ parentSubComponents.push(bomData.parentComponent);
3978
4077
  }
3979
4078
  componentsXmls = componentsXmls.concat(
3980
4079
  listComponents(options, {}, bomData.bomJson.components, "github", "xml")
3981
4080
  );
3982
4081
  }
3983
- bomData = await createCloudBuildBom(path, options);
4082
+ bomData = createCloudBuildBom(path, options);
3984
4083
  if (bomData && bomData.bomJson && bomData.bomJson.components) {
3985
4084
  if (DEBUG_MODE) {
3986
4085
  console.log(
@@ -3989,8 +4088,11 @@ export const createMultiXBom = async (pathList, options) => {
3989
4088
  }
3990
4089
  components = components.concat(bomData.bomJson.components);
3991
4090
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
3992
- if (!parentComponent || !Object.keys(parentComponent).length) {
3993
- parentComponent = bomData.parentComponent;
4091
+ if (
4092
+ bomData.parentComponent &&
4093
+ Object.keys(bomData.parentComponent).length
4094
+ ) {
4095
+ parentSubComponents.push(bomData.parentComponent);
3994
4096
  }
3995
4097
  componentsXmls = componentsXmls.concat(
3996
4098
  listComponents(
@@ -4016,8 +4118,11 @@ export const createMultiXBom = async (pathList, options) => {
4016
4118
  }
4017
4119
  components = components.concat(bomData.bomJson.components);
4018
4120
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
4019
- if (!parentComponent || !Object.keys(parentComponent).length) {
4020
- parentComponent = bomData.parentComponent;
4121
+ if (
4122
+ bomData.parentComponent &&
4123
+ Object.keys(bomData.parentComponent).length
4124
+ ) {
4125
+ parentSubComponents.push(bomData.parentComponent);
4021
4126
  }
4022
4127
  componentsXmls = componentsXmls.concat(
4023
4128
  listComponents(options, {}, bomData.bomJson.components, "swift", "xml")
@@ -4034,8 +4139,11 @@ export const createMultiXBom = async (pathList, options) => {
4034
4139
  }
4035
4140
  components = components.concat(bomData.bomJson.components);
4036
4141
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
4037
- if (!parentComponent || !Object.keys(parentComponent).length) {
4038
- parentComponent = bomData.parentComponent;
4142
+ if (
4143
+ bomData.parentComponent &&
4144
+ Object.keys(bomData.parentComponent).length
4145
+ ) {
4146
+ parentSubComponents.push(bomData.parentComponent);
4039
4147
  }
4040
4148
  componentsXmls = componentsXmls.concat(
4041
4149
  listComponents(options, {}, bomData.bomJson.components, "maven", "xml")
@@ -4052,14 +4160,36 @@ export const createMultiXBom = async (pathList, options) => {
4052
4160
  }
4053
4161
  components = components.concat(bomData.bomJson.components);
4054
4162
  dependencies = dependencies.concat(bomData.bomJson.dependencies);
4055
- if (!parentComponent || !Object.keys(parentComponent).length) {
4056
- parentComponent = bomData.parentComponent;
4163
+ if (
4164
+ bomData.parentComponent &&
4165
+ Object.keys(bomData.parentComponent).length
4166
+ ) {
4167
+ parentSubComponents.push(bomData.parentComponent);
4057
4168
  }
4058
4169
  componentsXmls = componentsXmls.concat(
4059
4170
  listComponents(options, {}, bomData.bomJson.components, "maven", "xml")
4060
4171
  );
4061
4172
  }
4062
4173
  }
4174
+ // Retain the components of parent component
4175
+ if (parentSubComponents.length) {
4176
+ if (!parentComponent || !Object.keys(parentComponent).length) {
4177
+ parentComponent = parentSubComponents[0];
4178
+ }
4179
+ // Our naive approach to appending to sub-components could result in same parent being included as a child
4180
+ // This is filtered out here
4181
+ parentSubComponents = parentSubComponents.filter(
4182
+ (c) => c["bom-ref"] !== parentComponent["bom-ref"]
4183
+ );
4184
+ parentComponent.components = trimComponents(parentSubComponents, "json");
4185
+ if (
4186
+ parentComponent.components.length == 1 &&
4187
+ parentComponent.components[0].name == parentComponent.name
4188
+ ) {
4189
+ parentComponent = parentComponent.components[0];
4190
+ delete parentComponent.components;
4191
+ }
4192
+ }
4063
4193
  return dedupeBom(
4064
4194
  options,
4065
4195
  components,
@@ -4217,7 +4347,7 @@ export const createXBom = async (path, options) => {
4217
4347
  (options.multiProject ? "**/" : "") + "cabal.project.freeze"
4218
4348
  );
4219
4349
  if (hackageFiles.length) {
4220
- return await createHaskellBom(path, options);
4350
+ return createHaskellBom(path, options);
4221
4351
  }
4222
4352
 
4223
4353
  // Elixir
@@ -4226,7 +4356,7 @@ export const createXBom = async (path, options) => {
4226
4356
  (options.multiProject ? "**/" : "") + "mix.lock"
4227
4357
  );
4228
4358
  if (mixFiles.length) {
4229
- return await createElixirBom(path, options);
4359
+ return createElixirBom(path, options);
4230
4360
  }
4231
4361
 
4232
4362
  // cpp
@@ -4239,7 +4369,7 @@ export const createXBom = async (path, options) => {
4239
4369
  (options.multiProject ? "**/" : "") + "conanfile.txt"
4240
4370
  );
4241
4371
  if (conanLockFiles.length || conanFiles.length) {
4242
- return await createCppBom(path, options);
4372
+ return createCppBom(path, options);
4243
4373
  }
4244
4374
 
4245
4375
  // clojure
@@ -4258,7 +4388,7 @@ export const createXBom = async (path, options) => {
4258
4388
  // GitHub actions
4259
4389
  const ghactionFiles = getAllFiles(path, ".github/workflows/" + "*.yml");
4260
4390
  if (ghactionFiles.length) {
4261
- return await createGitHubBom(path, options);
4391
+ return createGitHubBom(path, options);
4262
4392
  }
4263
4393
 
4264
4394
  // Jenkins plugins
@@ -4280,7 +4410,7 @@ export const createXBom = async (path, options) => {
4280
4410
  (options.multiProject ? "**/" : "") + "values.yaml"
4281
4411
  );
4282
4412
  if (chartFiles.length || yamlFiles.length) {
4283
- return await createHelmBom(path, options);
4413
+ return createHelmBom(path, options);
4284
4414
  }
4285
4415
 
4286
4416
  // Docker compose, kubernetes and skaffold
@@ -4306,7 +4436,7 @@ export const createXBom = async (path, options) => {
4306
4436
  (options.multiProject ? "**/" : "") + "cloudbuild.yaml"
4307
4437
  );
4308
4438
  if (cbFiles.length) {
4309
- return await createCloudBuildBom(path, options);
4439
+ return createCloudBuildBom(path, options);
4310
4440
  }
4311
4441
 
4312
4442
  // Swift
@@ -4509,18 +4639,18 @@ export const createBom = async (path, options) => {
4509
4639
  case "hackage":
4510
4640
  case "cabal":
4511
4641
  options.multiProject = true;
4512
- return await createHaskellBom(path, options);
4642
+ return createHaskellBom(path, options);
4513
4643
  case "elixir":
4514
4644
  case "hex":
4515
4645
  case "mix":
4516
4646
  options.multiProject = true;
4517
- return await createElixirBom(path, options);
4647
+ return createElixirBom(path, options);
4518
4648
  case "c":
4519
4649
  case "cpp":
4520
4650
  case "c++":
4521
4651
  case "conan":
4522
4652
  options.multiProject = true;
4523
- return await createCppBom(path, options);
4653
+ return createCppBom(path, options);
4524
4654
  case "clojure":
4525
4655
  case "edn":
4526
4656
  case "clj":
@@ -4530,7 +4660,7 @@ export const createBom = async (path, options) => {
4530
4660
  case "github":
4531
4661
  case "actions":
4532
4662
  options.multiProject = true;
4533
- return await createGitHubBom(path, options);
4663
+ return createGitHubBom(path, options);
4534
4664
  case "os":
4535
4665
  case "osquery":
4536
4666
  case "windows":
@@ -4543,11 +4673,11 @@ export const createBom = async (path, options) => {
4543
4673
  case "helm":
4544
4674
  case "charts":
4545
4675
  options.multiProject = true;
4546
- return await createHelmBom(path, options);
4676
+ return createHelmBom(path, options);
4547
4677
  case "helm-index":
4548
4678
  case "helm-repo":
4549
4679
  options.multiProject = true;
4550
- return await createHelmBom(
4680
+ return createHelmBom(
4551
4681
  join(homedir(), ".cache", "helm", "repository"),
4552
4682
  options
4553
4683
  );
@@ -4565,7 +4695,7 @@ export const createBom = async (path, options) => {
4565
4695
  return await createContainerSpecLikeBom(path, options);
4566
4696
  case "cloudbuild":
4567
4697
  options.multiProject = true;
4568
- return await createCloudBuildBom(path, options);
4698
+ return createCloudBuildBom(path, options);
4569
4699
  case "swift":
4570
4700
  options.multiProject = true;
4571
4701
  return createSwiftBom(path, options);
@@ -4662,38 +4792,3 @@ export async function submitBom(args, bomContents) {
4662
4792
  }
4663
4793
  }
4664
4794
  }
4665
-
4666
- /**
4667
- * Validate the generated bom using jsonschema
4668
- *
4669
- * @param {object} bomJson content
4670
- */
4671
- export const validateBom = (bomJson) => {
4672
- if (!bomJson) {
4673
- return true;
4674
- }
4675
- const schema = JSON.parse(
4676
- readFileSync(join(dirName, "data", "bom-1.5.schema.json"))
4677
- );
4678
- const defsSchema = JSON.parse(
4679
- readFileSync(join(dirName, "data", "jsf-0.82.schema.json"))
4680
- );
4681
- const spdxSchema = JSON.parse(
4682
- readFileSync(join(dirName, "data", "spdx.schema.json"))
4683
- );
4684
- const ajv = new Ajv({
4685
- schemas: [schema, defsSchema, spdxSchema],
4686
- strict: false,
4687
- logger: false
4688
- });
4689
- addFormats(ajv);
4690
- const validate = ajv.getSchema(
4691
- "http://cyclonedx.org/schema/bom-1.5.schema.json"
4692
- );
4693
- const isValid = validate(bomJson);
4694
- if (!isValid) {
4695
- console.log(validate.errors);
4696
- return false;
4697
- }
4698
- return true;
4699
- };