@cyclonedx/cdxgen 9.2.2 → 9.3.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/index.js CHANGED
@@ -90,7 +90,8 @@ import {
90
90
  parseCsPkgLockData,
91
91
  parseCsPkgData,
92
92
  parseCsProjData,
93
- DEBUG_MODE
93
+ DEBUG_MODE,
94
+ parsePyProjectToml
94
95
  } from "./utils.js";
95
96
  import { spawnSync } from "node:child_process";
96
97
  import { fileURLToPath } from "node:url";
@@ -180,6 +181,7 @@ const createDefaultParentComponent = (path, type = "application") => {
180
181
  const parentComponent = {
181
182
  group: "",
182
183
  name: dirName,
184
+ version: "latest",
183
185
  type: "application"
184
186
  };
185
187
  const ppurl = new PackageURL(
@@ -1148,7 +1150,7 @@ export const createJavaBom = async (path, options) => {
1148
1150
  );
1149
1151
  } else {
1150
1152
  console.log(
1151
- "1. Java version requirement: cdxgen container image bundles Java 19 with maven 3.9 which might be incompatible."
1153
+ "1. Java version requirement: cdxgen container image bundles Java 20 with maven 3.9 which might be incompatible."
1152
1154
  );
1153
1155
  }
1154
1156
  console.log(
@@ -1775,6 +1777,16 @@ export const createNodejsBom = async (path, options) => {
1775
1777
  if (pcs.length) {
1776
1778
  parentComponent = pcs[0];
1777
1779
  parentComponent.type = "application";
1780
+ ppurl = new PackageURL(
1781
+ "npm",
1782
+ parentComponent.group,
1783
+ parentComponent.name,
1784
+ parentComponent.version,
1785
+ null,
1786
+ null
1787
+ ).toString();
1788
+ parentComponent["bom-ref"] = decodeURIComponent(ppurl);
1789
+ parentComponent["purl"] = ppurl;
1778
1790
  }
1779
1791
  } else {
1780
1792
  let dirName = dirname(f);
@@ -1793,7 +1805,7 @@ export const createNodejsBom = async (path, options) => {
1793
1805
  null,
1794
1806
  null
1795
1807
  ).toString();
1796
- parentComponent["bom-ref"] = ppurl;
1808
+ parentComponent["bom-ref"] = decodeURIComponent(ppurl);
1797
1809
  parentComponent["purl"] = ppurl;
1798
1810
  }
1799
1811
  // Parse the pnpm file
@@ -1906,6 +1918,16 @@ export const createNodejsBom = async (path, options) => {
1906
1918
  if (pcs.length) {
1907
1919
  const tmpParentComponent = pcs[0];
1908
1920
  tmpParentComponent.type = "application";
1921
+ ppurl = new PackageURL(
1922
+ "npm",
1923
+ tmpParentComponent.group,
1924
+ tmpParentComponent.name,
1925
+ tmpParentComponent.version,
1926
+ null,
1927
+ null
1928
+ ).toString();
1929
+ tmpParentComponent["bom-ref"] = decodeURIComponent(ppurl);
1930
+ tmpParentComponent["purl"] = ppurl;
1909
1931
  if (!Object.keys(parentComponent).length) {
1910
1932
  parentComponent = tmpParentComponent;
1911
1933
  } else {
@@ -2019,12 +2041,19 @@ export const createPythonBom = async (path, options) => {
2019
2041
  let dependencies = [];
2020
2042
  let pkgList = [];
2021
2043
  const tempDir = mkdtempSync(join(tmpdir(), "cdxgen-venv-"));
2022
- const parentComponent = createDefaultParentComponent(path, "pypi");
2044
+ let parentComponent = createDefaultParentComponent(path, "pypi");
2023
2045
  const pipenvMode = existsSync(join(path, "Pipfile"));
2024
- const poetryFiles = getAllFiles(
2046
+ let poetryFiles = getAllFiles(
2025
2047
  path,
2026
2048
  (options.multiProject ? "**/" : "") + "poetry.lock"
2027
2049
  );
2050
+ const pdmLockFiles = getAllFiles(
2051
+ path,
2052
+ (options.multiProject ? "**/" : "") + "pdm.lock"
2053
+ );
2054
+ if (pdmLockFiles && pdmLockFiles.length) {
2055
+ poetryFiles = poetryFiles.concat(pdmLockFiles);
2056
+ }
2028
2057
  const reqFiles = getAllFiles(
2029
2058
  path,
2030
2059
  (options.multiProject ? "**/" : "") + "*requirements*.txt"
@@ -2048,6 +2077,25 @@ export const createPythonBom = async (path, options) => {
2048
2077
  const setupPy = join(path, "setup.py");
2049
2078
  const pyProjectFile = join(path, "pyproject.toml");
2050
2079
  const pyProjectMode = existsSync(pyProjectFile);
2080
+ if (pyProjectMode) {
2081
+ const tmpParentComponent = parsePyProjectToml(pyProjectFile);
2082
+ if (tmpParentComponent && tmpParentComponent.name) {
2083
+ parentComponent = tmpParentComponent;
2084
+ delete parentComponent.homepage;
2085
+ delete parentComponent.repository;
2086
+ parentComponent.type = "application";
2087
+ const ppurl = new PackageURL(
2088
+ "pypi",
2089
+ parentComponent.group || "",
2090
+ parentComponent.name,
2091
+ parentComponent.version || "latest",
2092
+ null,
2093
+ null
2094
+ ).toString();
2095
+ parentComponent["bom-ref"] = decodeURIComponent(ppurl);
2096
+ parentComponent["purl"] = ppurl;
2097
+ }
2098
+ }
2051
2099
  const requirementsMode =
2052
2100
  (reqFiles && reqFiles.length) || (reqDirFiles && reqDirFiles.length);
2053
2101
  const poetryMode = poetryFiles && poetryFiles.length;
@@ -2056,15 +2104,39 @@ export const createPythonBom = async (path, options) => {
2056
2104
  // we give preference to poetry lock file. Issue# 129
2057
2105
  if (poetryMode) {
2058
2106
  for (const f of poetryFiles) {
2107
+ const basePath = dirname(f);
2059
2108
  const lockData = readFileSync(f, { encoding: "utf-8" });
2060
- const dlist = await parsePoetrylockData(lockData);
2109
+ const dlist = await parsePoetrylockData(lockData, f);
2061
2110
  if (dlist && dlist.length) {
2062
2111
  pkgList = pkgList.concat(dlist);
2063
2112
  }
2113
+ const pkgMap = getPipFrozenTree(basePath, f, tempDir);
2114
+ if (pkgMap.pkgList && pkgMap.pkgList.length) {
2115
+ pkgList = pkgList.concat(pkgMap.pkgList);
2116
+ }
2117
+ if (pkgMap.dependenciesList) {
2118
+ dependencies = mergeDependencies(
2119
+ dependencies,
2120
+ pkgMap.dependenciesList,
2121
+ parentComponent
2122
+ );
2123
+ }
2124
+ const parentDependsOn = [];
2125
+ // Complete the dependency tree by making parent component depend on the first level
2126
+ for (const p of pkgMap.rootList) {
2127
+ parentDependsOn.push(`pkg:pypi/${p.name}@${p.version}`);
2128
+ }
2129
+ const pdependencies = {
2130
+ ref: parentComponent["bom-ref"],
2131
+ dependsOn: parentDependsOn
2132
+ };
2133
+ dependencies.splice(0, 0, pdependencies);
2064
2134
  }
2065
2135
  return buildBomNSData(options, pkgList, "pypi", {
2066
2136
  src: path,
2067
- filename: poetryFiles.join(", ")
2137
+ filename: poetryFiles.join(", "),
2138
+ dependencies,
2139
+ parentComponent
2068
2140
  });
2069
2141
  } else if (metadataFiles && metadataFiles.length) {
2070
2142
  // dist-info directories
@@ -2130,7 +2202,8 @@ export const createPythonBom = async (path, options) => {
2130
2202
  if (pkgMap.dependenciesList) {
2131
2203
  dependencies = mergeDependencies(
2132
2204
  dependencies,
2133
- pkgMap.dependenciesList
2205
+ pkgMap.dependenciesList,
2206
+ parentComponent
2134
2207
  );
2135
2208
  }
2136
2209
  }
@@ -2188,24 +2261,41 @@ export const createPythonBom = async (path, options) => {
2188
2261
  }
2189
2262
  }
2190
2263
  if (retMap.dependenciesList) {
2191
- dependencies = mergeDependencies(dependencies, retMap.dependenciesList);
2264
+ dependencies = mergeDependencies(
2265
+ dependencies,
2266
+ retMap.dependenciesList,
2267
+ parentComponent
2268
+ );
2192
2269
  }
2193
2270
  if (retMap.allImports) {
2194
2271
  allImports = { ...allImports, ...retMap.allImports };
2195
2272
  }
2196
2273
  // Complete the dependency tree by making parent component depend on the first level
2197
2274
  for (const p of pkgMap.rootList) {
2275
+ if (
2276
+ parentComponent &&
2277
+ p.name === parentComponent.name &&
2278
+ p.version === parentComponent.version
2279
+ ) {
2280
+ continue;
2281
+ }
2198
2282
  parentDependsOn.push(`pkg:pypi/${p.name}@${p.version}`);
2199
2283
  }
2200
2284
  if (pkgMap.pkgList && pkgMap.pkgList.length) {
2201
2285
  pkgList = pkgList.concat(pkgMap.pkgList);
2202
2286
  }
2203
2287
  if (pkgMap.dependenciesList) {
2204
- dependencies = mergeDependencies(dependencies, pkgMap.dependenciesList);
2288
+ dependencies = mergeDependencies(
2289
+ dependencies,
2290
+ pkgMap.dependenciesList,
2291
+ parentComponent
2292
+ );
2205
2293
  }
2206
2294
  const pdependencies = {
2207
- ref: parentComponent.purl,
2208
- dependsOn: parentDependsOn
2295
+ ref: parentComponent["bom-ref"],
2296
+ dependsOn: parentDependsOn.filter(
2297
+ (r) => parentComponent && r !== parentComponent["bom-ref"]
2298
+ )
2209
2299
  };
2210
2300
  dependencies.splice(0, 0, pdependencies);
2211
2301
  }
@@ -3628,15 +3718,25 @@ export const createCsharpBom = async (path, options) => {
3628
3718
  return {};
3629
3719
  };
3630
3720
 
3631
- export const mergeDependencies = (dependencies, newDependencies) => {
3721
+ export const mergeDependencies = (
3722
+ dependencies,
3723
+ newDependencies,
3724
+ parentComponent = {}
3725
+ ) => {
3632
3726
  const deps_map = {};
3727
+ const parentRef =
3728
+ parentComponent && parentComponent["bom-ref"]
3729
+ ? parentComponent["bom-ref"]
3730
+ : undefined;
3633
3731
  const combinedDeps = dependencies.concat(newDependencies || []);
3634
3732
  for (const adep of combinedDeps) {
3635
3733
  if (!deps_map[adep.ref]) {
3636
3734
  deps_map[adep.ref] = new Set();
3637
3735
  }
3638
3736
  for (const eachDepends of adep["dependsOn"]) {
3639
- deps_map[adep.ref].add(eachDepends);
3737
+ if (parentRef && eachDepends.toLowerCase() !== parentRef.toLowerCase()) {
3738
+ deps_map[adep.ref].add(eachDepends);
3739
+ }
3640
3740
  }
3641
3741
  }
3642
3742
  const retlist = [];
@@ -4762,7 +4862,7 @@ export async function submitBom(args, bomContents) {
4762
4862
  projectName: args.projectName,
4763
4863
  projectVersion: projectVersion,
4764
4864
  autoCreate: "true",
4765
- bom: Buffer.from(bomContents).toString()
4865
+ bom: encodedBomContents
4766
4866
  },
4767
4867
  responseType: "json"
4768
4868
  }).json();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "9.2.2",
3
+ "version": "9.3.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>",
@@ -75,7 +75,7 @@
75
75
  "yargs": "^17.7.2"
76
76
  },
77
77
  "optionalDependencies": {
78
- "@appthreat/atom": "^0.10.1",
78
+ "@appthreat/atom": "^1.0.0",
79
79
  "@cyclonedx/cdxgen-plugins-bin": "^1.2.0",
80
80
  "body-parser": "^1.20.2",
81
81
  "compression": "^1.7.4",
package/piptree.js CHANGED
@@ -50,19 +50,25 @@ def get_installed_distributions():
50
50
  return [d._dist for d in dists]
51
51
 
52
52
 
53
- def find_deps(idx, reqs):
53
+ def find_deps(idx, visited, reqs, traverse_count):
54
54
  freqs = []
55
55
  for r in reqs:
56
56
  d = idx.get(r.key)
57
+ if not d:
58
+ continue
57
59
  r.project_name = d.project_name if d is not None else r.project_name
60
+ if len(visited) > 100 and visited.get(r.project_name):
61
+ return freqs
58
62
  specs = sorted(r.specs, reverse=True)
59
63
  specs_str = ",".join(["".join(sp) for sp in specs]) if specs else ""
64
+ dreqs = d.requires()
65
+ visited[r.project_name] = True
60
66
  freqs.append(
61
67
  {
62
68
  "name": r.project_name,
63
69
  "version": importlib_metadata.version(r.key),
64
70
  "versionSpecifiers": specs_str,
65
- "dependencies": find_deps(idx, d.requires()),
71
+ "dependencies": find_deps(idx, visited, dreqs, traverse_count + 1) if dreqs and traverse_count < 200 else [],
66
72
  }
67
73
  )
68
74
  return freqs
@@ -73,20 +79,26 @@ def main(argv):
73
79
  tree = []
74
80
  pkgs = get_installed_distributions()
75
81
  idx = {p.key: p for p in pkgs}
82
+ visited = {}
83
+ traverse_count = 0
76
84
  for p in pkgs:
77
85
  fr = frozen_req_from_dist(p)
78
- tmpA = fr.split("==")
86
+ if not fr.startswith('# Editable'):
87
+ tmpA = fr.split("==")
88
+ else:
89
+ fr = p.key
90
+ tmpA = [fr,p.version]
79
91
  name = tmpA[0]
80
92
  if name.startswith("-e"):
81
- continue
82
- version = ""
93
+ name = name.split("#egg=")[-1].split(" ")[0].split("&")[0]
94
+ version = "latest"
83
95
  if len(tmpA) == 2:
84
96
  version = tmpA[1]
85
97
  tree.append(
86
98
  {
87
- "name": name,
99
+ "name": name.split(" ")[0],
88
100
  "version": version,
89
- "dependencies": find_deps(idx, p.requires()),
101
+ "dependencies": find_deps(idx, visited, p.requires(), traverse_count + 1),
90
102
  }
91
103
  )
92
104
  all_deps = {}
@@ -108,7 +120,7 @@ if __name__ == "__main__":
108
120
  * Execute the piptree plugin and return the generated tree as json object
109
121
  */
110
122
  export const getTreeWithPlugin = (env, python_cmd, basePath) => {
111
- let tree = undefined;
123
+ let tree = [];
112
124
  const tempDir = mkdtempSync(join(tmpdir(), "cdxgen-piptree-"));
113
125
  const pipPlugin = join(tempDir, "piptree.py");
114
126
  const pipTreeJson = join(tempDir, "piptree.json");