@cyclonedx/cdxgen 9.8.7 → 9.8.8

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
@@ -4,7 +4,7 @@
4
4
 
5
5
  cdxgen is a cli tool, library, [REPL](./ADVANCED.md), and server to create a valid and compliant [CycloneDX][cyclonedx-homepage] Software Bill of Materials (SBOM) containing an aggregate of all project dependencies for c/c++, node.js, php, python, ruby, rust, java, .Net, dart, haskell, elixir, and Go projects in JSON format. CycloneDX 1.5 is a lightweight SBOM specification that is easily created, human and machine-readable, and simple to parse.
6
6
 
7
- When used with plugins, cdxgen could generate an OBoM for Linux docker images and even VMs running Linux or Windows operating systems. cdxgen also includes an evinse tool to generate component evidence and SaaSBOM for some languages.
7
+ When used with plugins, cdxgen could generate an OBOM for Linux docker images and even VMs running Linux or Windows operating systems. cdxgen also includes an evinse tool to generate component evidence and SaaSBOM for some languages.
8
8
 
9
9
  NOTE:
10
10
 
@@ -61,7 +61,7 @@ NOTE:
61
61
 
62
62
  Footnotes:
63
63
 
64
- - [1] - For multi-module applications, the BoM file could include components not included in the packaged war or ear file.
64
+ - [1] - For multi-module applications, the BOM file could include components not included in the packaged war or ear file.
65
65
  - [2] - Pip freeze is automatically performed to improve precision. Requires virtual environment.
66
66
  - [3] - Perform dotnet or nuget restore to generate project.assets.json. Without this file, cdxgen would not include indirect dependencies.
67
67
  - [4] - See the section on plugins
@@ -197,7 +197,7 @@ To print the SBOM as a table pass `-p` argument.
197
197
  cdxgen -t java -o bom.json -p
198
198
  ```
199
199
 
200
- To recursively generate a single BoM for all languages pass `-r` argument.
200
+ To recursively generate a single BOM for all languages pass `-r` argument.
201
201
 
202
202
  ```shell
203
203
  cdxgen -r -o bom.json
@@ -254,6 +254,14 @@ Arguments can be passed either via the query string or as a JSON body. The follo
254
254
  | projectGroup | Dependency track project group |
255
255
  | projectVersion | Dependency track project version [default: ""] |
256
256
 
257
+ ### Health endpoint
258
+
259
+ Use the /health endpoint to check if the SBOM server is up and running.
260
+
261
+ ```shell
262
+ curl "http://127.0.0.1:9090/health"
263
+ ```
264
+
257
265
  ### Scanning a local path
258
266
 
259
267
  ```shell
@@ -281,7 +289,7 @@ docker compose up
281
289
 
282
290
  ## War file support
283
291
 
284
- cdxgen can generate a BoM file from a given war file.
292
+ cdxgen can generate a BOM file from a given war file.
285
293
 
286
294
  ```shell
287
295
  # cdxgen -t java app.war
@@ -318,6 +326,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
318
326
  - Scala SBT
319
327
  - Python (requirements.txt, setup.py, pyproject.toml, poetry.lock)
320
328
  - csharp (projects.assets.json)
329
+ - Go (go.mod)
321
330
 
322
331
  ## Environment variables
323
332
 
@@ -399,9 +408,9 @@ systemctl --user start podman.socket
399
408
  podman system service -t 0 &
400
409
  ```
401
410
 
402
- ### Generate OBoM for a live system
411
+ ### Generate OBOM for a live system
403
412
 
404
- You can use the `obom` command to generate an OBoM for a live system or a VM for compliance and vulnerability management purposes. Windows and Linux operating systems are supported in this mode.
413
+ You can use the `obom` command to generate an OBOM for a live system or a VM for compliance and vulnerability management purposes. Windows and Linux operating systems are supported in this mode.
405
414
 
406
415
  ```shell
407
416
  # obom is an alias for cdxgen -t os
@@ -417,7 +426,7 @@ See [evinse mode](./ADVANCED.md) in the advanced documentation.
417
426
 
418
427
  ## BoM signing
419
428
 
420
- cdxgen can sign the generated BoM json file to increase authenticity and non-repudiation capabilities. To enable this, set the following environment variables.
429
+ cdxgen can sign the generated BOM json file to increase authenticity and non-repudiation capabilities. To enable this, set the following environment variables.
421
430
 
422
431
  - SBOM_SIGN_ALGORITHM: Algorithm. Example: RS512
423
432
  - SBOM_SIGN_PRIVATE_KEY: Location to the RSA private key
package/analyzer.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { parse } from "@babel/parser";
2
2
  import traverse from "@babel/traverse";
3
- import { join } from "path";
4
- import { readdirSync, statSync, readFileSync } from "fs";
5
- import { basename, resolve, isAbsolute, relative } from "path";
3
+ import { join } from "node:path";
4
+ import { readdirSync, statSync, readFileSync } from "node:fs";
5
+ import { basename, resolve, isAbsolute, relative } from "node:path";
6
6
 
7
7
  const IGNORE_DIRS = process.env.ASTGEN_IGNORE_DIRS
8
8
  ? process.env.ASTGEN_IGNORE_DIRS.split(",")
package/bin/cdxgen.js CHANGED
@@ -6,8 +6,7 @@ import fs from "node:fs";
6
6
  import { tmpdir } from "node:os";
7
7
  import { basename, dirname, join, resolve } from "node:path";
8
8
  import jws from "jws";
9
- import crypto from "crypto";
10
- import { start as _serverStart } from "../server.js";
9
+ import crypto from "node:crypto";
11
10
  import { fileURLToPath } from "node:url";
12
11
  import globalAgent from "global-agent";
13
12
  import process from "node:process";
@@ -243,7 +242,8 @@ const checkPermissions = (filePath) => {
243
242
  (async () => {
244
243
  // Start SBOM server
245
244
  if (args.server) {
246
- return await _serverStart(options);
245
+ const serverModule = await import("../server.js");
246
+ return await serverModule.start(options);
247
247
  }
248
248
  // Check if cdxgen has the required permissions
249
249
  if (!checkPermissions(filePath)) {
package/bin/repl.js CHANGED
@@ -110,16 +110,16 @@ cdxgenRepl.defineCommand("create", {
110
110
  });
111
111
  if (bomNSData) {
112
112
  sbom = bomNSData.bomJson;
113
- console.log("✅ BoM imported successfully.");
114
- console.log("💭 Type .print to view the BoM as a table");
113
+ console.log("✅ BOM imported successfully.");
114
+ console.log("💭 Type .print to view the BOM as a table");
115
115
  } else {
116
- console.log("BoM was not generated successfully");
116
+ console.log("BOM was not generated successfully");
117
117
  }
118
118
  this.displayPrompt();
119
119
  }
120
120
  });
121
121
  cdxgenRepl.defineCommand("import", {
122
- help: "import an existing BoM",
122
+ help: "import an existing BOM",
123
123
  action(sbomOrPath) {
124
124
  this.clearBufferedCommand();
125
125
  importSbom(sbomOrPath);
@@ -139,7 +139,7 @@ cdxgenRepl.defineCommand("sbom", {
139
139
  console.log(sbom);
140
140
  } else {
141
141
  console.log(
142
- "⚠ No BoM is loaded. Use .import command to import an existing BoM"
142
+ "⚠ No BOM is loaded. Use .import command to import an existing BOM"
143
143
  );
144
144
  }
145
145
  this.displayPrompt();
@@ -171,7 +171,7 @@ cdxgenRepl.defineCommand("search", {
171
171
  }
172
172
  } else {
173
173
  console.log(
174
- "⚠ No BoM is loaded. Use .import command to import an existing BoM"
174
+ "⚠ No BOM is loaded. Use .import command to import an existing BOM"
175
175
  );
176
176
  }
177
177
  this.displayPrompt();
@@ -205,7 +205,7 @@ cdxgenRepl.defineCommand("sort", {
205
205
  }
206
206
  } else {
207
207
  console.log(
208
- "⚠ No BoM is loaded. Use .import command to import an existing BoM"
208
+ "⚠ No BOM is loaded. Use .import command to import an existing BOM"
209
209
  );
210
210
  }
211
211
  this.displayPrompt();
@@ -229,7 +229,7 @@ cdxgenRepl.defineCommand("query", {
229
229
  }
230
230
  } else {
231
231
  console.log(
232
- "⚠ No BoM is loaded. Use .import command to import an existing BoM"
232
+ "⚠ No BOM is loaded. Use .import command to import an existing BOM"
233
233
  );
234
234
  }
235
235
  this.displayPrompt();
@@ -242,7 +242,7 @@ cdxgenRepl.defineCommand("print", {
242
242
  printTable(sbom);
243
243
  } else {
244
244
  console.log(
245
- "⚠ No BoM is loaded. Use .import command to import an existing BoM"
245
+ "⚠ No BOM is loaded. Use .import command to import an existing BOM"
246
246
  );
247
247
  }
248
248
  this.displayPrompt();
@@ -255,7 +255,7 @@ cdxgenRepl.defineCommand("tree", {
255
255
  printDependencyTree(sbom);
256
256
  } else {
257
257
  console.log(
258
- "⚠ No BoM is loaded. Use .import command to import an existing BoM"
258
+ "⚠ No BOM is loaded. Use .import command to import an existing BOM"
259
259
  );
260
260
  }
261
261
  this.displayPrompt();
@@ -271,7 +271,7 @@ cdxgenRepl.defineCommand("validate", {
271
271
  }
272
272
  } else {
273
273
  console.log(
274
- "⚠ No BoM is loaded. Use .import command to import an existing BoM"
274
+ "⚠ No BOM is loaded. Use .import command to import an existing BOM"
275
275
  );
276
276
  }
277
277
  this.displayPrompt();
@@ -285,10 +285,10 @@ cdxgenRepl.defineCommand("save", {
285
285
  saveToFile = "bom.json";
286
286
  }
287
287
  fs.writeFileSync(saveToFile, JSON.stringify(sbom, null, 2));
288
- console.log(`BoM saved successfully to ${saveToFile}`);
288
+ console.log(`BOM saved successfully to ${saveToFile}`);
289
289
  } else {
290
290
  console.log(
291
- "⚠ No BoM is loaded. Use .import command to import an existing BoM"
291
+ "⚠ No BOM is loaded. Use .import command to import an existing BOM"
292
292
  );
293
293
  }
294
294
  this.displayPrompt();
@@ -313,10 +313,10 @@ cdxgenRepl.defineCommand("update", {
313
313
  if (newSbom && newSbom.components.length <= sbom.components.length) {
314
314
  sbom = newSbom;
315
315
  }
316
- console.log("BoM updated successfully.");
316
+ console.log("BOM updated successfully.");
317
317
  } else {
318
318
  console.log(
319
- "⚠ No BoM is loaded. Use .import command to import an existing BoM"
319
+ "⚠ No BOM is loaded. Use .import command to import an existing BOM"
320
320
  );
321
321
  }
322
322
  this.displayPrompt();
@@ -333,7 +333,7 @@ cdxgenRepl.defineCommand("occurrences", {
333
333
  let components = await expression.evaluate(sbom);
334
334
  if (!components) {
335
335
  console.log(
336
- "No results found. Use evinse command to generate an BoM with evidence."
336
+ "No results found. Use evinse command to generate an BOM with evidence."
337
337
  );
338
338
  } else {
339
339
  if (!Array.isArray(components)) {
@@ -346,7 +346,7 @@ cdxgenRepl.defineCommand("occurrences", {
346
346
  }
347
347
  } else {
348
348
  console.log(
349
- "⚠ No BoM is loaded. Use .import command to import an evinse BoM"
349
+ "⚠ No BOM is loaded. Use .import command to import an evinse BOM"
350
350
  );
351
351
  }
352
352
  this.displayPrompt();
@@ -437,7 +437,7 @@ cdxgenRepl.defineCommand("osinfocategories", {
437
437
  let catgories = await expression.evaluate(sbom);
438
438
  if (!catgories) {
439
439
  console.log(
440
- "Unable to retrieve the os info categories. Only OBoMs generated by cdxgen are supported by this tool."
440
+ "Unable to retrieve the os info categories. Only OBOMs generated by cdxgen are supported by this tool."
441
441
  );
442
442
  } else {
443
443
  console.log(catgories.join("\n"));
@@ -447,7 +447,7 @@ cdxgenRepl.defineCommand("osinfocategories", {
447
447
  }
448
448
  } else {
449
449
  console.log(
450
- "⚠ No OBoM is loaded. Use .import command to import an OBoM"
450
+ "⚠ No OBOM is loaded. Use .import command to import an OBOM"
451
451
  );
452
452
  }
453
453
  this.displayPrompt();
@@ -546,7 +546,7 @@ cdxgenRepl.defineCommand("osinfocategories", {
546
546
  }
547
547
  } else {
548
548
  console.log(
549
- "⚠ No OBoM is loaded. Use .import command to import an OBoM"
549
+ "⚠ No OBOM is loaded. Use .import command to import an OBOM"
550
550
  );
551
551
  }
552
552
  this.displayPrompt();
package/binary.js CHANGED
@@ -230,7 +230,7 @@ export const getGoBuildInfo = (src) => {
230
230
  let result = spawnSync(GOVERSION_BIN, [src], {
231
231
  encoding: "utf-8"
232
232
  });
233
- if (result.status !== 0 || result.error) {
233
+ if (result.status !== 0 || result.error || !result.stdout) {
234
234
  if (result.stdout || result.stderr) {
235
235
  console.error(result.stdout, result.stderr);
236
236
  }
package/docker.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import got from "got";
2
2
  import { globSync } from "glob";
3
- import { parse } from "url";
3
+ import { parse } from "node:url";
4
4
  import stream from "node:stream/promises";
5
5
  import {
6
6
  existsSync,
package/index.js CHANGED
@@ -60,6 +60,7 @@ import {
60
60
  parseGoVersionData,
61
61
  parseGosumData,
62
62
  parseGoListDep,
63
+ parseGoModGraph,
63
64
  parseGoModWhy,
64
65
  parseGoModData,
65
66
  parseGopkgData,
@@ -2309,24 +2310,36 @@ export const createPythonBom = async (path, options) => {
2309
2310
  for (const f of poetryFiles) {
2310
2311
  const basePath = dirname(f);
2311
2312
  const lockData = readFileSync(f, { encoding: "utf-8" });
2312
- const dlist = await parsePoetrylockData(lockData, f);
2313
- if (dlist && dlist.length) {
2314
- pkgList = pkgList.concat(dlist);
2315
- }
2316
- const pkgMap = getPipFrozenTree(basePath, f, tempDir);
2317
- if (pkgMap.pkgList && pkgMap.pkgList.length) {
2318
- pkgList = pkgList.concat(pkgMap.pkgList);
2313
+ let retMap = await parsePoetrylockData(lockData, f);
2314
+ if (retMap.pkgList && retMap.pkgList.length) {
2315
+ pkgList = pkgList.concat(retMap.pkgList);
2316
+ pkgList = trimComponents(pkgList, "json");
2319
2317
  }
2320
- if (pkgMap.dependenciesList) {
2318
+ if (retMap.dependenciesList && retMap.dependenciesList.length) {
2321
2319
  dependencies = mergeDependencies(
2322
2320
  dependencies,
2323
- pkgMap.dependenciesList,
2321
+ retMap.dependenciesList,
2324
2322
  parentComponent
2325
2323
  );
2326
2324
  }
2325
+ // Retrieve the tree using virtualenv in deep mode and as a fallback
2326
+ // This is a slow operation
2327
+ if (options.deep || !dependencies.length) {
2328
+ retMap = getPipFrozenTree(basePath, f, tempDir);
2329
+ if (retMap.pkgList && retMap.pkgList.length) {
2330
+ pkgList = pkgList.concat(retMap.pkgList);
2331
+ }
2332
+ if (retMap.dependenciesList) {
2333
+ dependencies = mergeDependencies(
2334
+ dependencies,
2335
+ retMap.dependenciesList,
2336
+ parentComponent
2337
+ );
2338
+ }
2339
+ }
2327
2340
  const parentDependsOn = [];
2328
2341
  // Complete the dependency tree by making parent component depend on the first level
2329
- for (const p of pkgMap.rootList) {
2342
+ for (const p of retMap.rootList) {
2330
2343
  parentDependsOn.push(`pkg:pypi/${p.name}@${p.version}`);
2331
2344
  }
2332
2345
  const pdependencies = {
@@ -2397,6 +2410,8 @@ export const createPythonBom = async (path, options) => {
2397
2410
  let frozen = false;
2398
2411
  // Attempt to pip freeze in a virtualenv to improve precision
2399
2412
  if (options.installDeps) {
2413
+ // If there are multiple requirements files then the tree is getting constructed for each one
2414
+ // adding to the delay.
2400
2415
  const pkgMap = getPipFrozenTree(basePath, f, tempDir);
2401
2416
  if (pkgMap.pkgList && pkgMap.pkgList.length) {
2402
2417
  pkgList = pkgList.concat(pkgMap.pkgList);
@@ -2454,7 +2469,7 @@ export const createPythonBom = async (path, options) => {
2454
2469
  }
2455
2470
  // Get the imported modules and a dedupe list of packages
2456
2471
  const parentDependsOn = new Set();
2457
- const retMap = await getPyModules(path, pkgList);
2472
+ const retMap = await getPyModules(path, pkgList, options);
2458
2473
  if (retMap.pkgList && retMap.pkgList.length) {
2459
2474
  pkgList = pkgList.concat(retMap.pkgList);
2460
2475
  for (const p of retMap.pkgList) {
@@ -2548,6 +2563,9 @@ export const createPythonBom = async (path, options) => {
2548
2563
  */
2549
2564
  export const createGoBom = async (path, options) => {
2550
2565
  let pkgList = [];
2566
+ let dependencies = [];
2567
+ const allImports = {};
2568
+ let parentComponent = createDefaultParentComponent(path, "golang", options);
2551
2569
  // Is this a binary file
2552
2570
  let maybeBinary = false;
2553
2571
  try {
@@ -2569,6 +2587,8 @@ export const createGoBom = async (path, options) => {
2569
2587
  }
2570
2588
  return buildBomNSData(options, pkgList, "golang", {
2571
2589
  allImports,
2590
+ dependencies,
2591
+ parentComponent,
2572
2592
  src: path,
2573
2593
  filename: path
2574
2594
  });
@@ -2598,8 +2618,70 @@ export const createGoBom = async (path, options) => {
2598
2618
  pkgList = pkgList.concat(dlist);
2599
2619
  }
2600
2620
  }
2621
+ const doneList = {};
2622
+ let circuitBreak = false;
2623
+ if (DEBUG_MODE) {
2624
+ console.log(
2625
+ `Attempting to detect required packages using "go mod why" command for ${pkgList.length} packages`
2626
+ );
2627
+ }
2628
+ // Using go mod why detect required packages
2629
+ for (const apkg of pkgList) {
2630
+ if (circuitBreak) {
2631
+ break;
2632
+ }
2633
+ const pkgFullName = `${apkg.name}`;
2634
+ if (apkg.scope === "required") {
2635
+ allImports[pkgFullName] = true;
2636
+ continue;
2637
+ }
2638
+ if (
2639
+ apkg.scope === "optional" ||
2640
+ allImports[pkgFullName] ||
2641
+ doneList[pkgFullName]
2642
+ ) {
2643
+ continue;
2644
+ }
2645
+ if (DEBUG_MODE) {
2646
+ console.log(`go mod why -m -vendor ${pkgFullName}`);
2647
+ }
2648
+ const mresult = spawnSync(
2649
+ "go",
2650
+ ["mod", "why", "-m", "-vendor", pkgFullName],
2651
+ { cwd: path, encoding: "utf-8", timeout: TIMEOUT_MS }
2652
+ );
2653
+ if (mresult.status !== 0 || mresult.error) {
2654
+ if (DEBUG_MODE) {
2655
+ if (mresult.stdout) {
2656
+ console.log(mresult.stdout);
2657
+ }
2658
+ if (mresult.stderr) {
2659
+ console.log(mresult.stderr);
2660
+ }
2661
+ }
2662
+ circuitBreak = true;
2663
+ } else {
2664
+ const mstdout = mresult.stdout;
2665
+ if (mstdout) {
2666
+ const cmdOutput = Buffer.from(mstdout).toString();
2667
+ const whyPkg = parseGoModWhy(cmdOutput);
2668
+ // whyPkg would include this package string
2669
+ // github.com/golang/protobuf/proto github.com/golang/protobuf
2670
+ // golang.org/x/tools/cmd/goimports golang.org/x/tools
2671
+ if (whyPkg && whyPkg.includes(pkgFullName)) {
2672
+ allImports[pkgFullName] = true;
2673
+ }
2674
+ doneList[pkgFullName] = true;
2675
+ }
2676
+ }
2677
+ }
2678
+ if (DEBUG_MODE) {
2679
+ console.log(`Required packages: ${Object.keys(allImports).length}`);
2680
+ }
2601
2681
  return buildBomNSData(options, pkgList, "golang", {
2602
2682
  src: path,
2683
+ dependencies,
2684
+ parentComponent,
2603
2685
  filename: gosumFiles.join(", ")
2604
2686
  });
2605
2687
  }
@@ -2615,7 +2697,7 @@ export const createGoBom = async (path, options) => {
2615
2697
  const dlist = await parseGosumData(gosumData);
2616
2698
  if (dlist && dlist.length) {
2617
2699
  dlist.forEach((pkg) => {
2618
- gosumMap[`${pkg.group}/${pkg.name}/${pkg.version}`] = pkg._integrity;
2700
+ gosumMap[`${pkg.group}/${pkg.name}@${pkg.version}`] = pkg._integrity;
2619
2701
  });
2620
2702
  }
2621
2703
  }
@@ -2634,7 +2716,7 @@ export const createGoBom = async (path, options) => {
2634
2716
  );
2635
2717
  if (gomodFiles.length) {
2636
2718
  let shouldManuallyParse = false;
2637
- // Use the go list -deps and go mod why commands to generate a good quality BoM for non-docker invocations
2719
+ // Use the go list -deps and go mod why commands to generate a good quality BOM for non-docker invocations
2638
2720
  if (!["docker", "oci", "os"].includes(options.projectType)) {
2639
2721
  for (const f of gomodFiles) {
2640
2722
  const basePath = dirname(f);
@@ -2642,36 +2724,85 @@ export const createGoBom = async (path, options) => {
2642
2724
  if (basePath.includes("/vendor/") || basePath.includes("/build/")) {
2643
2725
  continue;
2644
2726
  }
2727
+ // First we execute the go list -deps command which gives the correct list of dependencies
2645
2728
  if (DEBUG_MODE) {
2646
2729
  console.log("Executing go list -deps in", basePath);
2647
2730
  }
2648
- const result = spawnSync(
2731
+ let result = spawnSync(
2649
2732
  "go",
2650
2733
  [
2651
2734
  "list",
2652
2735
  "-deps",
2653
2736
  "-f",
2654
- "'{{with .Module}}{{.Path}} {{.Version}} {{.Indirect}} {{.GoMod}} {{.GoVersion}}{{end}}'",
2737
+ "'{{with .Module}}{{.Path}} {{.Version}} {{.Indirect}} {{.GoMod}} {{.GoVersion}} {{.Main}}{{end}}'",
2655
2738
  "./..."
2656
2739
  ],
2657
2740
  { cwd: basePath, encoding: "utf-8", timeout: TIMEOUT_MS }
2658
2741
  );
2742
+ if (DEBUG_MODE) {
2743
+ console.log("Executing go mod graph in", basePath);
2744
+ }
2659
2745
  if (result.status !== 0 || result.error) {
2660
2746
  shouldManuallyParse = true;
2661
- if (result.stdout) {
2747
+ if (DEBUG_MODE && result.stdout) {
2662
2748
  console.log(result.stdout);
2663
2749
  }
2664
- if (result.stderr) {
2750
+ if (DEBUG_MODE && result.stderr) {
2665
2751
  console.log(result.stderr);
2666
2752
  }
2667
2753
  options.failOnError && process.exit(1);
2668
2754
  }
2669
2755
  const stdout = result.stdout;
2670
2756
  if (stdout) {
2671
- const cmdOutput = Buffer.from(stdout).toString();
2672
- const dlist = await parseGoListDep(cmdOutput, gosumMap);
2673
- if (dlist && dlist.length) {
2674
- pkgList = pkgList.concat(dlist);
2757
+ let cmdOutput = Buffer.from(stdout).toString();
2758
+ const retMap = await parseGoListDep(cmdOutput, gosumMap);
2759
+ if (retMap.pkgList && retMap.pkgList.length) {
2760
+ pkgList = pkgList.concat(retMap.pkgList);
2761
+ }
2762
+ // We treat the main module as our parent
2763
+ if (
2764
+ retMap.parentComponent &&
2765
+ Object.keys(retMap.parentComponent).length
2766
+ ) {
2767
+ parentComponent = retMap.parentComponent;
2768
+ parentComponent.type = "application";
2769
+ }
2770
+ // Next we use the go mod graph command to construct the dependency tree
2771
+ result = spawnSync("go", ["mod", "graph"], {
2772
+ cwd: basePath,
2773
+ encoding: "utf-8",
2774
+ timeout: TIMEOUT_MS
2775
+ });
2776
+ // Check if got a mod graph successfully
2777
+ if (result.status !== 0 || result.error) {
2778
+ if (DEBUG_MODE && result.stdout) {
2779
+ console.log(result.stdout);
2780
+ }
2781
+ if (DEBUG_MODE && result.stderr) {
2782
+ console.log(result.stderr);
2783
+ }
2784
+ options.failOnError && process.exit(1);
2785
+ }
2786
+ if (result.stdout) {
2787
+ cmdOutput = Buffer.from(result.stdout).toString();
2788
+ const retMap = await parseGoModGraph(
2789
+ cmdOutput,
2790
+ f,
2791
+ gosumMap,
2792
+ pkgList,
2793
+ parentComponent
2794
+ );
2795
+ if (retMap.pkgList && retMap.pkgList.length) {
2796
+ pkgList = pkgList.concat(retMap.pkgList);
2797
+ pkgList = trimComponents(pkgList, "json");
2798
+ }
2799
+ if (retMap.dependenciesList && retMap.dependenciesList.length) {
2800
+ dependencies = mergeDependencies(
2801
+ dependencies,
2802
+ retMap.dependenciesList,
2803
+ parentComponent
2804
+ );
2805
+ }
2675
2806
  }
2676
2807
  } else {
2677
2808
  shouldManuallyParse = true;
@@ -2681,67 +2812,20 @@ export const createGoBom = async (path, options) => {
2681
2812
  options.failOnError && process.exit(1);
2682
2813
  }
2683
2814
  }
2684
- const allImports = {};
2685
- let circuitBreak = false;
2686
- if (DEBUG_MODE) {
2687
- console.log(
2688
- `Attempting to detect required packages using "go mod why" command for ${pkgList.length} packages`
2689
- );
2690
- }
2691
- // Using go mod why detect required packages
2692
- for (const apkg of pkgList) {
2693
- if (circuitBreak) {
2694
- break;
2695
- }
2696
- const pkgFullName = `${apkg.name}`;
2697
- if (apkg.scope === "required") {
2698
- allImports[pkgFullName] = true;
2699
- continue;
2700
- }
2701
- if (DEBUG_MODE) {
2702
- console.log(`go mod why -m -vendor ${pkgFullName}`);
2703
- }
2704
- const mresult = spawnSync(
2705
- "go",
2706
- ["mod", "why", "-m", "-vendor", pkgFullName],
2707
- { cwd: path, encoding: "utf-8", timeout: TIMEOUT_MS }
2708
- );
2709
- if (mresult.status !== 0 || mresult.error) {
2710
- if (DEBUG_MODE) {
2711
- if (mresult.stdout) {
2712
- console.log(mresult.stdout);
2713
- }
2714
- if (mresult.stderr) {
2715
- console.log(mresult.stderr);
2716
- }
2717
- }
2718
- circuitBreak = true;
2719
- } else {
2720
- const mstdout = mresult.stdout;
2721
- if (mstdout) {
2722
- const cmdOutput = Buffer.from(mstdout).toString();
2723
- const whyPkg = parseGoModWhy(cmdOutput);
2724
- if (whyPkg == pkgFullName) {
2725
- allImports[pkgFullName] = true;
2726
- }
2727
- }
2728
- }
2729
- }
2730
- if (DEBUG_MODE) {
2731
- console.log(`Required packages: ${Object.keys(allImports).length}`);
2732
- }
2733
2815
  if (pkgList.length && !shouldManuallyParse) {
2734
2816
  return buildBomNSData(options, pkgList, "golang", {
2735
2817
  allImports,
2818
+ dependencies,
2819
+ parentComponent,
2736
2820
  src: path,
2737
2821
  filename: gomodFiles.join(", ")
2738
2822
  });
2739
2823
  }
2740
2824
  }
2741
- // Parse the gomod files manually. The resultant BoM would be incomplete
2825
+ // Parse the gomod files manually. The resultant BOM would be incomplete
2742
2826
  if (!["docker", "oci", "os"].includes(options.projectType)) {
2743
2827
  console.log(
2744
- "Manually parsing go.mod files. The resultant BoM would be incomplete."
2828
+ "Manually parsing go.mod files. The resultant BOM would be incomplete."
2745
2829
  );
2746
2830
  }
2747
2831
  for (const f of gomodFiles) {
@@ -2756,6 +2840,8 @@ export const createGoBom = async (path, options) => {
2756
2840
  }
2757
2841
  return buildBomNSData(options, pkgList, "golang", {
2758
2842
  src: path,
2843
+ dependencies,
2844
+ parentComponent,
2759
2845
  filename: gomodFiles.join(", ")
2760
2846
  });
2761
2847
  } else if (gopkgLockFiles.length) {
@@ -2773,6 +2859,8 @@ export const createGoBom = async (path, options) => {
2773
2859
  }
2774
2860
  return buildBomNSData(options, pkgList, "golang", {
2775
2861
  src: path,
2862
+ dependencies,
2863
+ parentComponent,
2776
2864
  filename: gopkgLockFiles.join(", ")
2777
2865
  });
2778
2866
  }
@@ -3285,7 +3373,7 @@ export const createCloudBuildBom = (path, options) => {
3285
3373
  */
3286
3374
  export const createOSBom = (path, options) => {
3287
3375
  console.warn(
3288
- "About to generate OBoM for the current OS installation. This will take several minutes ..."
3376
+ "About to generate OBOM for the current OS installation. This will take several minutes ..."
3289
3377
  );
3290
3378
  let pkgList = [];
3291
3379
  let bomData = {};
@@ -4165,7 +4253,7 @@ export const dedupeBom = (
4165
4253
  componentsXmls = trimComponents(componentsXmls, "xml");
4166
4254
  if (DEBUG_MODE) {
4167
4255
  console.log(
4168
- `BoM includes ${components.length} components and ${dependencies.length} dependencies after dedupe`
4256
+ `BOM includes ${components.length} components and ${dependencies.length} dependencies after dedupe`
4169
4257
  );
4170
4258
  }
4171
4259
  const serialNum = "urn:uuid:" + uuidv4();