@cyclonedx/cdxgen 9.8.1 → 9.8.3

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
@@ -11,6 +11,7 @@ NOTE:
11
11
  CycloneDX 1.5 specification is new and unsupported by many downstream tools. Use version 8.6.0 for 1.4 compatibility or pass the argument `--spec-version 1.4`.
12
12
 
13
13
  ## Why cdxgen?
14
+
14
15
  A typical application might have several repos, components, and libraries. Traditional techniques to generate a single SBoM per language or package manifest do not work in enterprise environments. So we built cdxgen - the universal polyglot SBoM generator!
15
16
 
16
17
  <img src="./docs/why-cdxgen.jpg" alt="why cdxgen" width="256">
@@ -69,6 +70,7 @@ Footnotes:
69
70
  <img src="./docs/cdxgen-tree.jpg" alt="cdxgen tree" width="256">
70
71
 
71
72
  ### Automatic usage detection
73
+
72
74
  For node.js projects, lock files are parsed initially, so the SBoM would include all dependencies, including dev ones. An AST parser powered by babel-parser is then used to detect packages that are imported and used by non-test code. Such imported packages would automatically set their scope property to `required` in the resulting SBoM. You can turn off this analysis by passing the argument `--no-babel`. Scope property would then be set based on the `dev` attribute in the lock file.
73
75
 
74
76
  This attribute can be later used for various purposes. For example, [dep-scan](https://github.com/cyclonedx/dep-scan) uses this attribute to prioritize vulnerabilities. Unfortunately, tools such as dependency track, do not include this feature and might over-report the CVEs.
package/index.js CHANGED
@@ -332,6 +332,17 @@ const componentToSimpleFullName = (comp) => {
332
332
  return fullName;
333
333
  };
334
334
 
335
+ // Remove unwanted properties from parent component
336
+ const cleanParentComponent = (comp) => {
337
+ delete comp.evidence;
338
+ delete comp._integrity;
339
+ delete comp.license;
340
+ delete comp.qualifiers;
341
+ delete comp.repository;
342
+ delete comp.homepage;
343
+ return comp;
344
+ };
345
+
335
346
  /**
336
347
  * Function to create metadata block
337
348
  *
@@ -358,10 +369,7 @@ function addMetadata(parentComponent = {}, format = "xml", options = {}) {
358
369
  }
359
370
  if (parentComponent && Object.keys(parentComponent).length) {
360
371
  if (parentComponent) {
361
- delete parentComponent.evidence;
362
- delete parentComponent._integrity;
363
- delete parentComponent.license;
364
- delete parentComponent.qualifiers;
372
+ cleanParentComponent(parentComponent);
365
373
  if (!parentComponent["purl"] && parentComponent["bom-ref"]) {
366
374
  parentComponent["purl"] = parentComponent["bom-ref"];
367
375
  }
@@ -371,10 +379,7 @@ function addMetadata(parentComponent = {}, format = "xml", options = {}) {
371
379
  const subComponents = [];
372
380
  const addedSubComponents = {};
373
381
  for (const comp of parentComponent.components) {
374
- delete comp.evidence;
375
- delete comp._integrity;
376
- delete comp.license;
377
- delete comp.qualifiers;
382
+ cleanParentComponent(comp);
378
383
  if (comp.name && comp.type) {
379
384
  let fullName = componentToSimpleFullName(comp);
380
385
  // Fixes #479
@@ -1267,7 +1272,7 @@ export const createJavaBom = async (path, options) => {
1267
1272
  );
1268
1273
  } else {
1269
1274
  console.log(
1270
- "1. Java version requirement: cdxgen container image bundles Java 21 with maven 3.9 which might be incompatible."
1275
+ "1. Java version requirement: cdxgen container image bundles Java 20 with maven 3.9 which might be incompatible."
1271
1276
  );
1272
1277
  }
1273
1278
  console.log(
@@ -1948,7 +1953,7 @@ export const createNodejsBom = async (path, options) => {
1948
1953
  // Determine the parent component
1949
1954
  const packageJsonF = join(basePath, "package.json");
1950
1955
  if (existsSync(packageJsonF)) {
1951
- const pcs = await parsePkgJson(packageJsonF);
1956
+ const pcs = await parsePkgJson(packageJsonF, true);
1952
1957
  if (pcs.length) {
1953
1958
  parentComponent = pcs[0];
1954
1959
  parentComponent.type = "application";
@@ -2096,7 +2101,7 @@ export const createNodejsBom = async (path, options) => {
2096
2101
  // Determine the parent component
2097
2102
  const packageJsonF = join(basePath, "package.json");
2098
2103
  if (existsSync(packageJsonF)) {
2099
- const pcs = await parsePkgJson(packageJsonF);
2104
+ const pcs = await parsePkgJson(packageJsonF, true);
2100
2105
  if (pcs.length) {
2101
2106
  const tmpParentComponent = pcs[0];
2102
2107
  tmpParentComponent.type = "application";
@@ -2195,7 +2200,7 @@ export const createNodejsBom = async (path, options) => {
2195
2200
  }
2196
2201
  if (!parentComponent || !Object.keys(parentComponent).length) {
2197
2202
  if (existsSync(join(path, "package.json"))) {
2198
- const pcs = await parsePkgJson(join(path, "package.json"));
2203
+ const pcs = await parsePkgJson(join(path, "package.json"), true);
2199
2204
  if (pcs.length) {
2200
2205
  parentComponent = pcs[0];
2201
2206
  parentComponent.type = "application";
@@ -2659,7 +2664,12 @@ export const createGoBom = async (path, options) => {
2659
2664
  );
2660
2665
  if (result.status !== 0 || result.error) {
2661
2666
  shouldManuallyParse = true;
2662
- console.error(result.stdout, result.stderr);
2667
+ if (result.stdout) {
2668
+ console.log(result.stdout);
2669
+ }
2670
+ if (result.stderr) {
2671
+ console.log(result.stderr);
2672
+ }
2663
2673
  options.failOnError && process.exit(1);
2664
2674
  }
2665
2675
  const stdout = result.stdout;
@@ -2702,7 +2712,12 @@ export const createGoBom = async (path, options) => {
2702
2712
  );
2703
2713
  if (mresult.status !== 0 || mresult.error) {
2704
2714
  if (DEBUG_MODE) {
2705
- console.log(mresult.stdout, mresult.stderr);
2715
+ if (mresult.stdout) {
2716
+ console.log(mresult.stdout);
2717
+ }
2718
+ if (mresult.stderr) {
2719
+ console.log(mresult.stderr);
2720
+ }
2706
2721
  }
2707
2722
  circuitBreak = true;
2708
2723
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "9.8.1",
3
+ "version": "9.8.3",
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>",
@@ -81,7 +81,7 @@
81
81
  "yargs": "^17.7.2"
82
82
  },
83
83
  "optionalDependencies": {
84
- "@appthreat/atom": "^1.2.0",
84
+ "@appthreat/atom": "^1.2.1",
85
85
  "@cyclonedx/cdxgen-plugins-bin": "^1.4.0",
86
86
  "@cyclonedx/cdxgen-plugins-bin-arm64": "^1.4.0",
87
87
  "@cyclonedx/cdxgen-plugins-bin-ppc64": "^1.4.0",
package/utils.js CHANGED
@@ -50,6 +50,7 @@ if (!url.startsWith("file://")) {
50
50
  url = new URL(`file://${import.meta.url}`).toString();
51
51
  }
52
52
  const dirNameStr = import.meta ? dirname(fileURLToPath(url)) : __dirname;
53
+ const isWin = platform() === "win32";
53
54
 
54
55
  const licenseMapping = JSON.parse(
55
56
  readFileSync(join(dirNameStr, "data", "lic-mapping.json"))
@@ -345,8 +346,9 @@ export const getNpmMetadata = async function (pkgList) {
345
346
  * Parse nodejs package json file
346
347
  *
347
348
  * @param {string} pkgJsonFile package.json file
349
+ * @param {boolean} simple Return a simpler representation of the component by skipping extended attributes and license fetch.
348
350
  */
349
- export const parsePkgJson = async (pkgJsonFile) => {
351
+ export const parsePkgJson = async (pkgJsonFile, simple = false) => {
350
352
  const pkgList = [];
351
353
  if (existsSync(pkgJsonFile)) {
352
354
  try {
@@ -362,18 +364,20 @@ export const parsePkgJson = async (pkgJsonFile) => {
362
364
  null,
363
365
  null
364
366
  ).toString();
365
- pkgList.push({
367
+ const apkg = {
366
368
  name,
367
369
  group,
368
370
  version: pkgData.version,
369
- "bom-ref": decodeURIComponent(purl),
370
- properties: [
371
+ "bom-ref": decodeURIComponent(purl)
372
+ };
373
+ if (!simple) {
374
+ apkg.properties = [
371
375
  {
372
376
  name: "SrcFile",
373
377
  value: pkgJsonFile
374
378
  }
375
- ],
376
- evidence: {
379
+ ];
380
+ apkg.evidence = {
377
381
  identity: {
378
382
  field: "purl",
379
383
  confidence: 1,
@@ -385,13 +389,14 @@ export const parsePkgJson = async (pkgJsonFile) => {
385
389
  }
386
390
  ]
387
391
  }
388
- }
389
- });
392
+ };
393
+ }
394
+ pkgList.push(apkg);
390
395
  } catch (err) {
391
396
  // continue regardless of error
392
397
  }
393
398
  }
394
- if (FETCH_LICENSE && pkgList && pkgList.length) {
399
+ if (!simple && FETCH_LICENSE && pkgList && pkgList.length) {
395
400
  if (DEBUG_MODE) {
396
401
  console.log(
397
402
  `About to fetch license information for ${pkgList.length} packages in parsePkgJson`
@@ -509,6 +514,11 @@ export const parsePkgLock = async (pkgLockFile, options = {}) => {
509
514
  "bom-ref": purlString
510
515
  };
511
516
  }
517
+ const packageLicense = node.package.license;
518
+ if (packageLicense) {
519
+ // License will be overridden if FETCH_LICENSE is enabled
520
+ pkg.license = packageLicense;
521
+ }
512
522
  pkgList.push(pkg);
513
523
 
514
524
  // retrieve workspace node pkglists
@@ -1861,7 +1871,8 @@ export const executeGradleProperties = function (dir, rootPath, subProject) {
1861
1871
  );
1862
1872
  const result = spawnSync(gradleCmd, gradlePropertiesArgs, {
1863
1873
  cwd: dir,
1864
- encoding: "utf-8"
1874
+ encoding: "utf-8",
1875
+ shell: isWin
1865
1876
  });
1866
1877
  if (result.status !== 0 || result.error) {
1867
1878
  if (result.stderr) {
@@ -1870,7 +1881,7 @@ export const executeGradleProperties = function (dir, rootPath, subProject) {
1870
1881
  } else {
1871
1882
  console.error(result.stdout, result.stderr);
1872
1883
  console.log(
1873
- "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 21 with gradle 8 which might be incompatible."
1884
+ "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 20 with gradle 8 which might be incompatible."
1874
1885
  );
1875
1886
  }
1876
1887
  if (result.stderr.includes("not get unknown property")) {
@@ -2892,11 +2903,20 @@ export const getGoPkgLicense = async function (repoMetadata) {
2892
2903
  const licenseIds = licenses.split(", ");
2893
2904
  const licList = [];
2894
2905
  for (const id of licenseIds) {
2895
- const alicense = {
2896
- id: id
2897
- };
2898
- alicense["url"] = pkgUrlPrefix;
2899
- licList.push(alicense);
2906
+ if (id.trim().length) {
2907
+ const alicense = {};
2908
+ if (id.includes(" ")) {
2909
+ alicense.name = id
2910
+ .trim()
2911
+ .replace(/ {2}/g, "")
2912
+ .replace("\n", " ")
2913
+ .replace("\n", " OR ");
2914
+ } else {
2915
+ alicense.id = id.trim();
2916
+ }
2917
+ alicense["url"] = pkgUrlPrefix;
2918
+ licList.push(alicense);
2919
+ }
2900
2920
  }
2901
2921
  metadata_cache[pkgUrlPrefix] = licList;
2902
2922
  return licList;
@@ -5316,7 +5336,8 @@ export const collectMvnDependencies = function (
5316
5336
  );
5317
5337
  const result = spawnSync(mavenCmd, copyArgs, {
5318
5338
  cwd: basePath,
5319
- encoding: "utf-8"
5339
+ encoding: "utf-8",
5340
+ shell: isWin
5320
5341
  });
5321
5342
  if (result.status !== 0 || result.error) {
5322
5343
  console.error(result.stdout, result.stderr);
@@ -5395,10 +5416,24 @@ export const collectJarNS = function (jarPath, pomPathMap = {}) {
5395
5416
  console.log(
5396
5417
  `About to identify class names for all jars in the path ${jarPath}`
5397
5418
  );
5419
+ const env = {
5420
+ ...process.env
5421
+ };
5422
+ // jar command usually would not be available in the PATH for windows
5423
+ if (isWin && env.JAVA_HOME) {
5424
+ env.PATH = `${env.PATH || env.Path}${_delimiter}${join(
5425
+ env.JAVA_HOME,
5426
+ "bin"
5427
+ )}`;
5428
+ }
5429
+ let jarCommandAvailable = true;
5398
5430
  // Execute jar tvf to get class names
5399
5431
  const jarFiles = getAllFiles(jarPath, "**/*.jar");
5400
5432
  if (jarFiles && jarFiles.length) {
5401
5433
  for (const jf of jarFiles) {
5434
+ if (!jarCommandAvailable) {
5435
+ break;
5436
+ }
5402
5437
  const jarname = jf;
5403
5438
  let pomname =
5404
5439
  pomPathMap[basename(jf).replace(".jar", ".pom")] ||
@@ -5500,18 +5535,42 @@ export const collectJarNS = function (jarPath, pomPathMap = {}) {
5500
5535
  if (DEBUG_MODE) {
5501
5536
  console.log(`Executing 'jar tf ${jf}'`);
5502
5537
  }
5503
- const jarResult = spawnSync("jar", ["-tf", jf], { encoding: "utf-8" });
5538
+
5539
+ const jarResult = spawnSync("jar", ["-tf", jf], {
5540
+ encoding: "utf-8",
5541
+ shell: isWin,
5542
+ maxBuffer: 50 * 1024 * 1024,
5543
+ env
5544
+ });
5545
+ if (
5546
+ jarResult &&
5547
+ jarResult.stderr &&
5548
+ jarResult.stderr.includes(
5549
+ "is not recognized as an internal or external command"
5550
+ )
5551
+ ) {
5552
+ jarCommandAvailable = false;
5553
+ console.log(
5554
+ "jar command is not available in PATH. Ensure JDK >= 17 is installed and set the environment variables JAVA_HOME and PATH to the bin directory inside JAVA_HOME."
5555
+ );
5556
+ }
5504
5557
  const consolelines = (jarResult.stdout || "").split("\n");
5505
5558
  const nsList = consolelines
5506
5559
  .filter((l) => {
5507
5560
  return (
5508
- l.includes(".class") &&
5561
+ (l.includes(".class") ||
5562
+ l.includes(".java") ||
5563
+ l.includes(".kt")) &&
5509
5564
  !l.includes("-INF") &&
5510
5565
  !l.includes("module-info")
5511
5566
  );
5512
5567
  })
5513
5568
  .map((e) => {
5514
- return e.replace(".class", "").replace(/\/$/, "").replace(/\//g, ".");
5569
+ return e
5570
+ .replace("\r", "")
5571
+ .replace(/.(class|java|kt)/, "")
5572
+ .replace(/\/$/, "")
5573
+ .replace(/\//g, ".");
5515
5574
  });
5516
5575
  jarNSMapping[purl || jf] = {
5517
5576
  jarFile: jf,
@@ -5678,10 +5737,22 @@ export const extractJarArchive = function (jarFile, tempDir) {
5678
5737
  // Only copy if the file doesn't exist
5679
5738
  copyFileSync(jarFile, join(tempDir, fname), constants.COPYFILE_FICLONE);
5680
5739
  }
5740
+ const env = {
5741
+ ...process.env
5742
+ };
5743
+ // jar command usually would not be available in the PATH for windows
5744
+ if (isWin && env.JAVA_HOME) {
5745
+ env.PATH = `${env.PATH || env.Path}${_delimiter}${join(
5746
+ env.JAVA_HOME,
5747
+ "bin"
5748
+ )}`;
5749
+ }
5681
5750
  if (jarFile.endsWith(".war") || jarFile.endsWith(".hpi")) {
5682
5751
  const jarResult = spawnSync("jar", ["-xf", join(tempDir, fname)], {
5683
5752
  encoding: "utf-8",
5684
- cwd: tempDir
5753
+ cwd: tempDir,
5754
+ shell: isWin,
5755
+ env
5685
5756
  });
5686
5757
  if (jarResult.status !== 0) {
5687
5758
  console.error(jarResult.stdout, jarResult.stderr);
@@ -5718,7 +5789,9 @@ export const extractJarArchive = function (jarFile, tempDir) {
5718
5789
  } else {
5719
5790
  jarResult = spawnSync("jar", ["-xf", jf], {
5720
5791
  encoding: "utf-8",
5721
- cwd: tempDir
5792
+ cwd: tempDir,
5793
+ shell: isWin,
5794
+ env
5722
5795
  });
5723
5796
  }
5724
5797
  if (jarResult.status !== 0) {
@@ -6058,7 +6131,8 @@ export const getMavenCommand = (srcPath, rootPath) => {
6058
6131
  const result = spawnSync(mavenCmd, ["wrapper:wrapper"], {
6059
6132
  encoding: "utf-8",
6060
6133
  cwd: rootPath,
6061
- timeout: TIMEOUT_MS
6134
+ timeout: TIMEOUT_MS,
6135
+ shell: isWin
6062
6136
  });
6063
6137
  if (!result.error) {
6064
6138
  isWrapperReady = true;
@@ -6121,16 +6195,26 @@ export const executeAtom = (src, args) => {
6121
6195
  const env = {
6122
6196
  ...process.env
6123
6197
  };
6124
- env.PATH = `${env.PATH}${_delimiter}${join(
6125
- dirNameStr,
6126
- "node_modules",
6127
- ".bin"
6128
- )}`;
6198
+
6199
+ if (isWin) {
6200
+ env.PATH = `${env.PATH || env.Path}${_delimiter}${join(
6201
+ dirNameStr,
6202
+ "node_modules",
6203
+ ".bin"
6204
+ )}`;
6205
+ } else {
6206
+ env.PATH = `${env.PATH}${_delimiter}${join(
6207
+ dirNameStr,
6208
+ "node_modules",
6209
+ ".bin"
6210
+ )}`;
6211
+ }
6129
6212
  const result = spawnSync(ATOM_BIN, args, {
6130
6213
  cwd,
6131
6214
  encoding: "utf-8",
6132
6215
  timeout: TIMEOUT_MS,
6133
- detached: true,
6216
+ detached: !isWin && !process.env.CI,
6217
+ shell: isWin,
6134
6218
  env
6135
6219
  });
6136
6220
  if (result.stderr) {
@@ -6288,7 +6372,8 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
6288
6372
  !reqOrSetupFile.endsWith("poetry.lock")
6289
6373
  ) {
6290
6374
  result = spawnSync(PYTHON_CMD, ["-m", "venv", tempVenvDir], {
6291
- encoding: "utf-8"
6375
+ encoding: "utf-8",
6376
+ shell: isWin
6292
6377
  });
6293
6378
  if (result.status !== 0 || result.error) {
6294
6379
  if (DEBUG_MODE) {
@@ -6329,14 +6414,16 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
6329
6414
  result = spawnSync("poetry", poetryConfigArgs, {
6330
6415
  cwd: basePath,
6331
6416
  encoding: "utf-8",
6332
- timeout: TIMEOUT_MS
6417
+ timeout: TIMEOUT_MS,
6418
+ shell: isWin
6333
6419
  });
6334
6420
  let poetryInstallArgs = ["install", "-n", "--no-root"];
6335
6421
  // Attempt to perform poetry install
6336
6422
  result = spawnSync("poetry", poetryInstallArgs, {
6337
6423
  cwd: basePath,
6338
6424
  encoding: "utf-8",
6339
- timeout: TIMEOUT_MS
6425
+ timeout: TIMEOUT_MS,
6426
+ shell: isWin
6340
6427
  });
6341
6428
  if (result.status !== 0 || result.error) {
6342
6429
  if (result.stderr && result.stderr.includes("No module named poetry")) {
@@ -6346,6 +6433,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
6346
6433
  cwd: basePath,
6347
6434
  encoding: "utf-8",
6348
6435
  timeout: TIMEOUT_MS,
6436
+ shell: isWin,
6349
6437
  env
6350
6438
  });
6351
6439
  if (result.status !== 0 || result.error) {
@@ -6374,6 +6462,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
6374
6462
  cwd: basePath,
6375
6463
  encoding: "utf-8",
6376
6464
  timeout: TIMEOUT_MS,
6465
+ shell: isWin,
6377
6466
  env
6378
6467
  });
6379
6468
  tempVenvDir = result.stdout.replaceAll(/[\r\n]+/g, "");
@@ -6407,6 +6496,7 @@ export const getPipFrozenTree = (basePath, reqOrSetupFile, tempVenvDir) => {
6407
6496
  cwd: basePath,
6408
6497
  encoding: "utf-8",
6409
6498
  timeout: TIMEOUT_MS,
6499
+ shell: isWin,
6410
6500
  env
6411
6501
  });
6412
6502
  if (result.status !== 0 || result.error) {
package/utils.test.js CHANGED
@@ -1143,7 +1143,7 @@ test("parse github actions workflow data", async () => {
1143
1143
  dep_list = parseGitHubWorkflowData(
1144
1144
  readFileSync("./.github/workflows/repotests.yml", { encoding: "utf-8" })
1145
1145
  );
1146
- expect(dep_list.length).toEqual(5);
1146
+ expect(dep_list.length).toEqual(6);
1147
1147
  expect(dep_list[0]).toEqual({
1148
1148
  group: "actions",
1149
1149
  name: "checkout",
@@ -1533,11 +1533,13 @@ test("parsePkgLock v2", async () => {
1533
1533
  expect(deps[1]._integrity).toEqual(
1534
1534
  "sha512-x9yaMvEh5BEaZKeVQC4vp3l+QoFj3BXcd4aYfuKSzIIyihjdVARAadYy3SMNIz0WCCdS2vB9JL/U6GQk5PaxQw=="
1535
1535
  );
1536
+ expect(deps[1].license).toEqual("Apache-2.0");
1536
1537
  expect(deps[0]).toEqual({
1537
1538
  "bom-ref": "pkg:npm/shopify-theme-tailwindcss@2.2.1",
1538
1539
  author: "Wessel van Ree <hello@wesselvanree.com>",
1539
1540
  group: "",
1540
1541
  name: "shopify-theme-tailwindcss",
1542
+ license: "MIT",
1541
1543
  type: "application",
1542
1544
  version: "2.2.1"
1543
1545
  });
@@ -1568,6 +1570,7 @@ test("parsePkgLock v2 workspace", async () => {
1568
1570
  let pkgs = parsedList.pkgList;
1569
1571
  let deps = parsedList.dependenciesList;
1570
1572
  expect(pkgs.length).toEqual(1032);
1573
+ expect(pkgs[0].license).toEqual("MIT");
1571
1574
  let hasAppWorkspacePkg = pkgs.some(
1572
1575
  (obj) => obj["bom-ref"] === "pkg:npm/app@0.0.0"
1573
1576
  );
@@ -1605,6 +1608,7 @@ test("parsePkgLock v3", async () => {
1605
1608
  "bom-ref": "pkg:npm/cdxgen@latest",
1606
1609
  group: "",
1607
1610
  author: "",
1611
+ license: "ISC",
1608
1612
  name: "cdxgen",
1609
1613
  type: "application",
1610
1614
  version: "latest"