@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/README.md +34 -34
- package/data/pypi-pkg-aliases.json +36 -24
- package/display.js +35 -24
- package/index.js +115 -15
- package/package.json +2 -2
- package/piptree.js +20 -8
- package/utils.js +214 -104
- package/utils.test.js +42 -17
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
|
|
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
|
-
|
|
2044
|
+
let parentComponent = createDefaultParentComponent(path, "pypi");
|
|
2023
2045
|
const pipenvMode = existsSync(join(path, "Pipfile"));
|
|
2024
|
-
|
|
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(
|
|
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(
|
|
2288
|
+
dependencies = mergeDependencies(
|
|
2289
|
+
dependencies,
|
|
2290
|
+
pkgMap.dependenciesList,
|
|
2291
|
+
parentComponent
|
|
2292
|
+
);
|
|
2205
2293
|
}
|
|
2206
2294
|
const pdependencies = {
|
|
2207
|
-
ref: parentComponent
|
|
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 = (
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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");
|