@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 +3 -1
- package/bin/cdxgen.js +40 -9
- package/bin/evinse.js +5 -1
- package/lib/cli/index.js +61 -9
- package/lib/evinser/evinser.js +167 -38
- package/lib/evinser/scalasem.js +52 -0
- package/lib/helpers/envcontext.js +1 -0
- package/lib/helpers/utils.js +45 -8
- package/lib/helpers/utils.test.js +1 -1
- package/lib/managers/binary.js +82 -80
- package/lib/server/server.js +13 -10
- package/package.json +19 -14
- package/types/lib/cli/index.d.ts +2 -3
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/scalasem.d.ts +6 -0
- package/types/lib/evinser/scalasem.d.ts.map +1 -0
- package/types/lib/helpers/envcontext.d.ts +1 -0
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +10 -1
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +0 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/server/server.d.ts.map +1 -1
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.
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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 :=
|
|
2433
|
+
`'set ThisBuild / asciiGraphWidth := 800'${sbtExtraArgs} "dependencyTree / toFile ${dlFile} --force"`,
|
|
2420
2434
|
];
|
|
2421
2435
|
} else {
|
|
2422
2436
|
sbtArgs = [
|
|
2423
|
-
`'set asciiGraphWidth in ThisBuild :=
|
|
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(
|
|
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 } |
|
|
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
|
-
|
|
8241
|
+
// rethrow error as function is async and we should try to catch it in the caller
|
|
8242
|
+
throw error;
|
|
8191
8243
|
}
|
|
8192
8244
|
}
|