@cyclonedx/cdxgen 9.8.6 → 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/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,
@@ -92,6 +93,7 @@ import {
92
93
  parseCsPkgLockData,
93
94
  parseCsPkgData,
94
95
  parseCsProjData,
96
+ parsePaketLockData,
95
97
  DEBUG_MODE,
96
98
  parsePyProjectToml,
97
99
  addEvidenceForImports,
@@ -2308,24 +2310,36 @@ export const createPythonBom = async (path, options) => {
2308
2310
  for (const f of poetryFiles) {
2309
2311
  const basePath = dirname(f);
2310
2312
  const lockData = readFileSync(f, { encoding: "utf-8" });
2311
- const dlist = await parsePoetrylockData(lockData, f);
2312
- if (dlist && dlist.length) {
2313
- pkgList = pkgList.concat(dlist);
2314
- }
2315
- const pkgMap = getPipFrozenTree(basePath, f, tempDir);
2316
- if (pkgMap.pkgList && pkgMap.pkgList.length) {
2317
- 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");
2318
2317
  }
2319
- if (pkgMap.dependenciesList) {
2318
+ if (retMap.dependenciesList && retMap.dependenciesList.length) {
2320
2319
  dependencies = mergeDependencies(
2321
2320
  dependencies,
2322
- pkgMap.dependenciesList,
2321
+ retMap.dependenciesList,
2323
2322
  parentComponent
2324
2323
  );
2325
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
+ }
2326
2340
  const parentDependsOn = [];
2327
2341
  // Complete the dependency tree by making parent component depend on the first level
2328
- for (const p of pkgMap.rootList) {
2342
+ for (const p of retMap.rootList) {
2329
2343
  parentDependsOn.push(`pkg:pypi/${p.name}@${p.version}`);
2330
2344
  }
2331
2345
  const pdependencies = {
@@ -2396,6 +2410,8 @@ export const createPythonBom = async (path, options) => {
2396
2410
  let frozen = false;
2397
2411
  // Attempt to pip freeze in a virtualenv to improve precision
2398
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.
2399
2415
  const pkgMap = getPipFrozenTree(basePath, f, tempDir);
2400
2416
  if (pkgMap.pkgList && pkgMap.pkgList.length) {
2401
2417
  pkgList = pkgList.concat(pkgMap.pkgList);
@@ -2453,7 +2469,7 @@ export const createPythonBom = async (path, options) => {
2453
2469
  }
2454
2470
  // Get the imported modules and a dedupe list of packages
2455
2471
  const parentDependsOn = new Set();
2456
- const retMap = await getPyModules(path, pkgList);
2472
+ const retMap = await getPyModules(path, pkgList, options);
2457
2473
  if (retMap.pkgList && retMap.pkgList.length) {
2458
2474
  pkgList = pkgList.concat(retMap.pkgList);
2459
2475
  for (const p of retMap.pkgList) {
@@ -2547,6 +2563,9 @@ export const createPythonBom = async (path, options) => {
2547
2563
  */
2548
2564
  export const createGoBom = async (path, options) => {
2549
2565
  let pkgList = [];
2566
+ let dependencies = [];
2567
+ const allImports = {};
2568
+ let parentComponent = createDefaultParentComponent(path, "golang", options);
2550
2569
  // Is this a binary file
2551
2570
  let maybeBinary = false;
2552
2571
  try {
@@ -2568,6 +2587,8 @@ export const createGoBom = async (path, options) => {
2568
2587
  }
2569
2588
  return buildBomNSData(options, pkgList, "golang", {
2570
2589
  allImports,
2590
+ dependencies,
2591
+ parentComponent,
2571
2592
  src: path,
2572
2593
  filename: path
2573
2594
  });
@@ -2597,8 +2618,70 @@ export const createGoBom = async (path, options) => {
2597
2618
  pkgList = pkgList.concat(dlist);
2598
2619
  }
2599
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
+ }
2600
2681
  return buildBomNSData(options, pkgList, "golang", {
2601
2682
  src: path,
2683
+ dependencies,
2684
+ parentComponent,
2602
2685
  filename: gosumFiles.join(", ")
2603
2686
  });
2604
2687
  }
@@ -2614,7 +2697,7 @@ export const createGoBom = async (path, options) => {
2614
2697
  const dlist = await parseGosumData(gosumData);
2615
2698
  if (dlist && dlist.length) {
2616
2699
  dlist.forEach((pkg) => {
2617
- gosumMap[`${pkg.group}/${pkg.name}/${pkg.version}`] = pkg._integrity;
2700
+ gosumMap[`${pkg.group}/${pkg.name}@${pkg.version}`] = pkg._integrity;
2618
2701
  });
2619
2702
  }
2620
2703
  }
@@ -2633,7 +2716,7 @@ export const createGoBom = async (path, options) => {
2633
2716
  );
2634
2717
  if (gomodFiles.length) {
2635
2718
  let shouldManuallyParse = false;
2636
- // 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
2637
2720
  if (!["docker", "oci", "os"].includes(options.projectType)) {
2638
2721
  for (const f of gomodFiles) {
2639
2722
  const basePath = dirname(f);
@@ -2641,36 +2724,85 @@ export const createGoBom = async (path, options) => {
2641
2724
  if (basePath.includes("/vendor/") || basePath.includes("/build/")) {
2642
2725
  continue;
2643
2726
  }
2727
+ // First we execute the go list -deps command which gives the correct list of dependencies
2644
2728
  if (DEBUG_MODE) {
2645
2729
  console.log("Executing go list -deps in", basePath);
2646
2730
  }
2647
- const result = spawnSync(
2731
+ let result = spawnSync(
2648
2732
  "go",
2649
2733
  [
2650
2734
  "list",
2651
2735
  "-deps",
2652
2736
  "-f",
2653
- "'{{with .Module}}{{.Path}} {{.Version}} {{.Indirect}} {{.GoMod}} {{.GoVersion}}{{end}}'",
2737
+ "'{{with .Module}}{{.Path}} {{.Version}} {{.Indirect}} {{.GoMod}} {{.GoVersion}} {{.Main}}{{end}}'",
2654
2738
  "./..."
2655
2739
  ],
2656
2740
  { cwd: basePath, encoding: "utf-8", timeout: TIMEOUT_MS }
2657
2741
  );
2742
+ if (DEBUG_MODE) {
2743
+ console.log("Executing go mod graph in", basePath);
2744
+ }
2658
2745
  if (result.status !== 0 || result.error) {
2659
2746
  shouldManuallyParse = true;
2660
- if (result.stdout) {
2747
+ if (DEBUG_MODE && result.stdout) {
2661
2748
  console.log(result.stdout);
2662
2749
  }
2663
- if (result.stderr) {
2750
+ if (DEBUG_MODE && result.stderr) {
2664
2751
  console.log(result.stderr);
2665
2752
  }
2666
2753
  options.failOnError && process.exit(1);
2667
2754
  }
2668
2755
  const stdout = result.stdout;
2669
2756
  if (stdout) {
2670
- const cmdOutput = Buffer.from(stdout).toString();
2671
- const dlist = await parseGoListDep(cmdOutput, gosumMap);
2672
- if (dlist && dlist.length) {
2673
- 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
+ }
2674
2806
  }
2675
2807
  } else {
2676
2808
  shouldManuallyParse = true;
@@ -2680,67 +2812,20 @@ export const createGoBom = async (path, options) => {
2680
2812
  options.failOnError && process.exit(1);
2681
2813
  }
2682
2814
  }
2683
- const allImports = {};
2684
- let circuitBreak = false;
2685
- if (DEBUG_MODE) {
2686
- console.log(
2687
- `Attempting to detect required packages using "go mod why" command for ${pkgList.length} packages`
2688
- );
2689
- }
2690
- // Using go mod why detect required packages
2691
- for (const apkg of pkgList) {
2692
- if (circuitBreak) {
2693
- break;
2694
- }
2695
- const pkgFullName = `${apkg.name}`;
2696
- if (apkg.scope === "required") {
2697
- allImports[pkgFullName] = true;
2698
- continue;
2699
- }
2700
- if (DEBUG_MODE) {
2701
- console.log(`go mod why -m -vendor ${pkgFullName}`);
2702
- }
2703
- const mresult = spawnSync(
2704
- "go",
2705
- ["mod", "why", "-m", "-vendor", pkgFullName],
2706
- { cwd: path, encoding: "utf-8", timeout: TIMEOUT_MS }
2707
- );
2708
- if (mresult.status !== 0 || mresult.error) {
2709
- if (DEBUG_MODE) {
2710
- if (mresult.stdout) {
2711
- console.log(mresult.stdout);
2712
- }
2713
- if (mresult.stderr) {
2714
- console.log(mresult.stderr);
2715
- }
2716
- }
2717
- circuitBreak = true;
2718
- } else {
2719
- const mstdout = mresult.stdout;
2720
- if (mstdout) {
2721
- const cmdOutput = Buffer.from(mstdout).toString();
2722
- const whyPkg = parseGoModWhy(cmdOutput);
2723
- if (whyPkg == pkgFullName) {
2724
- allImports[pkgFullName] = true;
2725
- }
2726
- }
2727
- }
2728
- }
2729
- if (DEBUG_MODE) {
2730
- console.log(`Required packages: ${Object.keys(allImports).length}`);
2731
- }
2732
2815
  if (pkgList.length && !shouldManuallyParse) {
2733
2816
  return buildBomNSData(options, pkgList, "golang", {
2734
2817
  allImports,
2818
+ dependencies,
2819
+ parentComponent,
2735
2820
  src: path,
2736
2821
  filename: gomodFiles.join(", ")
2737
2822
  });
2738
2823
  }
2739
2824
  }
2740
- // Parse the gomod files manually. The resultant BoM would be incomplete
2825
+ // Parse the gomod files manually. The resultant BOM would be incomplete
2741
2826
  if (!["docker", "oci", "os"].includes(options.projectType)) {
2742
2827
  console.log(
2743
- "Manually parsing go.mod files. The resultant BoM would be incomplete."
2828
+ "Manually parsing go.mod files. The resultant BOM would be incomplete."
2744
2829
  );
2745
2830
  }
2746
2831
  for (const f of gomodFiles) {
@@ -2755,6 +2840,8 @@ export const createGoBom = async (path, options) => {
2755
2840
  }
2756
2841
  return buildBomNSData(options, pkgList, "golang", {
2757
2842
  src: path,
2843
+ dependencies,
2844
+ parentComponent,
2758
2845
  filename: gomodFiles.join(", ")
2759
2846
  });
2760
2847
  } else if (gopkgLockFiles.length) {
@@ -2772,6 +2859,8 @@ export const createGoBom = async (path, options) => {
2772
2859
  }
2773
2860
  return buildBomNSData(options, pkgList, "golang", {
2774
2861
  src: path,
2862
+ dependencies,
2863
+ parentComponent,
2775
2864
  filename: gopkgLockFiles.join(", ")
2776
2865
  });
2777
2866
  }
@@ -3284,7 +3373,7 @@ export const createCloudBuildBom = (path, options) => {
3284
3373
  */
3285
3374
  export const createOSBom = (path, options) => {
3286
3375
  console.warn(
3287
- "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 ..."
3288
3377
  );
3289
3378
  let pkgList = [];
3290
3379
  let bomData = {};
@@ -3963,6 +4052,10 @@ export const createCsharpBom = async (
3963
4052
  path,
3964
4053
  (options.multiProject ? "**/" : "") + "packages.lock.json"
3965
4054
  );
4055
+ const paketLockFiles = getAllFiles(
4056
+ path,
4057
+ (options.multiProject ? "**/" : "") + "paket.lock"
4058
+ );
3966
4059
  const nupkgFiles = getAllFiles(
3967
4060
  path,
3968
4061
  (options.multiProject ? "**/" : "") + "*.nupkg"
@@ -4046,6 +4139,20 @@ export const createCsharpBom = async (
4046
4139
  }
4047
4140
  }
4048
4141
  }
4142
+ if (paketLockFiles.length) {
4143
+ manifestFiles = manifestFiles.concat(paketLockFiles);
4144
+ // paket.lock parsing
4145
+ for (const f of paketLockFiles) {
4146
+ if (DEBUG_MODE) {
4147
+ console.log(`Parsing ${f}`);
4148
+ }
4149
+ pkgData = readFileSync(f, { encoding: "utf-8" });
4150
+ const dlist = await parsePaketLockData(pkgData);
4151
+ if (dlist && dlist.length) {
4152
+ pkgList = pkgList.concat(dlist);
4153
+ }
4154
+ }
4155
+ }
4049
4156
  if (!parentComponent) {
4050
4157
  parentComponent = createDefaultParentComponent(path, options.type, options);
4051
4158
  }
@@ -4110,13 +4217,16 @@ export const trimComponents = (components, format) => {
4110
4217
  const filteredComponents = [];
4111
4218
  for (const comp of components) {
4112
4219
  if (format === "xml" && comp.component) {
4113
- const key = comp.component.purl || comp.component["bom-ref"];
4220
+ const key =
4221
+ comp.component.purl ||
4222
+ comp.component["bom-ref"] ||
4223
+ comp.name + comp.version;
4114
4224
  if (!keyCache[key]) {
4115
4225
  keyCache[key] = true;
4116
4226
  filteredComponents.push(comp);
4117
4227
  }
4118
4228
  } else {
4119
- const key = comp.purl || comp["bom-ref"];
4229
+ const key = comp.purl || comp["bom-ref"] || comp.name + comp.version;
4120
4230
  if (!keyCache[key]) {
4121
4231
  keyCache[key] = true;
4122
4232
  filteredComponents.push(comp);
@@ -4143,7 +4253,7 @@ export const dedupeBom = (
4143
4253
  componentsXmls = trimComponents(componentsXmls, "xml");
4144
4254
  if (DEBUG_MODE) {
4145
4255
  console.log(
4146
- `BoM includes ${components.length} components and ${dependencies.length} dependencies after dedupe`
4256
+ `BOM includes ${components.length} components and ${dependencies.length} dependencies after dedupe`
4147
4257
  );
4148
4258
  }
4149
4259
  const serialNum = "urn:uuid:" + uuidv4();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "9.8.6",
3
+ "version": "9.8.8",
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>",
@@ -31,11 +31,11 @@
31
31
  "type": "module",
32
32
  "exports": "./index.js",
33
33
  "bin": {
34
- "cdxgen": "./bin/cdxgen.js",
35
- "obom": "./bin/cdxgen.js",
36
- "cdxi": "./bin/repl.js",
37
- "evinse": "./bin/evinse.js",
38
- "cdx-verify": "./bin/verify.js"
34
+ "cdxgen": "bin/cdxgen.js",
35
+ "obom": "bin/cdxgen.js",
36
+ "cdxi": "bin/repl.js",
37
+ "evinse": "bin/evinse.js",
38
+ "cdx-verify": "bin/verify.js"
39
39
  },
40
40
  "scripts": {
41
41
  "docs": "docsify serve docs",
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "repository": {
51
51
  "type": "git",
52
- "url": "git+https://github.com/CycloneDX/cdxgen"
52
+ "url": "git+https://github.com/CycloneDX/cdxgen.git"
53
53
  },
54
54
  "bugs": {
55
55
  "url": "https://github.com/cyclonedx/cdxgen/issues"
@@ -82,7 +82,7 @@
82
82
  "yargs": "^17.7.2"
83
83
  },
84
84
  "optionalDependencies": {
85
- "@appthreat/atom": "^1.2.3",
85
+ "@appthreat/atom": "1.2.5",
86
86
  "@cyclonedx/cdxgen-plugins-bin": "^1.4.0",
87
87
  "@cyclonedx/cdxgen-plugins-bin-arm64": "^1.4.0",
88
88
  "@cyclonedx/cdxgen-plugins-bin-ppc64": "^1.4.0",
@@ -107,4 +107,4 @@
107
107
  "jest": "^29.7.0",
108
108
  "prettier": "3.0.3"
109
109
  }
110
- }
110
+ }
package/server.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import connect from "connect";
2
2
  import http from "node:http";
3
3
  import bodyParser from "body-parser";
4
- import url from "node:url";
4
+ import url, { URL } from "node:url";
5
5
  import { spawnSync } from "node:child_process";
6
6
  import os from "node:os";
7
7
  import fs from "node:fs";
@@ -24,10 +24,14 @@ app.use(
24
24
  app.use(compression());
25
25
 
26
26
  const gitClone = (repoUrl) => {
27
+ const parsedUrl = new URL(repoUrl);
28
+
29
+ const sanitizedRepoUrl = `${parsedUrl.protocol}//${parsedUrl.host}${parsedUrl.pathname}`;
30
+
27
31
  const tempDir = fs.mkdtempSync(
28
- path.join(os.tmpdir(), path.basename(repoUrl))
32
+ path.join(os.tmpdir(), path.basename(parsedUrl.pathname))
29
33
  );
30
- console.log("Cloning", repoUrl, "to", tempDir);
34
+ console.log("Cloning", sanitizedRepoUrl, "to", tempDir);
31
35
  const result = spawnSync("git", ["clone", repoUrl, "--depth", "1", tempDir], {
32
36
  encoding: "utf-8",
33
37
  shell: false