@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 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
- "org.cyclonedx:cyclonedx-maven-plugin:2.7.2:makeAggregateBom",
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
- if (["true", "1"].includes(multiProjectMode)) {
1155
- console.log("Executing", gradleCmd, "projects in", path);
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 allProjects = utils.parseGradleProjects(cmdOutput);
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("Found", allProjects.length, "gradle sub-projects");
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.2.3",
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 (rawOutput) {
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: "root",
1151
- version: "latest",
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 = "pkg:maven/root@latest?type=jar";
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("+--- Project") || l.startsWith("\\--- Project")) {
1328
- let projName = l
1329
- .replace("+--- Project ", "")
1330
- .replace("\\--- Project ", "")
1331
- .split(" ")[0];
1332
- projName = projName.replace(/'/g, "");
1333
- if (
1334
- !projName.startsWith(":test") &&
1335
- !projName.startsWith(":docs") &&
1336
- !projName.startsWith(":qa")
1337
- ) {
1338
- projects.push(projName);
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 projects;
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
- let proj_list = utils.parseGradleProjects(
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(proj_list.length).toEqual(9);
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", () => {