@cyclonedx/cdxgen 9.6.1 → 9.7.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/utils.js CHANGED
@@ -329,21 +329,21 @@ export const getNpmMetadata = async function (pkgList) {
329
329
  const _getDepPkgList = async function (
330
330
  pkgLockFile,
331
331
  pkgList,
332
- dependenciesList,
333
332
  depKeys,
334
- pkg
333
+ pkg,
334
+ versionCache
335
335
  ) {
336
- const pkgDependencies =
337
- pkg.lockfileVersion && pkg.lockfileVersion >= 3
338
- ? pkg.packages
339
- : pkg.dependencies;
340
- const versionCache = {};
336
+ const pkgDependencies = {
337
+ ...(pkg.packages || {}),
338
+ ...(pkg.dependencies || {})
339
+ };
341
340
  if (pkg.packages) {
342
341
  for (const k in pkg.packages) {
343
342
  if (k === "") {
344
343
  continue;
345
344
  }
346
345
  const pl = pkg.packages[k];
346
+ versionCache[k] = pl.version;
347
347
  versionCache[k.replaceAll("node_modules/", "")] = pl.version;
348
348
  }
349
349
  }
@@ -355,7 +355,13 @@ const _getDepPkgList = async function (
355
355
  continue;
356
356
  }
357
357
  const name = k;
358
- const version = pkgDependencies[name].version;
358
+ let version = pkgDependencies[name].version;
359
+ if (!version && versionCache[k]) {
360
+ version = versionCache[k];
361
+ }
362
+ if (!version && pkgDependencies["node_modules/" + name]) {
363
+ version = pkgDependencies["node_modules/" + name].version;
364
+ }
359
365
  const purl = new PackageURL(
360
366
  "npm",
361
367
  "",
@@ -415,32 +421,20 @@ const _getDepPkgList = async function (
415
421
  const deppurlString = decodeURIComponent(deppurl.toString());
416
422
  deplist.push(deppurlString);
417
423
  }
418
- if (!depKeys[purlString]) {
419
- dependenciesList.push({
420
- ref: purlString,
421
- dependsOn: deplist
422
- });
423
- depKeys[purlString] = true;
424
- }
424
+ depKeys[purlString] = (depKeys[purlString] || []).concat(deplist);
425
425
  if (pkg.lockfileVersion && pkg.lockfileVersion >= 3) {
426
426
  // Do not recurse for lock file v3 and above
427
427
  } else {
428
428
  await _getDepPkgList(
429
429
  pkgLockFile,
430
430
  pkgList,
431
- dependenciesList,
432
431
  depKeys,
433
- pkgDependencies[name]
432
+ pkgDependencies[name],
433
+ versionCache
434
434
  );
435
435
  }
436
- } else {
437
- if (!depKeys[purlString]) {
438
- dependenciesList.push({
439
- ref: purlString,
440
- dependsOn: []
441
- });
442
- depKeys[purlString] = true;
443
- }
436
+ } else if (!depKeys[purlString]) {
437
+ depKeys[purlString] = [];
444
438
  }
445
439
  }
446
440
  }
@@ -519,6 +513,7 @@ export const parsePkgLock = async (pkgLockFile, options = {}) => {
519
513
  const dependenciesList = [];
520
514
  const depKeys = {};
521
515
  let rootPkg = {};
516
+ const versionCache = {};
522
517
  if (!options) {
523
518
  options = {};
524
519
  }
@@ -612,11 +607,17 @@ export const parsePkgLock = async (pkgLockFile, options = {}) => {
612
607
  pkgList = await _getDepPkgList(
613
608
  pkgLockFile,
614
609
  pkgList,
615
- dependenciesList,
616
610
  depKeys,
617
- lockData
611
+ lockData,
612
+ versionCache
618
613
  );
619
614
  }
615
+ for (const dk of Object.keys(depKeys)) {
616
+ dependenciesList.push({
617
+ ref: dk,
618
+ dependsOn: depKeys[dk] || []
619
+ });
620
+ }
620
621
  if (fetchLicenses && pkgList && pkgList.length) {
621
622
  if (DEBUG_MODE) {
622
623
  console.log(
@@ -1338,7 +1339,7 @@ export const parseMavenTree = function (rawOutput) {
1338
1339
  pkgArr[0],
1339
1340
  pkgArr[1],
1340
1341
  versionStr,
1341
- { type: "jar" },
1342
+ { type: pkgArr[2] },
1342
1343
  null
1343
1344
  ).toString();
1344
1345
  purlString = decodeURIComponent(purlString);
@@ -1346,7 +1347,7 @@ export const parseMavenTree = function (rawOutput) {
1346
1347
  group: pkgArr[0],
1347
1348
  name: pkgArr[1],
1348
1349
  version: versionStr,
1349
- qualifiers: { type: "jar" }
1350
+ qualifiers: { type: pkgArr[2] }
1350
1351
  });
1351
1352
  if (!level_trees[purlString]) {
1352
1353
  level_trees[purlString] = [];
@@ -1877,8 +1878,9 @@ export const parseBazelSkyframe = function (rawOutput) {
1877
1878
  // Remove the protocol, registry url and then file name
1878
1879
  let prefix_slice_count = 2;
1879
1880
  // Bug: #169
1880
- if (l.includes("/maven2/")) {
1881
- prefix_slice_count = 3;
1881
+ const prefix = process.env.BAZEL_STRIP_MAVEN_PREFIX || "/maven2/";
1882
+ if (l.includes(prefix)) {
1883
+ prefix_slice_count = prefix.split("/").length;
1882
1884
  }
1883
1885
  jarPathParts = jarPathParts.slice(prefix_slice_count, -1);
1884
1886
  // The last part would be the version
@@ -2007,7 +2009,8 @@ export const guessLicenseId = function (content) {
2007
2009
  * @param {Array} pkgList Package list
2008
2010
  */
2009
2011
  export const getMvnMetadata = async function (pkgList) {
2010
- const MAVEN_CENTRAL_URL = "https://repo1.maven.org/maven2/";
2012
+ const MAVEN_CENTRAL_URL =
2013
+ process.env.MAVEN_CENTRAL_URL || "https://repo1.maven.org/maven2/";
2011
2014
  const ANDROID_MAVEN = "https://maven.google.com/";
2012
2015
  const cdepList = [];
2013
2016
  if (!pkgList || !pkgList.length) {
@@ -3285,14 +3288,15 @@ export const getCratesMetadata = async function (pkgList) {
3285
3288
  * @param {Array} pkgList Package list
3286
3289
  */
3287
3290
  export const getDartMetadata = async function (pkgList) {
3288
- const PUB_DEV_URL = "https://pub.dev/api/packages/";
3291
+ const PUB_DEV_URL = process.env.PUB_DEV_URL || "https://pub.dev";
3292
+ const PUB_PACKAGES_URL = PUB_DEV_URL + "/api/packages/";
3289
3293
  const cdepList = [];
3290
3294
  for (const p of pkgList) {
3291
3295
  try {
3292
3296
  if (DEBUG_MODE) {
3293
- console.log(`Querying pub.dev for ${p.name}`);
3297
+ console.log(`Querying ${PUB_DEV_URL} for ${p.name}`);
3294
3298
  }
3295
- const res = await cdxgenAgent.get(PUB_DEV_URL + p.name, {
3299
+ const res = await cdxgenAgent.get(PUB_PACKAGES_URL + p.name, {
3296
3300
  responseType: "json",
3297
3301
  headers: {
3298
3302
  Accept: "application/vnd.pub.v2+json"
@@ -3310,7 +3314,7 @@ export const getDartMetadata = async function (pkgList) {
3310
3314
  if (pubspec.homepage) {
3311
3315
  p.homepage = { url: pubspec.homepage };
3312
3316
  }
3313
- p.license = "https://pub.dev/packages/" + p.name + "/license";
3317
+ p.license = `${PUB_DEV_URL}/packages/${p.name}/license`;
3314
3318
  cdepList.push(p);
3315
3319
  break;
3316
3320
  }
@@ -4764,49 +4768,99 @@ export const convertOSQueryResults = function (
4764
4768
  const pkgList = [];
4765
4769
  if (results && results.length) {
4766
4770
  for (const res of results) {
4767
- if (res.version) {
4768
- const version = res.version;
4769
- let name = res.name || res.device_id;
4770
- const group = "";
4771
- const subpath = res.path || res.admindir || res.source;
4772
- const publisher = res.maintainer || res.creator;
4773
- let scope = undefined;
4774
- const compScope = res.priority;
4775
- if (["required", "optional", "excluded"].includes(compScope)) {
4776
- scope = compScope;
4777
- }
4778
- const description =
4779
- res.description ||
4780
- res.arguments ||
4781
- res.device ||
4782
- res.codename ||
4783
- res.section ||
4784
- res.status ||
4785
- res.identifier ||
4786
- res.components;
4787
- // Re-use the name from query obj
4788
- if (!name && results.length === 1 && queryObj.name) {
4789
- name = queryObj.name;
4790
- }
4791
- if (name && version) {
4792
- const purl = new PackageURL(
4793
- queryObj.purlType || "swid",
4794
- group,
4795
- name,
4796
- version,
4797
- undefined,
4798
- subpath
4799
- );
4800
- pkgList.push({
4801
- name,
4802
- group,
4803
- version,
4804
- description,
4805
- publisher,
4806
- purl,
4807
- scope
4808
- });
4771
+ const version =
4772
+ res.version ||
4773
+ res.hotfix_id ||
4774
+ res.hardware_version ||
4775
+ res.port ||
4776
+ res.pid ||
4777
+ res.subject_key_id ||
4778
+ res.interface ||
4779
+ res.instance_id;
4780
+ let name =
4781
+ res.name ||
4782
+ res.device_id ||
4783
+ res.hotfix_id ||
4784
+ res.uuid ||
4785
+ res.serial ||
4786
+ res.pid ||
4787
+ res.address ||
4788
+ res.ami_id ||
4789
+ res.interface ||
4790
+ res.client_app_id;
4791
+ let group = "";
4792
+ const subpath = res.path || res.admindir || res.source;
4793
+ let publisher =
4794
+ res.publisher ||
4795
+ res.maintainer ||
4796
+ res.creator ||
4797
+ res.manufacturer ||
4798
+ res.provider ||
4799
+ "";
4800
+ if (publisher === "null") {
4801
+ publisher = "";
4802
+ }
4803
+ let scope = undefined;
4804
+ const compScope = res.priority;
4805
+ if (["required", "optional", "excluded"].includes(compScope)) {
4806
+ scope = compScope;
4807
+ }
4808
+ const description =
4809
+ res.description ||
4810
+ res.summary ||
4811
+ res.arguments ||
4812
+ res.device ||
4813
+ res.codename ||
4814
+ res.section ||
4815
+ res.status ||
4816
+ res.identifier ||
4817
+ res.components ||
4818
+ "";
4819
+ // Re-use the name from query obj
4820
+ if (!name && results.length === 1 && queryObj.name) {
4821
+ name = queryObj.name;
4822
+ }
4823
+ let qualifiers = undefined;
4824
+ if (res.identifying_number && res.identifying_number.length) {
4825
+ qualifiers = {
4826
+ tag_id: res.identifying_number.replace("{", "").replace("}", "")
4827
+ };
4828
+ }
4829
+ if (name) {
4830
+ name = name.replace(/ /g, "+").replace(/[:%]/g, "-");
4831
+ group = group.replace(/ /g, "+").replace(/[:%]/g, "-");
4832
+ const purl = new PackageURL(
4833
+ queryObj.purlType || "swid",
4834
+ group,
4835
+ name,
4836
+ version || "",
4837
+ qualifiers,
4838
+ subpath
4839
+ ).toString();
4840
+ const apkg = {
4841
+ name,
4842
+ group,
4843
+ version: version || "",
4844
+ description,
4845
+ publisher,
4846
+ "bom-ref": decodeURIComponent(purl),
4847
+ purl,
4848
+ scope,
4849
+ type: queryObj.componentType
4850
+ };
4851
+ const props = [{ name: "cdx:osquery:category", value: queryCategory }];
4852
+ for (const k of Object.keys(res).filter(
4853
+ (p) => !["name", "version", "description", "publisher"].includes(p)
4854
+ )) {
4855
+ if (res[k] && res[k] !== "null") {
4856
+ props.push({
4857
+ name: k,
4858
+ value: res[k]
4859
+ });
4860
+ }
4809
4861
  }
4862
+ apkg.properties = props;
4863
+ pkgList.push(apkg);
4810
4864
  }
4811
4865
  }
4812
4866
  }
@@ -5150,11 +5204,18 @@ export const collectJarNS = function (jarPath, pomPathMap = {}) {
5150
5204
  if (jarFiles && jarFiles.length) {
5151
5205
  for (const jf of jarFiles) {
5152
5206
  const jarname = jf;
5153
- const pomname =
5207
+ let pomname =
5154
5208
  pomPathMap[basename(jf).replace(".jar", ".pom")] ||
5155
5209
  jarname.replace(".jar", ".pom");
5156
5210
  let pomData = undefined;
5157
5211
  let purl = undefined;
5212
+ // In some cases, the pom name might be slightly different to the jar name
5213
+ if (!existsSync(pomname)) {
5214
+ const pomSearch = getAllFiles(dirname(jf), "*.pom");
5215
+ if (pomSearch && pomSearch.length === 1) {
5216
+ pomname = pomSearch[0];
5217
+ }
5218
+ }
5158
5219
  if (existsSync(pomname)) {
5159
5220
  pomData = parsePomXml(readFileSync(pomname, "utf-8"));
5160
5221
  if (pomData) {
@@ -5168,6 +5229,52 @@ export const collectJarNS = function (jarPath, pomPathMap = {}) {
5168
5229
  );
5169
5230
  purl = purlObj.toString();
5170
5231
  }
5232
+ } else if (jf.includes(join(".m2", "repository"))) {
5233
+ // Let's try our best to construct a purl for .m2 cache entries of the form
5234
+ // .m2/repository/org/apache/logging/log4j/log4j-web/3.0.0-SNAPSHOT/log4j-web-3.0.0-SNAPSHOT.jar
5235
+ const tmpA = jf.split(join(".m2", "repository", ""));
5236
+ if (tmpA && tmpA.length) {
5237
+ let tmpJarPath = tmpA[tmpA.length - 1];
5238
+ // This would yield log4j-web-3.0.0-SNAPSHOT.jar
5239
+ const jarFileName = basename(tmpJarPath).replace(".jar", "");
5240
+ let tmpDirParts = dirname(tmpJarPath).split(_sep);
5241
+ // Retrieve the version
5242
+ let jarVersion = tmpDirParts.pop();
5243
+ if (jarVersion === "plugins") {
5244
+ jarVersion = tmpDirParts.pop();
5245
+ if (jarVersion === "eclipse") {
5246
+ jarVersion = tmpDirParts.pop();
5247
+ }
5248
+ }
5249
+ // The result would form the group name
5250
+ let jarGroupName = tmpDirParts.join(".").replace(/^\./, "");
5251
+ let qualifierType = "jar";
5252
+ // Support for p2 bundles and plugins
5253
+ // See https://github.com/CycloneDX/cyclonedx-maven-plugin/issues/137
5254
+ // See https://github.com/CycloneDX/cdxgen/pull/510#issuecomment-1702551615
5255
+ if (jarGroupName.startsWith("p2.osgi.bundle")) {
5256
+ jarGroupName = "p2.osgi.bundle";
5257
+ qualifierType = "osgi-bundle";
5258
+ } else if (jarGroupName.startsWith("p2.eclipse.plugin")) {
5259
+ jarGroupName = "p2.eclipse.plugin";
5260
+ qualifierType = "eclipse-plugin";
5261
+ } else if (jarGroupName.startsWith("p2.binary")) {
5262
+ jarGroupName = "p2.binary";
5263
+ qualifierType = "eclipse-executable";
5264
+ } else if (jarGroupName.startsWith("p2.org.eclipse.update.feature")) {
5265
+ jarGroupName = "p2.org.eclipse.update.feature";
5266
+ qualifierType = "eclipse-feature";
5267
+ }
5268
+ const purlObj = new PackageURL(
5269
+ "maven",
5270
+ jarGroupName,
5271
+ jarFileName.replace(`-${jarVersion}`, ""),
5272
+ jarVersion,
5273
+ { type: qualifierType },
5274
+ null
5275
+ );
5276
+ purl = purlObj.toString();
5277
+ }
5171
5278
  } else if (jf.includes(join(".gradle", "caches"))) {
5172
5279
  // Let's try our best to construct a purl for gradle cache entries of the form
5173
5280
  // .gradle/caches/modules-2/files-2.1/org.xmlresolver/xmlresolver/4.2.0/f4dbdaa83d636dcac91c9003ffa7fb173173fe8d/xmlresolver-4.2.0-data.jar
@@ -5175,7 +5282,7 @@ export const collectJarNS = function (jarPath, pomPathMap = {}) {
5175
5282
  if (tmpA && tmpA.length) {
5176
5283
  let tmpJarPath = tmpA[tmpA.length - 1];
5177
5284
  // This would yield xmlresolver-4.2.0-data.jar
5178
- const jarFileName = basename(tmpJarPath);
5285
+ const jarFileName = basename(tmpJarPath).replace(".jar", "");
5179
5286
  let tmpDirParts = dirname(tmpJarPath).split(_sep);
5180
5287
  // This would remove the hash from the end of the directory name
5181
5288
  tmpDirParts.pop();
@@ -5241,6 +5348,9 @@ export const convertJarNSToPackages = (jarNSMapping) => {
5241
5348
  }
5242
5349
  const name = pom.artifactId || purlObj.name;
5243
5350
  if (!name) {
5351
+ console.warn(
5352
+ `Unable to identify the metadata for ${purl}. This will be skipped.`
5353
+ );
5244
5354
  continue;
5245
5355
  }
5246
5356
  const apackage = {
@@ -5783,27 +5893,37 @@ export const executeAtom = (src, args) => {
5783
5893
  }
5784
5894
  const freeMemoryGB = Math.floor(freemem() / 1024 / 1024 / 1024);
5785
5895
  if (DEBUG_MODE) {
5786
- console.log("Execuing", ATOM_BIN, args.join(" "));
5896
+ console.log("Executing", ATOM_BIN, args.join(" "));
5787
5897
  }
5788
5898
  const env = {
5789
5899
  ...process.env,
5790
5900
  JAVA_OPTS: `-Xms${freeMemoryGB}G -Xmx${freeMemoryGB}G`
5791
5901
  };
5902
+ env.PATH = `${env.PATH}${_delimiter}${join(
5903
+ dirNameStr,
5904
+ "node_modules",
5905
+ ".bin"
5906
+ )}`;
5792
5907
  const result = spawnSync(ATOM_BIN, args, {
5793
5908
  cwd,
5794
5909
  encoding: "utf-8",
5795
5910
  timeout: TIMEOUT_MS,
5796
5911
  env
5797
5912
  });
5798
- if (
5799
- result.stderr &&
5800
- result.stderr.includes(
5801
- "has been compiled by a more recent version of the Java Runtime"
5802
- )
5803
- ) {
5804
- console.log(
5805
- "Atom requires Java 17 or above. Please install a suitable version and re-run cdxgen to improve the SBoM accuracy.\nAlternatively, use the cdxgen container image."
5806
- );
5913
+ if (result.stderr) {
5914
+ if (
5915
+ result.stderr.includes(
5916
+ "has been compiled by a more recent version of the Java Runtime"
5917
+ )
5918
+ ) {
5919
+ console.log(
5920
+ "Atom requires Java 17 or above. Please install a suitable version and re-run cdxgen to improve the SBoM accuracy.\nAlternatively, use the cdxgen container image."
5921
+ );
5922
+ } else if (result.stderr.includes("astgen")) {
5923
+ console.warn(
5924
+ "WARN: Unable to locate astgen command. Install atom globally using sudo npm install -g @appthreat/atom to resolve this issue."
5925
+ );
5926
+ }
5807
5927
  }
5808
5928
  if (DEBUG_MODE) {
5809
5929
  if (result.stdout) {
@@ -5813,7 +5933,7 @@ export const executeAtom = (src, args) => {
5813
5933
  console.log(result.stderr);
5814
5934
  }
5815
5935
  }
5816
- return true;
5936
+ return !result.error;
5817
5937
  };
5818
5938
 
5819
5939
  /**
@@ -6232,3 +6352,14 @@ export const addEvidenceForImports = (pkgList, allImports) => {
6232
6352
  }
6233
6353
  return pkgList;
6234
6354
  };
6355
+
6356
+ export const componentSorter = (a, b) => {
6357
+ if (a && b) {
6358
+ for (const k of ["bom-ref", "purl", "name"]) {
6359
+ if (a[k] && b[k]) {
6360
+ return a[k].localeCompare(b[k]);
6361
+ }
6362
+ }
6363
+ }
6364
+ return a.localeCompare(b);
6365
+ };
package/utils.test.js CHANGED
@@ -502,10 +502,10 @@ test("parse maven tree", () => {
502
502
  group: "com.pogeyan.cmis",
503
503
  name: "copper-server",
504
504
  version: "1.15.2",
505
- qualifiers: { type: "jar" }
505
+ qualifiers: { type: "war" }
506
506
  });
507
507
  expect(parsedList.dependenciesList[0]).toEqual({
508
- ref: "pkg:maven/com.pogeyan.cmis/copper-server@1.15.2?type=jar",
508
+ ref: "pkg:maven/com.pogeyan.cmis/copper-server@1.15.2?type=war",
509
509
  dependsOn: [
510
510
  "pkg:maven/javax/javaee-web-api@7.0?type=jar",
511
511
  "pkg:maven/org.apache.chemistry.opencmis/chemistry-opencmis-server-support@1.0.0?type=jar",
@@ -553,6 +553,35 @@ test("parse maven tree", () => {
553
553
  "pkg:maven/org.apache.geode/geode-core@1.1.1?type=jar"
554
554
  ]
555
555
  });
556
+ parsedList = parseMavenTree(
557
+ readFileSync("./test/data/mvn-p2-plugin.txt", {
558
+ encoding: "utf-8"
559
+ })
560
+ );
561
+ expect(parsedList.pkgList.length).toEqual(79);
562
+ expect(parsedList.pkgList[0]).toEqual({
563
+ group: "example.group",
564
+ name: "eclipse-repository",
565
+ version: "1.0.0-SNAPSHOT",
566
+ qualifiers: { type: "eclipse-repository" }
567
+ });
568
+ expect(parsedList.pkgList[4]).toEqual({
569
+ group: "p2.eclipse.plugin",
570
+ name: "com.ibm.icu",
571
+ version: "67.1.0.v20200706-1749",
572
+ qualifiers: { type: "eclipse-plugin" }
573
+ });
574
+ expect(parsedList.dependenciesList.length).toEqual(79);
575
+ expect(parsedList.dependenciesList[0]).toEqual({
576
+ ref: "pkg:maven/example.group/eclipse-repository@1.0.0-SNAPSHOT?type=eclipse-repository",
577
+ dependsOn: [
578
+ "pkg:maven/example.group/example-feature@0.1.0-SNAPSHOT?type=eclipse-feature",
579
+ "pkg:maven/example.group/example-feature-2@0.2.0-SNAPSHOT?type=eclipse-feature",
580
+ "pkg:maven/example.group/example-bundle@0.1.0-SNAPSHOT?type=eclipse-plugin",
581
+ "pkg:maven/example.group/org.tycho.demo.rootfiles@1.0.0?type=p2-installable-unit",
582
+ "pkg:maven/example.group/org.tycho.demo.rootfiles.win@1.0.0-SNAPSHOT?type=p2-installable-unit"
583
+ ]
584
+ });
556
585
  });
557
586
 
558
587
  // Slow test
@@ -1358,8 +1387,8 @@ test("parsePkgLock", async () => {
1358
1387
  });
1359
1388
  parsedList = await parsePkgLock("./test/data/package-lock-v2.json");
1360
1389
  deps = parsedList.pkgList;
1361
- expect(deps.length).toEqual(1467);
1362
- expect(parsedList.dependenciesList.length).toEqual(1280);
1390
+ expect(deps.length).toEqual(5433);
1391
+ expect(parsedList.dependenciesList.length).toEqual(1616);
1363
1392
  expect(deps[0]).toEqual({
1364
1393
  "bom-ref": "pkg:npm/flink-dashboard@2.0.0",
1365
1394
  group: "",