@cyclonedx/cdxgen 8.4.11 → 8.4.13

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.
Files changed (4) hide show
  1. package/index.js +53 -17
  2. package/package.json +1 -1
  3. package/utils.js +58 -18
  4. package/utils.test.js +32 -15
package/index.js CHANGED
@@ -1057,7 +1057,7 @@ const createJavaBom = async (path, options) => {
1057
1057
  "Resolve the above maven error. This could be due to the following:\n"
1058
1058
  );
1059
1059
  console.log(
1060
- "1. Java version requirement: cdxgen container image bundles Java 17 with gradle 8 which might be incompatible."
1060
+ "1. Java version requirement: cdxgen container image bundles Java 17 with maven 3.8 which might be incompatible."
1061
1061
  );
1062
1062
  console.log(
1063
1063
  "2. Private dependencies cannot be downloaded: Check if any additional arguments must be passed to maven and set them via MVN_ARGS environment variable."
@@ -1580,6 +1580,7 @@ const createJavaBom = async (path, options) => {
1580
1580
  sbtVersion != null &&
1581
1581
  semver.gte(sbtVersion, "1.3.4") &&
1582
1582
  semver.lte(sbtVersion, "1.4.0");
1583
+ const useSlashSyntax = semver.gte(sbtVersion, "1.5.0");
1583
1584
  const isDependencyTreeBuiltIn =
1584
1585
  sbtVersion != null && semver.gte(sbtVersion, "1.4.0");
1585
1586
  let tempDir = fs.mkdtempSync(pathLib.join(os.tmpdir(), "cdxsbt-"));
@@ -1619,7 +1620,11 @@ const createJavaBom = async (path, options) => {
1619
1620
  ];
1620
1621
  } else {
1621
1622
  // write to the existing plugins file
1622
- sbtArgs = [`"dependencyList::toFile ${dlFile} --force"`];
1623
+ if (useSlashSyntax) {
1624
+ sbtArgs = [`"dependencyList / toFile ${dlFile} --force"`];
1625
+ } else {
1626
+ sbtArgs = [`"dependencyList::toFile ${dlFile} --force"`];
1627
+ }
1623
1628
  pluginFile = utils.addPlugin(basePath, sbtPluginDefinition);
1624
1629
  }
1625
1630
  // Note that the command has to be invoked with `shell: true` to properly execut sbt
@@ -1641,17 +1646,12 @@ const createJavaBom = async (path, options) => {
1641
1646
  "3. Consider creating a lockfile using sbt-dependency-lock plugin. See https://github.com/stringbean/sbt-dependency-lock"
1642
1647
  );
1643
1648
  options.failOnError && process.exit(1);
1644
- } else if (DEBUG_MODE) {
1645
- console.log(result.stdout);
1646
1649
  }
1647
1650
  if (!standalonePluginFile) {
1648
1651
  utils.cleanupPlugin(basePath, pluginFile);
1649
1652
  }
1650
1653
  if (fs.existsSync(dlFile)) {
1651
1654
  const cmdOutput = fs.readFileSync(dlFile, { encoding: "utf-8" });
1652
- if (DEBUG_MODE) {
1653
- console.log(cmdOutput);
1654
- }
1655
1655
  const dlist = utils.parseKVDep(cmdOutput);
1656
1656
  if (dlist && dlist.length) {
1657
1657
  pkgList = pkgList.concat(dlist);
@@ -4569,7 +4569,7 @@ exports.createBom = createBom;
4569
4569
  * @param bomContents BOM Xml
4570
4570
  */
4571
4571
  exports.submitBom = async (args, bomContents) => {
4572
- let serverUrl = args.serverUrl + "/api/v1/bom";
4572
+ let serverUrl = args.serverUrl.replace(/\/$/, "") + "/api/v1/bom";
4573
4573
  let encodedBomContents = Buffer.from(bomContents).toString("base64");
4574
4574
  if (encodedBomContents.startsWith("77u/")) {
4575
4575
  encodedBomContents = encodedBomContents.substring(4);
@@ -4594,13 +4594,49 @@ exports.submitBom = async (args, bomContents) => {
4594
4594
  projectVersion
4595
4595
  );
4596
4596
  }
4597
- return await got(serverUrl, {
4598
- method: "POST",
4599
- headers: {
4600
- "X-Api-Key": args.apiKey,
4601
- "Content-Type": "application/json"
4602
- },
4603
- json: bomPayload,
4604
- responseType: "json"
4605
- }).json();
4597
+ try {
4598
+ return await got(serverUrl, {
4599
+ method: "PUT",
4600
+ headers: {
4601
+ "X-Api-Key": args.apiKey,
4602
+ "Content-Type": "application/json"
4603
+ },
4604
+ json: bomPayload,
4605
+ responseType: "json"
4606
+ }).json();
4607
+ } catch (error) {
4608
+ if (error.response && error.response.statusCode === 401) {
4609
+ // Unauthorized
4610
+ console.log(
4611
+ "Received Unauthorized error. Check the API key used is valid and has necessary permissions to create projects and upload bom."
4612
+ );
4613
+ } else if (error.response && error.response.statusCode === 405) {
4614
+ // Method not allowed errors
4615
+ try {
4616
+ return await got(serverUrl, {
4617
+ method: "POST",
4618
+ headers: {
4619
+ "X-Api-Key": args.apiKey,
4620
+ "Content-Type": "application/json"
4621
+ },
4622
+ json: {
4623
+ project: args.projectId,
4624
+ projectName: args.projectName,
4625
+ projectVersion: projectVersion,
4626
+ autoCreate: "true",
4627
+ bom: Buffer.from(bomContents).toString()
4628
+ },
4629
+ responseType: "json"
4630
+ }).json();
4631
+ } catch (error) {
4632
+ console.log(
4633
+ "Unable to submit the SBoM to the Dependency-Track server using POST method"
4634
+ );
4635
+ console.log(error);
4636
+ }
4637
+ } else {
4638
+ console.log("Unable to submit the SBoM to the Dependency-Track server");
4639
+ console.log(error);
4640
+ }
4641
+ }
4606
4642
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "8.4.11",
3
+ "version": "8.4.13",
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
@@ -1103,7 +1103,7 @@ const parseMavenTree = function (rawOutput) {
1103
1103
  let last_purl = "";
1104
1104
  let stack = [];
1105
1105
  tmpA.forEach((l) => {
1106
- if (!includeMavenTestScope && l.endsWith(":test")) {
1106
+ if (!includeMavenTestScope && l.trim().endsWith(":test")) {
1107
1107
  return;
1108
1108
  }
1109
1109
  let level = 0;
@@ -1218,8 +1218,11 @@ const parseGradleDep = function (
1218
1218
  let last_level = 0;
1219
1219
  let last_purl = `pkg:maven/${rootProjectName}@${rootProjectVersion}?type=jar`;
1220
1220
  const first_purl = last_purl;
1221
+ let last_project_purl = first_purl;
1221
1222
  const level_trees = {};
1222
1223
  level_trees[last_purl] = [];
1224
+ let scope = undefined;
1225
+ let profileName = undefined;
1223
1226
  if (retMap && retMap.projects) {
1224
1227
  const subDependsOn = [];
1225
1228
  for (const sd of retMap.projects) {
@@ -1242,16 +1245,34 @@ const parseGradleDep = function (
1242
1245
  const depRegex =
1243
1246
  /^.*?--- +(?<group>[^\s:]+):(?<name>[^\s:]+)(?::(?:{strictly [[]?)?(?<versionspecified>[^,\s:}]+))?(?:})?(?:[^->]* +-> +(?<versionoverride>[^\s:]+))?/gm;
1244
1247
  for (const rline of rawOutput.split("\n")) {
1245
- if (last_level !== 1) {
1246
- if (
1247
- !rline ||
1248
- rline.trim() === "" ||
1249
- rline.startsWith("+--- ") ||
1250
- rline.startsWith("\\--- ")
1251
- ) {
1252
- last_level = 1;
1253
- last_purl = first_purl;
1254
- stack = [last_purl];
1248
+ if (!rline) {
1249
+ continue;
1250
+ }
1251
+ if (
1252
+ rline.trim() === "" ||
1253
+ rline.startsWith("+--- ") ||
1254
+ rline.startsWith("\\--- ")
1255
+ ) {
1256
+ last_level = 1;
1257
+ if (rline.startsWith("+--- project :")) {
1258
+ let tmpProj = rline.split("+--- project :");
1259
+ last_project_purl = `pkg:maven/${tmpProj[1].trim()}@${rootProjectVersion}?type=jar`;
1260
+ stack = [last_project_purl];
1261
+ last_purl = last_project_purl;
1262
+ } else {
1263
+ last_project_purl = first_purl;
1264
+ last_purl = last_project_purl;
1265
+ stack = [first_purl];
1266
+ }
1267
+ }
1268
+ if (rline.includes(" - ")) {
1269
+ profileName = rline.split(" - ")[0];
1270
+ if (profileName.toLowerCase().includes("test")) {
1271
+ scope = "optional";
1272
+ } else if (profileName.toLowerCase().includes("runtime")) {
1273
+ scope = "required";
1274
+ } else {
1275
+ scope = undefined;
1255
1276
  }
1256
1277
  }
1257
1278
  while ((match = depRegex.exec(rline))) {
@@ -1273,21 +1294,38 @@ const parseGradleDep = function (
1273
1294
  // Filter duplicates
1274
1295
  if (!deps_keys_cache[purlString]) {
1275
1296
  deps_keys_cache[purlString] = true;
1276
- deps.push({
1297
+ const adep = {
1277
1298
  group,
1278
1299
  name: name,
1279
1300
  version: version,
1280
1301
  qualifiers: { type: "jar" }
1281
- });
1302
+ };
1303
+ if (scope) {
1304
+ adep["scope"] = scope;
1305
+ }
1306
+ if (profileName) {
1307
+ adep.properties = [
1308
+ {
1309
+ name: "GradleProfileName",
1310
+ value: profileName
1311
+ }
1312
+ ];
1313
+ }
1314
+ deps.push(adep);
1282
1315
  }
1283
1316
  if (!level_trees[purlString]) {
1284
1317
  level_trees[purlString] = [];
1285
1318
  }
1286
- if (level == 0 || last_purl === "") {
1319
+ if (level == 0) {
1320
+ stack = [first_purl];
1321
+ stack.push(purlString);
1322
+ } else if (last_purl === "") {
1287
1323
  stack.push(purlString);
1288
1324
  } else if (level > last_level) {
1289
1325
  const cnodes = level_trees[last_purl] || [];
1290
- cnodes.push(purlString);
1326
+ if (!cnodes.includes(purlString)) {
1327
+ cnodes.push(purlString);
1328
+ }
1291
1329
  level_trees[last_purl] = cnodes;
1292
1330
  if (stack[stack.length - 1] !== purlString) {
1293
1331
  stack.push(purlString);
@@ -1297,9 +1335,11 @@ const parseGradleDep = function (
1297
1335
  stack.pop();
1298
1336
  }
1299
1337
  const last_stack =
1300
- stack.length > 0 ? stack[stack.length - 1] : first_purl;
1338
+ stack.length > 0 ? stack[stack.length - 1] : last_project_purl;
1301
1339
  const cnodes = level_trees[last_stack] || [];
1302
- cnodes.push(purlString);
1340
+ if (!cnodes.includes(purlString)) {
1341
+ cnodes.push(purlString);
1342
+ }
1303
1343
  level_trees[last_stack] = cnodes;
1304
1344
  stack.push(purlString);
1305
1345
  }
@@ -1614,7 +1654,7 @@ const getMvnMetadata = async function (pkgList) {
1614
1654
  if (!pkgList || !pkgList.length) {
1615
1655
  return pkgList;
1616
1656
  }
1617
- if (DEBUG_MODE) {
1657
+ if (DEBUG_MODE && fetchLicenses) {
1618
1658
  console.log(`About to query maven for ${pkgList.length} packages`);
1619
1659
  }
1620
1660
  for (const p of pkgList) {
package/utils.test.js CHANGED
@@ -98,7 +98,14 @@ test("parse gradle dependencies", () => {
98
98
  qualifiers: {
99
99
  type: "jar"
100
100
  },
101
- version: "1.0.2"
101
+ scope: "optional",
102
+ version: "1.0.2",
103
+ properties: [
104
+ {
105
+ name: "GradleProfileName",
106
+ value: "androidTestImplementation"
107
+ }
108
+ ]
102
109
  });
103
110
  expect(parsedList.pkgList[104]).toEqual({
104
111
  group: "androidx.print",
@@ -106,15 +113,14 @@ test("parse gradle dependencies", () => {
106
113
  qualifiers: {
107
114
  type: "jar"
108
115
  },
109
- version: "1.0.0"
110
- });
111
- expect(parsedList.pkgList[105]).toEqual({
112
- group: "androidx.core",
113
- name: "core",
114
- qualifiers: {
115
- type: "jar"
116
- },
117
- version: "1.7.0"
116
+ version: "1.0.0",
117
+ scope: "optional",
118
+ properties: [
119
+ {
120
+ name: "GradleProfileName",
121
+ value: "releaseUnitTestRuntimeClasspath"
122
+ }
123
+ ]
118
124
  });
119
125
  parsedList = utils.parseGradleDep(
120
126
  fs.readFileSync("./test/data/gradle-out1.dep", { encoding: "utf-8" })
@@ -125,7 +131,13 @@ test("parse gradle dependencies", () => {
125
131
  group: "org.springframework.boot",
126
132
  name: "spring-boot-starter-web",
127
133
  version: "2.2.0.RELEASE",
128
- qualifiers: { type: "jar" }
134
+ qualifiers: { type: "jar" },
135
+ properties: [
136
+ {
137
+ name: "GradleProfileName",
138
+ value: "compileClasspath"
139
+ }
140
+ ]
129
141
  });
130
142
 
131
143
  parsedList = utils.parseGradleDep(
@@ -210,17 +222,17 @@ test("parse gradle dependencies", () => {
210
222
  fs.readFileSync("./test/data/gradle-out-249.dep", { encoding: "utf-8" })
211
223
  );
212
224
  expect(parsedList.pkgList.length).toEqual(21);
213
- expect(parsedList.dependenciesList.length).toEqual(21);
225
+ expect(parsedList.dependenciesList.length).toEqual(22);
214
226
  parsedList = utils.parseGradleDep(
215
227
  fs.readFileSync("./test/data/gradle-service.out", { encoding: "utf-8" })
216
228
  );
217
229
  expect(parsedList.pkgList.length).toEqual(35);
218
- expect(parsedList.dependenciesList.length).toEqual(35);
230
+ expect(parsedList.dependenciesList.length).toEqual(36);
219
231
  parsedList = utils.parseGradleDep(
220
232
  fs.readFileSync("./test/data/gradle-s.out", { encoding: "utf-8" })
221
233
  );
222
234
  expect(parsedList.pkgList.length).toEqual(28);
223
- expect(parsedList.dependenciesList.length).toEqual(28);
235
+ expect(parsedList.dependenciesList.length).toEqual(29);
224
236
  parsedList = utils.parseGradleDep(
225
237
  fs.readFileSync("./test/data/gradle-core.out", { encoding: "utf-8" })
226
238
  );
@@ -1022,8 +1034,8 @@ test("get repo license", async () => {
1022
1034
  url: "https://github.com/ugorji/go/blob/master/LICENSE"
1023
1035
  });
1024
1036
  });
1025
- */
1026
1037
  test("get go pkg license", async () => {
1038
+ jest.setTimeout(120000);
1027
1039
  let license = await utils.getGoPkgLicense({
1028
1040
  group: "github.com/Azure/azure-amqp-common-go",
1029
1041
  name: "v2"
@@ -1057,6 +1069,7 @@ test("get go pkg license", async () => {
1057
1069
  }
1058
1070
  ]);
1059
1071
  });
1072
+ */
1060
1073
 
1061
1074
  test("get licenses", () => {
1062
1075
  let licenses = utils.getLicenses({ license: "MIT" });
@@ -1639,6 +1652,10 @@ test("parse scala sbt list", async () => {
1639
1652
  fs.readFileSync("./test/data/sbt-dl.list", { encoding: "utf-8" })
1640
1653
  );
1641
1654
  expect(deps.length).toEqual(57);
1655
+ deps = utils.parseKVDep(
1656
+ fs.readFileSync("./test/data/atom-sbt-list.txt", { encoding: "utf-8" })
1657
+ );
1658
+ expect(deps.length).toEqual(117);
1642
1659
  });
1643
1660
 
1644
1661
  test("parse scala sbt lock", async () => {