@cyclonedx/cdxgen 8.2.3 → 8.3.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/README.md +2 -0
- package/index.js +48 -11
- package/package.json +1 -1
- package/utils.js +45 -20
- package/utils.test.js +17 -3
package/README.md
CHANGED
|
@@ -270,6 +270,8 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
|
|
|
270
270
|
| SBOM_SIGN_ALGORITHM | Signature algorithm. Some valid values are RS256, RS384, RS512, PS256, PS384, PS512, ES256 etc |
|
|
271
271
|
| SBOM_SIGN_PRIVATE_KEY | Private key to use for signing |
|
|
272
272
|
| SBOM_SIGN_PUBLIC_KEY | Optional. Public key to include in the SBoM signature |
|
|
273
|
+
| CDX_MAVEN_PLUGIN | CycloneDX Maven plugin to use. Default "org.cyclonedx:cyclonedx-maven-plugin:2.7.6" |
|
|
274
|
+
| CDX_MAVEN_GOAL | CycloneDX Maven plugin goal to use. Default makeAggregateBom. Other options: makeBom, makePackageBom |
|
|
273
275
|
|
|
274
276
|
## Plugins
|
|
275
277
|
|
package/index.js
CHANGED
|
@@ -986,9 +986,14 @@ const createJavaBom = async (path, options) => {
|
|
|
986
986
|
(options.multiProject ? "**/" : "") + "pom.xml"
|
|
987
987
|
);
|
|
988
988
|
if (pomFiles && pomFiles.length) {
|
|
989
|
+
const cdxMavenPlugin =
|
|
990
|
+
process.env.CDX_MAVEN_PLUGIN ||
|
|
991
|
+
"org.cyclonedx:cyclonedx-maven-plugin:2.7.7";
|
|
992
|
+
const cdxMavenGoal = process.env.CDX_MAVEN_GOAL || "makeAggregateBom";
|
|
989
993
|
let mvnArgs = [
|
|
990
|
-
|
|
991
|
-
"-DoutputName=bom"
|
|
994
|
+
`${cdxMavenPlugin}:${cdxMavenGoal}`,
|
|
995
|
+
"-DoutputName=bom",
|
|
996
|
+
"-DincludeTestScope=true"
|
|
992
997
|
];
|
|
993
998
|
// By using quiet mode we can reduce the maxBuffer used and avoid crashes
|
|
994
999
|
if (!DEBUG_MODE) {
|
|
@@ -1151,8 +1156,14 @@ const createJavaBom = async (path, options) => {
|
|
|
1151
1156
|
let gradleCmd = utils.getGradleCommand(path, null);
|
|
1152
1157
|
const multiProjectMode = process.env.GRADLE_MULTI_PROJECT_MODE || "";
|
|
1153
1158
|
// Support for multi-project applications
|
|
1154
|
-
|
|
1155
|
-
|
|
1159
|
+
// Let's experiment with defaulting to multi-project mode when multiple gradle files gets detected
|
|
1160
|
+
if (
|
|
1161
|
+
["true", "1"].includes(multiProjectMode) ||
|
|
1162
|
+
(gradleFiles.length > 1 && !["false", "0"].includes(multiProjectMode))
|
|
1163
|
+
) {
|
|
1164
|
+
if (DEBUG_MODE) {
|
|
1165
|
+
console.log("Executing", gradleCmd, "projects in", path);
|
|
1166
|
+
}
|
|
1156
1167
|
const result = spawnSync(
|
|
1157
1168
|
gradleCmd,
|
|
1158
1169
|
["projects", "-q", "--console", "plain"],
|
|
@@ -1170,14 +1181,20 @@ const createJavaBom = async (path, options) => {
|
|
|
1170
1181
|
const stdout = result.stdout;
|
|
1171
1182
|
if (stdout) {
|
|
1172
1183
|
const cmdOutput = Buffer.from(stdout).toString();
|
|
1173
|
-
const
|
|
1184
|
+
const retMap = utils.parseGradleProjects(cmdOutput);
|
|
1185
|
+
const allProjects = retMap.projects || [];
|
|
1186
|
+
let rootProject = retMap.rootProject;
|
|
1174
1187
|
if (!allProjects) {
|
|
1175
1188
|
console.log(
|
|
1176
1189
|
"No projects found. Is this a gradle multi-project application?"
|
|
1177
1190
|
);
|
|
1178
1191
|
options.failOnError && process.exit(1);
|
|
1179
1192
|
} else {
|
|
1180
|
-
console.log(
|
|
1193
|
+
console.log(
|
|
1194
|
+
"Found",
|
|
1195
|
+
allProjects.length,
|
|
1196
|
+
"gradle sub-projects. This might take a while ..."
|
|
1197
|
+
);
|
|
1181
1198
|
for (let sp of allProjects) {
|
|
1182
1199
|
let gradleDepArgs = [
|
|
1183
1200
|
sp + ":dependencies",
|
|
@@ -1211,7 +1228,7 @@ const createJavaBom = async (path, options) => {
|
|
|
1211
1228
|
const sstdout = sresult.stdout;
|
|
1212
1229
|
if (sstdout) {
|
|
1213
1230
|
const cmdOutput = Buffer.from(sstdout).toString();
|
|
1214
|
-
const parsedList = utils.parseGradleDep(cmdOutput);
|
|
1231
|
+
const parsedList = utils.parseGradleDep(cmdOutput, rootProject);
|
|
1215
1232
|
const dlist = parsedList.pkgList;
|
|
1216
1233
|
parentComponent = dlist.splice(0, 1)[0];
|
|
1217
1234
|
if (
|
|
@@ -1244,7 +1261,7 @@ const createJavaBom = async (path, options) => {
|
|
|
1244
1261
|
console.log(
|
|
1245
1262
|
"Obtained",
|
|
1246
1263
|
pkgList.length,
|
|
1247
|
-
"from this gradle multi-project"
|
|
1264
|
+
"from this gradle multi-project. De-duping this list ..."
|
|
1248
1265
|
);
|
|
1249
1266
|
} else {
|
|
1250
1267
|
console.log(
|
|
@@ -1285,8 +1302,29 @@ const createJavaBom = async (path, options) => {
|
|
|
1285
1302
|
});
|
|
1286
1303
|
if (result.status !== 0 || result.error) {
|
|
1287
1304
|
if (result.stderr) {
|
|
1305
|
+
const cmdError = Buffer.from(result.stderr).toString();
|
|
1306
|
+
if (
|
|
1307
|
+
cmdError.includes(
|
|
1308
|
+
"is not part of the build defined by settings file"
|
|
1309
|
+
) ||
|
|
1310
|
+
cmdError.includes(
|
|
1311
|
+
"was not found in any of the following sources"
|
|
1312
|
+
)
|
|
1313
|
+
) {
|
|
1314
|
+
console.log(
|
|
1315
|
+
"This is a multi-project gradle application. Set the environment variable GRADLE_MULTI_PROJECT_MODE to true to improve SBoM accuracy."
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
if (DEBUG_MODE) {
|
|
1319
|
+
console.log("-----------------------");
|
|
1320
|
+
}
|
|
1288
1321
|
console.error(result.stdout, result.stderr);
|
|
1322
|
+
if (DEBUG_MODE) {
|
|
1323
|
+
console.log("-----------------------");
|
|
1324
|
+
}
|
|
1325
|
+
options.failOnError && process.exit(1);
|
|
1289
1326
|
}
|
|
1327
|
+
|
|
1290
1328
|
if (DEBUG_MODE || !result.stderr || options.failOnError) {
|
|
1291
1329
|
console.log(
|
|
1292
1330
|
"1. Check if the correct version of java and gradle are installed and available in PATH. For example, some project might require Java 11 with gradle 7.\n cdxgen container image bundles Java 17 with gradle 8 which might be incompatible."
|
|
@@ -1314,9 +1352,6 @@ const createJavaBom = async (path, options) => {
|
|
|
1314
1352
|
);
|
|
1315
1353
|
options.failOnError && process.exit(1);
|
|
1316
1354
|
}
|
|
1317
|
-
} else {
|
|
1318
|
-
console.log("Gradle unexpectedly didn't produce any output");
|
|
1319
|
-
options.failOnError && process.exit(1);
|
|
1320
1355
|
}
|
|
1321
1356
|
}
|
|
1322
1357
|
}
|
|
@@ -4307,6 +4342,7 @@ const createBom = async (path, options) => {
|
|
|
4307
4342
|
case "mvn":
|
|
4308
4343
|
case "maven":
|
|
4309
4344
|
case "sbt":
|
|
4345
|
+
options.multiProject = true;
|
|
4310
4346
|
return await createJavaBom(path, options);
|
|
4311
4347
|
case "jar":
|
|
4312
4348
|
options.multiProject = true;
|
|
@@ -4333,6 +4369,7 @@ const createBom = async (path, options) => {
|
|
|
4333
4369
|
case "typescript":
|
|
4334
4370
|
case "ts":
|
|
4335
4371
|
case "tsx":
|
|
4372
|
+
options.multiProject = true;
|
|
4336
4373
|
return await createNodejsBom(path, options);
|
|
4337
4374
|
case "python":
|
|
4338
4375
|
case "py":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyclonedx/cdxgen",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.3.0",
|
|
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>",
|
package/utils.js
CHANGED
|
@@ -239,6 +239,7 @@ const getNpmMetadata = async function (pkgList) {
|
|
|
239
239
|
exports.getNpmMetadata = getNpmMetadata;
|
|
240
240
|
|
|
241
241
|
const _getDepPkgList = async function (
|
|
242
|
+
pkgLockFile,
|
|
242
243
|
pkgList,
|
|
243
244
|
dependenciesList,
|
|
244
245
|
depKeys,
|
|
@@ -256,7 +257,13 @@ const _getDepPkgList = async function (
|
|
|
256
257
|
name,
|
|
257
258
|
version,
|
|
258
259
|
_integrity: pkg.dependencies[name].integrity,
|
|
259
|
-
scope
|
|
260
|
+
scope,
|
|
261
|
+
properties: [
|
|
262
|
+
{
|
|
263
|
+
name: "SrcFile",
|
|
264
|
+
value: pkgLockFile
|
|
265
|
+
}
|
|
266
|
+
]
|
|
260
267
|
};
|
|
261
268
|
pkgList.push(apkg);
|
|
262
269
|
if (pkg.dependencies[name].dependencies) {
|
|
@@ -286,6 +293,7 @@ const _getDepPkgList = async function (
|
|
|
286
293
|
depKeys[purlString] = true;
|
|
287
294
|
}
|
|
288
295
|
await _getDepPkgList(
|
|
296
|
+
pkgLockFile,
|
|
289
297
|
pkgList,
|
|
290
298
|
dependenciesList,
|
|
291
299
|
depKeys,
|
|
@@ -431,6 +439,7 @@ const parsePkgLock = async (pkgLockFile) => {
|
|
|
431
439
|
});
|
|
432
440
|
}
|
|
433
441
|
pkgList = await _getDepPkgList(
|
|
442
|
+
pkgLockFile,
|
|
434
443
|
pkgList,
|
|
435
444
|
dependenciesList,
|
|
436
445
|
depKeys,
|
|
@@ -1140,15 +1149,21 @@ exports.parseMavenTree = parseMavenTree;
|
|
|
1140
1149
|
/**
|
|
1141
1150
|
* Parse gradle dependencies output
|
|
1142
1151
|
* @param {string} rawOutput Raw string output
|
|
1152
|
+
* @param {string} rootProjectName Root project name
|
|
1153
|
+
* @param {string} rootProjectVersion Root project version
|
|
1143
1154
|
*/
|
|
1144
|
-
const parseGradleDep = function (
|
|
1155
|
+
const parseGradleDep = function (
|
|
1156
|
+
rawOutput,
|
|
1157
|
+
rootProjectName = "root",
|
|
1158
|
+
rootProjectVersion = "latest"
|
|
1159
|
+
) {
|
|
1145
1160
|
if (typeof rawOutput === "string") {
|
|
1146
1161
|
let match = "";
|
|
1147
1162
|
// To render dependency tree we need a root project
|
|
1148
1163
|
const rootProject = {
|
|
1149
1164
|
group: "",
|
|
1150
|
-
name:
|
|
1151
|
-
version:
|
|
1165
|
+
name: rootProjectName,
|
|
1166
|
+
version: rootProjectVersion,
|
|
1152
1167
|
type: "maven",
|
|
1153
1168
|
qualifiers: { type: "jar" }
|
|
1154
1169
|
};
|
|
@@ -1156,7 +1171,7 @@ const parseGradleDep = function (rawOutput) {
|
|
|
1156
1171
|
const dependenciesList = [];
|
|
1157
1172
|
const keys_cache = {};
|
|
1158
1173
|
let last_level = 0;
|
|
1159
|
-
let last_purl =
|
|
1174
|
+
let last_purl = `pkg:maven/${rootProjectName}@${rootProjectVersion}?type=jar`;
|
|
1160
1175
|
const level_trees = {};
|
|
1161
1176
|
level_trees[last_purl] = [];
|
|
1162
1177
|
let stack = [last_purl];
|
|
@@ -1317,31 +1332,41 @@ exports.parseLeinMap = parseLeinMap;
|
|
|
1317
1332
|
|
|
1318
1333
|
/**
|
|
1319
1334
|
* Parse gradle projects output
|
|
1335
|
+
* FIXME: The method needs to be enhanced to capture project dependency tree. See issue #249
|
|
1336
|
+
*
|
|
1320
1337
|
* @param {string} rawOutput Raw string output
|
|
1321
1338
|
*/
|
|
1322
1339
|
const parseGradleProjects = function (rawOutput) {
|
|
1340
|
+
let rootProject = "root";
|
|
1341
|
+
const projects = new Set();
|
|
1323
1342
|
if (typeof rawOutput === "string") {
|
|
1324
|
-
const projects = [];
|
|
1325
1343
|
const tmpA = rawOutput.split("\n");
|
|
1326
1344
|
tmpA.forEach((l) => {
|
|
1327
|
-
if (l.startsWith("
|
|
1328
|
-
|
|
1329
|
-
.
|
|
1330
|
-
.
|
|
1331
|
-
.
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1345
|
+
if (l.startsWith("Root project ")) {
|
|
1346
|
+
rootProject = l
|
|
1347
|
+
.split("Root project ")[1]
|
|
1348
|
+
.split(" ")[0]
|
|
1349
|
+
.replace(/'/g, "");
|
|
1350
|
+
} else if (l.includes("--- Project")) {
|
|
1351
|
+
const tmpB = l.split("Project ");
|
|
1352
|
+
if (tmpB && tmpB.length > 1) {
|
|
1353
|
+
let projName = tmpB[1].split(" ")[0].replace(/'/g, "");
|
|
1354
|
+
// Include all projects including test projects
|
|
1355
|
+
if (projName.startsWith(":")) {
|
|
1356
|
+
projects.add(projName);
|
|
1357
|
+
}
|
|
1339
1358
|
}
|
|
1340
1359
|
}
|
|
1341
1360
|
});
|
|
1342
|
-
return
|
|
1361
|
+
return {
|
|
1362
|
+
rootProject,
|
|
1363
|
+
projects: Array.from(projects)
|
|
1364
|
+
};
|
|
1343
1365
|
}
|
|
1344
|
-
return
|
|
1366
|
+
return {
|
|
1367
|
+
rootProject,
|
|
1368
|
+
projects: []
|
|
1369
|
+
};
|
|
1345
1370
|
};
|
|
1346
1371
|
exports.parseGradleProjects = parseGradleProjects;
|
|
1347
1372
|
|
package/utils.test.js
CHANGED
|
@@ -208,11 +208,25 @@ test("parse gradle dependencies", () => {
|
|
|
208
208
|
});
|
|
209
209
|
|
|
210
210
|
test("parse gradle projects", () => {
|
|
211
|
-
expect(utils.parseGradleProjects(null)).toEqual(
|
|
212
|
-
|
|
211
|
+
expect(utils.parseGradleProjects(null)).toEqual({
|
|
212
|
+
projects: [],
|
|
213
|
+
rootProject: "root"
|
|
214
|
+
});
|
|
215
|
+
let retMap = utils.parseGradleProjects(
|
|
213
216
|
fs.readFileSync("./test/data/gradle-projects.out", { encoding: "utf-8" })
|
|
214
217
|
);
|
|
215
|
-
expect(
|
|
218
|
+
expect(retMap.rootProject).toEqual("elasticsearch");
|
|
219
|
+
expect(retMap.projects.length).toEqual(368);
|
|
220
|
+
retMap = utils.parseGradleProjects(
|
|
221
|
+
fs.readFileSync("./test/data/gradle-projects1.out", { encoding: "utf-8" })
|
|
222
|
+
);
|
|
223
|
+
expect(retMap.rootProject).toEqual("elasticsearch");
|
|
224
|
+
expect(retMap.projects.length).toEqual(403);
|
|
225
|
+
retMap = utils.parseGradleProjects(
|
|
226
|
+
fs.readFileSync("./test/data/gradle-projects2.out", { encoding: "utf-8" })
|
|
227
|
+
);
|
|
228
|
+
expect(retMap.rootProject).toEqual("fineract");
|
|
229
|
+
expect(retMap.projects.length).toEqual(22);
|
|
216
230
|
});
|
|
217
231
|
|
|
218
232
|
test("parse maven tree", () => {
|