@cyclonedx/cdxgen 11.2.2 → 11.2.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
@@ -147,7 +147,8 @@ Options:
147
147
  --server-host Listen address [default: "127.0.0.1"]
148
148
  --server-port Listen port [default: "9090"]
149
149
  --install-deps Install dependencies automatically for some projects. Defaults to true but disabled for c
150
- ontainers and oci scans. Use --no-install-deps to disable this feature. [boolean]
150
+ ontainers and oci scans. Use --no-install-deps to disable this feature.
151
+ [boolean] [default: true]
151
152
  --validate Validate the generated SBOM using json schema. Defaults to true. Pass --no-validate to di
152
153
  sable. [boolean] [default: true]
153
154
  --evidence Generate SBOM with evidence for supported languages. [boolean] [default: false]
@@ -170,6 +171,7 @@ Options:
170
171
  luated against or attested to.
171
172
  [array] [choices: "asvs-5.0", "asvs-4.0.3", "bsimm-v13", "masvs-2.0.0", "nist_ssdf-1.1", "pcissc-secure-slc-1.1", "scv
172
173
  s-1.0.0", "ssaf-DRAFT-2023-11"]
174
+ --json-pretty Pretty-print the generated BOM json. [boolean] [default: false]
173
175
  --min-confidence Minimum confidence needed for the identity of a component from 0 - 1, where 1 is 100% con
174
176
  fidence. [number] [default: 0]
175
177
  --technique Analysis technique to use
package/bin/cdxgen.js CHANGED
@@ -24,6 +24,7 @@ import {
24
24
  import { thoughtEnd, thoughtLog } from "../lib/helpers/logger.js";
25
25
  import {
26
26
  ATOM_DB,
27
+ DEBUG_MODE,
27
28
  dirNameStr,
28
29
  getTmpDir,
29
30
  isMac,
@@ -221,6 +222,7 @@ const args = yargs(hideBin(process.argv))
221
222
  description: "CycloneDX Specification version to use. Defaults to 1.6",
222
223
  default: 1.6,
223
224
  type: "number",
225
+ choices: [1.4, 1.5, 1.6],
224
226
  })
225
227
  .option("filter", {
226
228
  description:
@@ -303,6 +305,11 @@ const args = yargs(hideBin(process.argv))
303
305
  description:
304
306
  "Do not show the donation banner. Set this attribute if you are an active sponsor for OWASP CycloneDX.",
305
307
  })
308
+ .option("json-pretty", {
309
+ type: "boolean",
310
+ default: DEBUG_MODE,
311
+ description: "Pretty-print the generated BOM json.",
312
+ })
306
313
  .option("feature-flags", {
307
314
  description: "Experimental feature flags to enable. Advanced users only.",
308
315
  hidden: true,
@@ -442,11 +449,23 @@ if (!options.projectType) {
442
449
  "Ok, the user wants me to identify all the project types and generate a consolidated BOM document.",
443
450
  );
444
451
  }
445
- if (process.argv[1].includes("cbom")) {
446
- thoughtLog(
447
- "Ok, the user wants to generate Cryptographic Bill-of-Materials (CBOM).",
448
- );
449
- options.includeCrypto = true;
452
+ // Handle dedicated cbom and saasbom commands
453
+ if (["cbom", "saasbom"].includes(process.argv[1])) {
454
+ if (process.argv[1].includes("cbom")) {
455
+ thoughtLog(
456
+ "Ok, the user wants to generate Cryptographic Bill-of-Materials (CBOM).",
457
+ );
458
+ options.includeCrypto = true;
459
+ } else if (process.argv[1].includes("saasbom")) {
460
+ thoughtLog(
461
+ "Ok, the user wants to generate a Software as a Service Bill-of-Materials (SaaSBOM). I should carefully collect the services, endpoints, and data flows.",
462
+ );
463
+ if (process.env?.CDXGEN_IN_CONTAINER !== "true") {
464
+ thoughtLog(
465
+ "Wait, I'm not running in a container. This means the chances of successfully collecting this inventory are quite low. Perhaps this is an advanced user who has set up atom and atom-tools already 🤔?",
466
+ );
467
+ }
468
+ }
450
469
  options.evidence = true;
451
470
  options.specVersion = 1.6;
452
471
  options.deep = true;
@@ -693,7 +712,7 @@ const checkPermissions = (filePath, options) => {
693
712
  "usages-slices-file",
694
713
  "reachables-slices-file",
695
714
  ];
696
- if (options?.type?.includes("swift")) {
715
+ if (options?.type?.includes("swift") || options?.type?.includes("scala")) {
697
716
  slicesFilesKeys.push("semantics-slices-file");
698
717
  }
699
718
  for (const sf of slicesFilesKeys) {
@@ -798,7 +817,11 @@ const checkPermissions = (filePath, options) => {
798
817
  fs.writeFileSync(jsonFile, bomNSData.bomJson);
799
818
  jsonPayload = bomNSData.bomJson;
800
819
  } else {
801
- jsonPayload = JSON.stringify(bomNSData.bomJson, null, null);
820
+ jsonPayload = JSON.stringify(
821
+ bomNSData.bomJson,
822
+ null,
823
+ options.jsonPretty ? 2 : null,
824
+ );
802
825
  fs.writeFileSync(jsonFile, jsonPayload);
803
826
  if (jsonFile.endsWith("bom.json")) {
804
827
  thoughtLog(
@@ -900,7 +923,11 @@ const checkPermissions = (filePath, options) => {
900
923
  bomJsonUnsignedObj.signature = signatureBlock;
901
924
  fs.writeFileSync(
902
925
  jsonFile,
903
- JSON.stringify(bomJsonUnsignedObj, null, null),
926
+ JSON.stringify(
927
+ bomJsonUnsignedObj,
928
+ null,
929
+ options.jsonPretty ? 2 : null,
930
+ ),
904
931
  );
905
932
  thoughtLog(`Signing the BOM file "${jsonFile}".`);
906
933
  if (publicKeyFile) {
@@ -937,7 +964,9 @@ const checkPermissions = (filePath, options) => {
937
964
  }
938
965
  } else if (!options.print) {
939
966
  if (bomNSData.bomJson) {
940
- console.log(JSON.stringify(bomNSData.bomJson, null, 2));
967
+ console.log(
968
+ JSON.stringify(bomNSData.bomJson, null, options.jsonPretty ? 2 : null),
969
+ );
941
970
  } else {
942
971
  console.log("Unable to produce BOM for", filePath);
943
972
  console.log("Try running the command with -t <type> or -r argument");
@@ -967,6 +996,7 @@ const checkPermissions = (filePath, options) => {
967
996
  includeCrypto: options.includeCrypto,
968
997
  specVersion: options.specVersion,
969
998
  profile: options.profile,
999
+ jsonPretty: options.jsonPretty,
970
1000
  };
971
1001
  const dbObjMap = await evinserModule.prepareDB(evinseOptions);
972
1002
  if (dbObjMap) {
@@ -1003,6 +1033,7 @@ const checkPermissions = (filePath, options) => {
1003
1033
  await submitBom(options, bomNSData.bomJson);
1004
1034
  } catch (err) {
1005
1035
  console.log(err);
1036
+ process.exit(1);
1006
1037
  }
1007
1038
  }
1008
1039
  // Protobuf serialization
package/bin/evinse.js CHANGED
@@ -73,6 +73,7 @@ const args = yargs(hideBin(process.argv))
73
73
  "swift",
74
74
  "ios",
75
75
  "ruby",
76
+ "scala",
76
77
  ],
77
78
  })
78
79
  .option("db-path", {
@@ -127,7 +128,10 @@ const args = yargs(hideBin(process.argv))
127
128
  .option("semantics-slices-file", {
128
129
  description: "Use an existing semantics slices file.",
129
130
  default: "semantics.slices.json",
130
- hidden: true,
131
+ })
132
+ .option("openapi-spec-file", {
133
+ description: "Use an existing openapi specification file (SaaSBOM).",
134
+ default: "openapi.json",
131
135
  })
132
136
  .option("print", {
133
137
  alias: "p",
package/lib/cli/index.js CHANGED
@@ -1542,7 +1542,7 @@ export async function createJavaBom(path, options) {
1542
1542
  let mvnArgs;
1543
1543
  if (isQuarkus) {
1544
1544
  thoughtLog(
1545
- "This appears to be a quarkus project. Let's use the right maven plugin.",
1545
+ "This appears to be a Quarkus project. Let's use the right Maven plugin.",
1546
1546
  );
1547
1547
  // disable analytics. See: https://quarkus.io/usage/
1548
1548
  mvnArgs = [
@@ -1550,6 +1550,11 @@ export async function createJavaBom(path, options) {
1550
1550
  "quarkus:dependency-sbom",
1551
1551
  "-Dquarkus.analytics.disabled=true",
1552
1552
  ];
1553
+ if (options.specVersion) {
1554
+ mvnArgs = mvnArgs.concat(
1555
+ `-Dquarkus.dependency.sbom.schema-version=${options.specVersion}`,
1556
+ );
1557
+ }
1553
1558
  } else {
1554
1559
  const cdxMavenPlugin =
1555
1560
  process.env.CDX_MAVEN_PLUGIN ||
@@ -1755,7 +1760,7 @@ export async function createJavaBom(path, options) {
1755
1760
  );
1756
1761
  } else {
1757
1762
  console.log(
1758
- "1. Java version requirement: cdxgen container image bundles Java 23 with maven 3.9 which might be incompatible. Try running cdxgen with the custom JDK11-based image `ghcr.io/cyclonedx/cdxgen-java11:v11`.",
1763
+ "1. Java version requirement: cdxgen container image bundles Java 24 with maven 3.9 which might be incompatible. Try running cdxgen with the custom JDK11-based image `ghcr.io/cyclonedx/cdxgen-java11:v11`.",
1759
1764
  );
1760
1765
  }
1761
1766
  console.log(
@@ -2347,7 +2352,8 @@ export async function createJavaBom(path, options) {
2347
2352
  `${options.multiProject ? "**/" : ""}build.sbt.lock`,
2348
2353
  options,
2349
2354
  );
2350
-
2355
+ const tempCacheDir = mkdtempSync(join(getTmpDir(), "sbt-cache-"));
2356
+ safeMkdirSync(tempCacheDir, { recursive: true });
2351
2357
  if (
2352
2358
  sbtProjects?.length &&
2353
2359
  isPackageManagerAllowed("sbt", ["bazel", "maven", "gradle"], options)
@@ -2402,6 +2408,14 @@ export async function createJavaBom(path, options) {
2402
2408
  }
2403
2409
  }
2404
2410
  writeFileSync(tempSbtPlugins, sbtPluginDefinition);
2411
+ let sbtExtraArgs = "";
2412
+ const env = { ...process.env };
2413
+ // We need to collect the jars from the cache
2414
+ if (options.deep) {
2415
+ sbtExtraArgs = " updateClassifiers";
2416
+ env["COURSIER_CACHE"] = tempCacheDir;
2417
+ env["SBT_IVY_HOME"] = tempCacheDir;
2418
+ }
2405
2419
  for (const i in sbtProjects) {
2406
2420
  const basePath = sbtProjects[i];
2407
2421
  const dlFile = join(tempDir, `dl-${i}.tmp`);
@@ -2416,11 +2430,11 @@ export async function createJavaBom(path, options) {
2416
2430
  // write to the existing plugins file
2417
2431
  if (useSlashSyntax) {
2418
2432
  sbtArgs = [
2419
- `'set ThisBuild / asciiGraphWidth := 400' "dependencyTree / toFile ${dlFile} --force"`,
2433
+ `'set ThisBuild / asciiGraphWidth := 800'${sbtExtraArgs} "dependencyTree / toFile ${dlFile} --force"`,
2420
2434
  ];
2421
2435
  } else {
2422
2436
  sbtArgs = [
2423
- `'set asciiGraphWidth in ThisBuild := 400' "dependencyTree::toFile ${dlFile} --force"`,
2437
+ `'set asciiGraphWidth in ThisBuild := 800'${sbtExtraArgs} "dependencyTree::toFile ${dlFile} --force"`,
2424
2438
  ];
2425
2439
  }
2426
2440
  pluginFile = addPlugin(basePath, sbtPluginDefinition);
@@ -2441,6 +2455,7 @@ export async function createJavaBom(path, options) {
2441
2455
  encoding: "utf-8",
2442
2456
  timeout: TIMEOUT_MS,
2443
2457
  maxBuffer: MAX_BUFFER,
2458
+ env,
2444
2459
  });
2445
2460
  if (result.status !== 0 || result.error) {
2446
2461
  console.error(result.stdout, result.stderr);
@@ -2492,12 +2507,36 @@ export async function createJavaBom(path, options) {
2492
2507
  }
2493
2508
  // Should we attempt to resolve class names
2494
2509
  if (options.resolveClass || options.deep) {
2495
- const tmpjarNSMapping = await collectJarNS(SBT_CACHE_DIR);
2510
+ const tmpjarNSMapping = await collectJarNS(tempCacheDir);
2496
2511
  if (tmpjarNSMapping && Object.keys(tmpjarNSMapping).length) {
2497
2512
  jarNSMapping = { ...jarNSMapping, ...tmpjarNSMapping };
2498
2513
  }
2514
+ // sbt can store jars in the target directory
2515
+ const jarNSData = await createJarBom(path, options);
2516
+ if (jarNSData?.bomJson?.components) {
2517
+ pkgList = pkgList.concat(jarNSData?.bomJson?.components);
2518
+ const targetJarNSMapping = {};
2519
+ for (const p of jarNSData.bomJson.components) {
2520
+ if (!p?.purl || !p?.properties?.length) {
2521
+ continue;
2522
+ }
2523
+ const nsProp = p.properties.filter(
2524
+ (prop) => prop.name === "Namespaces",
2525
+ );
2526
+ if (nsProp.length) {
2527
+ targetJarNSMapping[p.purl] = nsProp[0].value;
2528
+ }
2529
+ }
2530
+ jarNSMapping = { ...jarNSMapping, ...targetJarNSMapping };
2531
+ }
2499
2532
  }
2500
2533
  }
2534
+ if (tempCacheDir?.startsWith(getTmpDir())) {
2535
+ rmSync(tempCacheDir, {
2536
+ recursive: true,
2537
+ force: true,
2538
+ });
2539
+ }
2501
2540
  pkgList = trimComponents(pkgList);
2502
2541
  pkgList = await getMvnMetadata(pkgList, jarNSMapping, options.deep);
2503
2542
  return buildBomNSData(options, pkgList, "maven", {
@@ -6669,6 +6708,16 @@ export function dedupeBom(options, components, parentComponent, dependencies) {
6669
6708
  dependencies = [];
6670
6709
  }
6671
6710
  components = trimComponents(components);
6711
+ // Let's apply some common tweaks
6712
+ // Convert evidence.identity section to an object for 1.5
6713
+ if (options.specVersion === 1.5) {
6714
+ for (const comp of components) {
6715
+ if (comp?.evidence?.identity && Array.isArray(comp.evidence.identity)) {
6716
+ comp.evidence.identity = comp.evidence.identity[0];
6717
+ delete comp.evidence.identity.concludedValue;
6718
+ }
6719
+ }
6720
+ }
6672
6721
  if (DEBUG_MODE) {
6673
6722
  console.log(
6674
6723
  `Obtained ${components.length} components and ${dependencies.length} dependencies after dedupe.`,
@@ -6723,7 +6772,7 @@ export async function createMultiXBom(pathList, options) {
6723
6772
  binPaths,
6724
6773
  executables,
6725
6774
  sharedLibs,
6726
- } = getOSPackages(
6775
+ } = await getOSPackages(
6727
6776
  options.allLayersExplodedDir,
6728
6777
  options.exportData?.inspectData?.Config,
6729
6778
  );
@@ -8069,7 +8118,8 @@ export async function createBom(path, options) {
8069
8118
  *
8070
8119
  * @param {Object} args CLI args
8071
8120
  * @param {Object} bomContents BOM Json
8072
- * @return {Promise<{ token: string } | { errors: string[] } | undefined>} a promise with a token (if request was successful), a body with errors (if request failed) or undefined (in case of invalid arguments)
8121
+ * @return {Promise<{ token: string } | undefined>} a promise with a token (if request was successful) or undefined (in case of invalid arguments)
8122
+ * @throws {Error} if the request fails
8073
8123
  */
8074
8124
  export async function submitBom(args, bomContents) {
8075
8125
  const serverUrl = `${args.serverUrl.replace(/\/$/, "")}/api/v1/bom`;
@@ -8102,6 +8152,7 @@ export async function submitBom(args, bomContents) {
8102
8152
  console.log(
8103
8153
  "projectId, projectName and projectVersion, or all three must be provided.",
8104
8154
  );
8155
+ args.failOnError && process.exit(1);
8105
8156
  return;
8106
8157
  }
8107
8158
  if (
@@ -8187,6 +8238,7 @@ export async function submitBom(args, bomContents) {
8187
8238
  console.log("Unable to submit the SBOM to the Dependency-Track server");
8188
8239
  }
8189
8240
  }
8190
- return error.response?.body;
8241
+ // rethrow error as function is async and we should try to catch it in the caller
8242
+ throw error;
8191
8243
  }
8192
8244
  }