@cyclonedx/cdxgen 8.2.4 → 8.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/binary.js CHANGED
@@ -279,8 +279,12 @@ const getOSPackages = (src) => {
279
279
  const bomJsonFile = path.join(tempDir, "trivy-bom.json");
280
280
  const args = [
281
281
  imageType,
282
- "--skip-update",
282
+ "--skip-db-update",
283
+ "--skip-java-db-update",
283
284
  "--offline-scan",
285
+ "--no-progress",
286
+ "--exit-code",
287
+ "0",
284
288
  "--format",
285
289
  "cyclonedx",
286
290
  "--output",
@@ -302,11 +306,16 @@ const getOSPackages = (src) => {
302
306
  }
303
307
  }
304
308
  if (fs.existsSync(bomJsonFile)) {
305
- const tmpBom = JSON.parse(
306
- fs.readFileSync(bomJsonFile, {
307
- encoding: "utf-8"
308
- })
309
- );
309
+ let tmpBom = {};
310
+ try {
311
+ tmpBom = JSON.parse(
312
+ fs.readFileSync(bomJsonFile, {
313
+ encoding: "utf-8"
314
+ })
315
+ );
316
+ } catch (e) {
317
+ // ignore errors
318
+ }
310
319
  // Clean up
311
320
  if (tempDir && tempDir.startsWith(os.tmpdir())) {
312
321
  if (DEBUG_MODE) {
package/docker.js CHANGED
@@ -159,9 +159,11 @@ const getConnection = async (options) => {
159
159
  dockerConn = got.extend(opts);
160
160
  if (DEBUG_MODE) {
161
161
  if (isDockerRootless) {
162
- console.log("Docker service in rootless mode detected!");
162
+ console.log("Docker service in rootless mode detected.");
163
163
  } else {
164
- console.log("Docker service in root mode detected!");
164
+ console.log(
165
+ "Docker service in root mode detected. Consider switching to rootless mode to improve security. See https://docs.docker.com/engine/security/rootless/"
166
+ );
165
167
  }
166
168
  }
167
169
  } catch (err) {
@@ -172,7 +174,7 @@ const getConnection = async (options) => {
172
174
  dockerConn = got.extend(opts);
173
175
  isDockerRootless = true;
174
176
  if (DEBUG_MODE) {
175
- console.log("Docker service in rootless mode detected!");
177
+ console.log("Docker service in rootless mode detected.");
176
178
  }
177
179
  return dockerConn;
178
180
  } catch (err) {
@@ -185,7 +187,7 @@ const getConnection = async (options) => {
185
187
  dockerConn = got.extend(opts);
186
188
  isWinLocalTLS = true;
187
189
  if (DEBUG_MODE) {
188
- console.log("Docker desktop on Windows detected!");
190
+ console.log("Docker desktop on Windows detected.");
189
191
  }
190
192
  } else {
191
193
  opts.prefixUrl = opts.podmanRootlessPrefixUrl;
@@ -194,7 +196,9 @@ const getConnection = async (options) => {
194
196
  isPodmanRootless = true;
195
197
  dockerConn = got.extend(opts);
196
198
  if (DEBUG_MODE) {
197
- console.log("Podman in rootless mode detected!");
199
+ console.log(
200
+ "Podman in rootless mode detected. Thank you for using podman!"
201
+ );
198
202
  }
199
203
  }
200
204
  } catch (err) {
@@ -205,7 +209,9 @@ const getConnection = async (options) => {
205
209
  isPodman = true;
206
210
  isPodmanRootless = false;
207
211
  dockerConn = got.extend(opts);
208
- console.log("Podman in root mode detected!");
212
+ console.log(
213
+ "Podman in root mode detected. Consider switching to rootless mode to improve security. See https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md"
214
+ );
209
215
  } catch (err) {
210
216
  if (os.platform() === "win32") {
211
217
  console.warn(
@@ -416,7 +422,13 @@ const extractTar = async (fullImageName, dir) => {
416
422
  strict: true,
417
423
  C: dir,
418
424
  portable: true,
419
- onwarn: () => {}
425
+ onwarn: () => {},
426
+ filter: (path) => {
427
+ if (path.endsWith("cacerts")) {
428
+ return false;
429
+ }
430
+ return true;
431
+ }
420
432
  })
421
433
  );
422
434
  return true;
package/index.js CHANGED
@@ -988,9 +988,13 @@ const createJavaBom = async (path, options) => {
988
988
  if (pomFiles && pomFiles.length) {
989
989
  const cdxMavenPlugin =
990
990
  process.env.CDX_MAVEN_PLUGIN ||
991
- "org.cyclonedx:cyclonedx-maven-plugin:2.7.6";
991
+ "org.cyclonedx:cyclonedx-maven-plugin:2.7.7";
992
992
  const cdxMavenGoal = process.env.CDX_MAVEN_GOAL || "makeAggregateBom";
993
- let mvnArgs = [`${cdxMavenPlugin}:${cdxMavenGoal}`, "-DoutputName=bom"];
993
+ let mvnArgs = [
994
+ `${cdxMavenPlugin}:${cdxMavenGoal}`,
995
+ "-DoutputName=bom",
996
+ "-DincludeTestScope=true"
997
+ ];
994
998
  // By using quiet mode we can reduce the maxBuffer used and avoid crashes
995
999
  if (!DEBUG_MODE) {
996
1000
  mvnArgs.push("-q");
@@ -1152,8 +1156,14 @@ const createJavaBom = async (path, options) => {
1152
1156
  let gradleCmd = utils.getGradleCommand(path, null);
1153
1157
  const multiProjectMode = process.env.GRADLE_MULTI_PROJECT_MODE || "";
1154
1158
  // Support for multi-project applications
1155
- if (["true", "1"].includes(multiProjectMode)) {
1156
- 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
+ }
1157
1167
  const result = spawnSync(
1158
1168
  gradleCmd,
1159
1169
  ["projects", "-q", "--console", "plain"],
@@ -1171,14 +1181,20 @@ const createJavaBom = async (path, options) => {
1171
1181
  const stdout = result.stdout;
1172
1182
  if (stdout) {
1173
1183
  const cmdOutput = Buffer.from(stdout).toString();
1174
- const allProjects = utils.parseGradleProjects(cmdOutput);
1184
+ const retMap = utils.parseGradleProjects(cmdOutput);
1185
+ const allProjects = retMap.projects || [];
1186
+ let rootProject = retMap.rootProject;
1175
1187
  if (!allProjects) {
1176
1188
  console.log(
1177
1189
  "No projects found. Is this a gradle multi-project application?"
1178
1190
  );
1179
1191
  options.failOnError && process.exit(1);
1180
1192
  } else {
1181
- 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
+ );
1182
1198
  for (let sp of allProjects) {
1183
1199
  let gradleDepArgs = [
1184
1200
  sp + ":dependencies",
@@ -1212,7 +1228,7 @@ const createJavaBom = async (path, options) => {
1212
1228
  const sstdout = sresult.stdout;
1213
1229
  if (sstdout) {
1214
1230
  const cmdOutput = Buffer.from(sstdout).toString();
1215
- const parsedList = utils.parseGradleDep(cmdOutput);
1231
+ const parsedList = utils.parseGradleDep(cmdOutput, rootProject);
1216
1232
  const dlist = parsedList.pkgList;
1217
1233
  parentComponent = dlist.splice(0, 1)[0];
1218
1234
  if (
@@ -1245,7 +1261,7 @@ const createJavaBom = async (path, options) => {
1245
1261
  console.log(
1246
1262
  "Obtained",
1247
1263
  pkgList.length,
1248
- "from this gradle multi-project"
1264
+ "from this gradle multi-project. De-duping this list ..."
1249
1265
  );
1250
1266
  } else {
1251
1267
  console.log(
@@ -1286,8 +1302,29 @@ const createJavaBom = async (path, options) => {
1286
1302
  });
1287
1303
  if (result.status !== 0 || result.error) {
1288
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
+ }
1289
1321
  console.error(result.stdout, result.stderr);
1322
+ if (DEBUG_MODE) {
1323
+ console.log("-----------------------");
1324
+ }
1325
+ options.failOnError && process.exit(1);
1290
1326
  }
1327
+
1291
1328
  if (DEBUG_MODE || !result.stderr || options.failOnError) {
1292
1329
  console.log(
1293
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."
@@ -1315,9 +1352,6 @@ const createJavaBom = async (path, options) => {
1315
1352
  );
1316
1353
  options.failOnError && process.exit(1);
1317
1354
  }
1318
- } else {
1319
- console.log("Gradle unexpectedly didn't produce any output");
1320
- options.failOnError && process.exit(1);
1321
1355
  }
1322
1356
  }
1323
1357
  }
@@ -4308,6 +4342,7 @@ const createBom = async (path, options) => {
4308
4342
  case "mvn":
4309
4343
  case "maven":
4310
4344
  case "sbt":
4345
+ options.multiProject = true;
4311
4346
  return await createJavaBom(path, options);
4312
4347
  case "jar":
4313
4348
  options.multiProject = true;
@@ -4334,6 +4369,7 @@ const createBom = async (path, options) => {
4334
4369
  case "typescript":
4335
4370
  case "ts":
4336
4371
  case "tsx":
4372
+ options.multiProject = true;
4337
4373
  return await createNodejsBom(path, options);
4338
4374
  case "python":
4339
4375
  case "py":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "8.2.4",
3
+ "version": "8.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>",
@@ -49,8 +49,8 @@
49
49
  "url": "https://github.com/cyclonedx/cdxgen/issues"
50
50
  },
51
51
  "dependencies": {
52
- "@babel/parser": "^7.20.15",
53
- "@babel/traverse": "^7.20.13",
52
+ "@babel/parser": "^7.21.4",
53
+ "@babel/traverse": "^7.21.4",
54
54
  "cheerio": "^1.0.0-rc.12",
55
55
  "edn-data": "^1.0.0",
56
56
  "glob": "^8.1.0",
@@ -59,24 +59,24 @@
59
59
  "js-yaml": "^4.1.0",
60
60
  "jws": "^4.0.0",
61
61
  "node-stream-zip": "^1.15.0",
62
- "packageurl-js": "^1.0.0",
62
+ "packageurl-js": "^1.0.2",
63
63
  "parse-packagejson-name": "^1.0.1",
64
64
  "prettify-xml": "^1.2.0",
65
65
  "properties-reader": "^2.2.0",
66
- "semver": "^7.3.8",
66
+ "semver": "^7.5.0",
67
67
  "ssri": "^8.0.1",
68
68
  "table": "^6.8.1",
69
69
  "tar": "^6.1.13",
70
70
  "uuid": "^9.0.0",
71
71
  "xml-js": "^1.6.11",
72
72
  "xmlbuilder": "^15.1.1",
73
- "yargs": "^17.6.2"
73
+ "yargs": "^17.7.1"
74
74
  },
75
75
  "optionalDependencies": {
76
- "@cyclonedx/cdxgen-plugins-bin": "^1.0.5",
77
- "connect": "^3.7.0",
78
- "body-parser": "^1.20.1",
79
- "compression": "^1.7.4"
76
+ "@cyclonedx/cdxgen-plugins-bin": "^1.1.0",
77
+ "body-parser": "^1.20.2",
78
+ "compression": "^1.7.4",
79
+ "connect": "^3.7.0"
80
80
  },
81
81
  "files": [
82
82
  "*.js",
@@ -88,7 +88,7 @@
88
88
  "queries.json"
89
89
  ],
90
90
  "devDependencies": {
91
- "eslint": "^8.31.0",
91
+ "eslint": "^8.39.0",
92
92
  "jest": "^26.6.3"
93
93
  }
94
94
  }
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
 
@@ -4182,8 +4207,13 @@ const extractJarArchive = function (jarFile, tempDir) {
4182
4207
  }
4183
4208
  if (pomname && fs.existsSync(pomname)) {
4184
4209
  tempDir = path.dirname(jarFile);
4185
- } else {
4186
- fs.copyFileSync(jarFile, path.join(tempDir, fname));
4210
+ } else if (!fs.existsSync(path.join(tempDir, fname))) {
4211
+ // Only copy if the file doesn't exist
4212
+ fs.copyFileSync(
4213
+ jarFile,
4214
+ path.join(tempDir, fname),
4215
+ fs.constants.COPYFILE_FICLONE
4216
+ );
4187
4217
  }
4188
4218
  if (jarFile.endsWith(".war") || jarFile.endsWith(".hpi")) {
4189
4219
  let jarResult = spawnSync("jar", ["-xf", path.join(tempDir, fname)], {
@@ -4380,7 +4410,11 @@ const addPlugin = function (projectPath, plugin) {
4380
4410
  var originalPluginsFile = null;
4381
4411
  if (fs.existsSync(pluginsFile)) {
4382
4412
  originalPluginsFile = pluginsFile + ".cdxgen";
4383
- fs.copyFileSync(pluginsFile, originalPluginsFile);
4413
+ fs.copyFileSync(
4414
+ pluginsFile,
4415
+ originalPluginsFile,
4416
+ fs.constants.COPYFILE_FICLONE
4417
+ );
4384
4418
  }
4385
4419
 
4386
4420
  fs.writeFileSync(pluginsFile, plugin, { flag: "a" });
@@ -4404,7 +4438,11 @@ const cleanupPlugin = function (projectPath, originalPluginsFile) {
4404
4438
  return !fs.existsSync(pluginsFile);
4405
4439
  } else {
4406
4440
  // Bring back the original file
4407
- fs.copyFileSync(originalPluginsFile, pluginsFile);
4441
+ fs.copyFileSync(
4442
+ originalPluginsFile,
4443
+ pluginsFile,
4444
+ fs.constants.COPYFILE_FICLONE
4445
+ );
4408
4446
  fs.unlinkSync(originalPluginsFile);
4409
4447
  return true;
4410
4448
  }
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", () => {