@cyclonedx/cdxgen 12.2.1 → 12.3.0
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 +239 -90
- package/bin/audit.js +191 -0
- package/bin/cdxgen.js +513 -167
- package/bin/convert.js +99 -0
- package/bin/evinse.js +23 -0
- package/bin/repl.js +339 -8
- package/bin/sign.js +8 -0
- package/bin/validate.js +8 -0
- package/bin/verify.js +8 -0
- package/data/container-knowledge-index.json +125 -0
- package/data/gtfobins-index.json +6296 -0
- package/data/lolbas-index.json +150 -0
- package/data/queries-darwin.json +63 -3
- package/data/queries-win.json +45 -3
- package/data/queries.json +74 -2
- package/data/rules/chrome-extensions.yaml +240 -0
- package/data/rules/ci-permissions.yaml +478 -18
- package/data/rules/container-risk.yaml +270 -0
- package/data/rules/obom-runtime.yaml +891 -0
- package/data/rules/package-integrity.yaml +49 -0
- package/data/spdx-export.schema.json +6794 -0
- package/data/spdx-model-v3.0.1.jsonld +15999 -0
- package/lib/audit/index.js +1924 -0
- package/lib/audit/index.poku.js +1488 -0
- package/lib/audit/progress.js +137 -0
- package/lib/audit/progress.poku.js +188 -0
- package/lib/audit/reporters.js +618 -0
- package/lib/audit/scoring.js +310 -0
- package/lib/audit/scoring.poku.js +341 -0
- package/lib/audit/targets.js +260 -0
- package/lib/audit/targets.poku.js +331 -0
- package/lib/cli/index.js +154 -11
- package/lib/cli/index.poku.js +251 -0
- package/lib/helpers/analyzer.js +446 -2
- package/lib/helpers/analyzer.poku.js +72 -1
- package/lib/helpers/annotationFormatter.js +49 -0
- package/lib/helpers/annotationFormatter.poku.js +44 -0
- package/lib/helpers/bomUtils.js +36 -0
- package/lib/helpers/bomUtils.poku.js +51 -0
- package/lib/helpers/caxa.js +2 -2
- package/lib/helpers/chromextutils.js +1153 -0
- package/lib/helpers/chromextutils.poku.js +493 -0
- package/lib/helpers/ciParsers/githubActions.js +1632 -45
- package/lib/helpers/ciParsers/githubActions.poku.js +853 -1
- package/lib/helpers/containerRisk.js +186 -0
- package/lib/helpers/containerRisk.poku.js +52 -0
- package/lib/helpers/display.js +241 -59
- package/lib/helpers/display.poku.js +162 -2
- package/lib/helpers/exportUtils.js +123 -0
- package/lib/helpers/exportUtils.poku.js +60 -0
- package/lib/helpers/formulationParsers.js +69 -0
- package/lib/helpers/formulationParsers.poku.js +44 -0
- package/lib/helpers/gtfobins.js +189 -0
- package/lib/helpers/gtfobins.poku.js +49 -0
- package/lib/helpers/lolbas.js +267 -0
- package/lib/helpers/lolbas.poku.js +39 -0
- package/lib/helpers/osqueryTransform.js +84 -0
- package/lib/helpers/osqueryTransform.poku.js +49 -0
- package/lib/helpers/provenanceUtils.js +193 -0
- package/lib/helpers/provenanceUtils.poku.js +145 -0
- package/lib/helpers/pylockutils.js +281 -0
- package/lib/helpers/pylockutils.poku.js +48 -0
- package/lib/helpers/registryProvenance.js +793 -0
- package/lib/helpers/registryProvenance.poku.js +452 -0
- package/lib/helpers/source.js +1267 -0
- package/lib/helpers/source.poku.js +771 -0
- package/lib/helpers/spdxUtils.js +97 -0
- package/lib/helpers/spdxUtils.poku.js +70 -0
- package/lib/helpers/unicodeScan.js +147 -0
- package/lib/helpers/unicodeScan.poku.js +45 -0
- package/lib/helpers/utils.js +700 -128
- package/lib/helpers/utils.poku.js +877 -80
- package/lib/managers/binary.js +29 -5
- package/lib/managers/docker.js +179 -52
- package/lib/managers/docker.poku.js +327 -28
- package/lib/managers/oci.js +107 -23
- package/lib/managers/oci.poku.js +132 -0
- package/lib/server/openapi.yaml +17 -0
- package/lib/server/server.js +225 -336
- package/lib/server/server.poku.js +16 -10
- package/lib/stages/postgen/annotator.js +7 -0
- package/lib/stages/postgen/annotator.poku.js +40 -0
- package/lib/stages/postgen/auditBom.js +19 -3
- package/lib/stages/postgen/auditBom.poku.js +1729 -67
- package/lib/stages/postgen/postgen.js +40 -0
- package/lib/stages/postgen/postgen.poku.js +47 -0
- package/lib/stages/postgen/ruleEngine.js +80 -2
- package/lib/stages/postgen/spdxConverter.js +796 -0
- package/lib/stages/postgen/spdxConverter.poku.js +341 -0
- package/lib/validator/bomValidator.js +232 -0
- package/lib/validator/bomValidator.poku.js +70 -0
- package/lib/validator/complianceRules.js +70 -7
- package/lib/validator/complianceRules.poku.js +30 -0
- package/lib/validator/reporters/annotations.js +2 -2
- package/lib/validator/reporters/console.js +11 -0
- package/lib/validator/reporters.poku.js +13 -0
- package/package.json +10 -7
- package/types/bin/audit.d.ts +3 -0
- package/types/bin/audit.d.ts.map +1 -0
- package/types/bin/convert.d.ts +3 -0
- package/types/bin/convert.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +115 -0
- package/types/lib/audit/index.d.ts.map +1 -0
- package/types/lib/audit/progress.d.ts +27 -0
- package/types/lib/audit/progress.d.ts.map +1 -0
- package/types/lib/audit/reporters.d.ts +35 -0
- package/types/lib/audit/reporters.d.ts.map +1 -0
- package/types/lib/audit/scoring.d.ts +35 -0
- package/types/lib/audit/scoring.d.ts.map +1 -0
- package/types/lib/audit/targets.d.ts +63 -0
- package/types/lib/audit/targets.d.ts.map +1 -0
- package/types/lib/cli/index.d.ts +8 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +13 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/annotationFormatter.d.ts +23 -0
- package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
- package/types/lib/helpers/bomUtils.d.ts +5 -0
- package/types/lib/helpers/bomUtils.d.ts.map +1 -0
- package/types/lib/helpers/chromextutils.d.ts +97 -0
- package/types/lib/helpers/chromextutils.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +3 -8
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/containerRisk.d.ts +17 -0
- package/types/lib/helpers/containerRisk.d.ts.map +1 -0
- package/types/lib/helpers/display.d.ts +4 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/exportUtils.d.ts +40 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +17 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts +16 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -0
- package/types/lib/helpers/osqueryTransform.d.ts +7 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -0
- package/types/lib/helpers/provenanceUtils.d.ts +90 -0
- package/types/lib/helpers/provenanceUtils.d.ts.map +1 -0
- package/types/lib/helpers/pylockutils.d.ts +51 -0
- package/types/lib/helpers/pylockutils.d.ts.map +1 -0
- package/types/lib/helpers/registryProvenance.d.ts +17 -0
- package/types/lib/helpers/registryProvenance.d.ts.map +1 -0
- package/types/lib/helpers/source.d.ts +141 -0
- package/types/lib/helpers/source.d.ts.map +1 -0
- package/types/lib/helpers/spdxUtils.d.ts +2 -0
- package/types/lib/helpers/spdxUtils.d.ts.map +1 -0
- package/types/lib/helpers/unicodeScan.d.ts +46 -0
- package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +29 -11
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +0 -36
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
- package/types/lib/validator/bomValidator.d.ts +1 -0
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/bin/dependencies.d.ts +0 -3
- package/types/bin/dependencies.d.ts.map +0 -1
- package/types/bin/licenses.d.ts +0 -3
- package/types/bin/licenses.d.ts.map +0 -1
package/bin/cdxgen.js
CHANGED
|
@@ -4,7 +4,14 @@ import crypto from "node:crypto";
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import http from "node:http";
|
|
6
6
|
import https from "node:https";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
basename,
|
|
9
|
+
dirname,
|
|
10
|
+
isAbsolute,
|
|
11
|
+
join,
|
|
12
|
+
relative,
|
|
13
|
+
resolve,
|
|
14
|
+
} from "node:path";
|
|
8
15
|
import process from "node:process";
|
|
9
16
|
|
|
10
17
|
import { parse as _load } from "yaml";
|
|
@@ -25,7 +32,26 @@ import {
|
|
|
25
32
|
printSummary,
|
|
26
33
|
printTable,
|
|
27
34
|
} from "../lib/helpers/display.js";
|
|
35
|
+
import {
|
|
36
|
+
createOutputPlan,
|
|
37
|
+
getOutputDirectory,
|
|
38
|
+
} from "../lib/helpers/exportUtils.js";
|
|
28
39
|
import { TRACE_MODE, thoughtEnd, thoughtLog } from "../lib/helpers/logger.js";
|
|
40
|
+
import {
|
|
41
|
+
cleanupSourceDir,
|
|
42
|
+
findGitRefForPurlVersion,
|
|
43
|
+
gitClone,
|
|
44
|
+
isAllowedPath,
|
|
45
|
+
isAllowedWinPath,
|
|
46
|
+
maybePurlSource,
|
|
47
|
+
maybeRemotePath,
|
|
48
|
+
PURL_REGISTRY_LOOKUP_WARNING,
|
|
49
|
+
resolveGitUrlFromPurl,
|
|
50
|
+
resolvePurlSourceDirectory,
|
|
51
|
+
sanitizeRemoteUrlForLogs,
|
|
52
|
+
validateAndRejectGitSource,
|
|
53
|
+
validatePurlSource,
|
|
54
|
+
} from "../lib/helpers/source.js";
|
|
29
55
|
import {
|
|
30
56
|
commandsExecuted,
|
|
31
57
|
DEBUG_MODE,
|
|
@@ -42,9 +68,10 @@ import {
|
|
|
42
68
|
toCamel,
|
|
43
69
|
} from "../lib/helpers/utils.js";
|
|
44
70
|
import { postProcess } from "../lib/stages/postgen/postgen.js";
|
|
71
|
+
import { convertCycloneDxToSpdx } from "../lib/stages/postgen/spdxConverter.js";
|
|
45
72
|
import { auditEnvironment } from "../lib/stages/pregen/envAudit.js";
|
|
46
73
|
import { prepareEnv } from "../lib/stages/pregen/pregen.js";
|
|
47
|
-
import { validateBom } from "../lib/validator/bomValidator.js";
|
|
74
|
+
import { validateBom, validateSpdx } from "../lib/validator/bomValidator.js";
|
|
48
75
|
|
|
49
76
|
// Support for config files
|
|
50
77
|
const configPaths = [
|
|
@@ -134,6 +161,10 @@ const args = _yargs
|
|
|
134
161
|
description:
|
|
135
162
|
"Perform deep searches for components. Useful while scanning C/C++ apps, live OS and oci images.",
|
|
136
163
|
})
|
|
164
|
+
.option("git-branch", {
|
|
165
|
+
description: "Git branch to clone when the source is a git URL or purl",
|
|
166
|
+
type: "string",
|
|
167
|
+
})
|
|
137
168
|
.option("server-url", {
|
|
138
169
|
description: "Dependency track url. Eg: https://deptrack.cyclonedx.io",
|
|
139
170
|
type: "string",
|
|
@@ -307,6 +338,25 @@ const args = _yargs
|
|
|
307
338
|
hidden: true,
|
|
308
339
|
choices: ["pre-build", "build", "post-build"],
|
|
309
340
|
})
|
|
341
|
+
.option("include-release-notes", {
|
|
342
|
+
type: "boolean",
|
|
343
|
+
default: false,
|
|
344
|
+
hidden: true,
|
|
345
|
+
description:
|
|
346
|
+
"Attach CycloneDX releaseNotes to the cdxgen tool component in metadata.",
|
|
347
|
+
})
|
|
348
|
+
.option("release-notes-current-tag", {
|
|
349
|
+
type: "string",
|
|
350
|
+
hidden: true,
|
|
351
|
+
description:
|
|
352
|
+
"Current git tag used to build CycloneDX releaseNotes for cdxgen metadata.",
|
|
353
|
+
})
|
|
354
|
+
.option("release-notes-previous-tag", {
|
|
355
|
+
type: "string",
|
|
356
|
+
hidden: true,
|
|
357
|
+
description:
|
|
358
|
+
"Previous git tag used to build CycloneDX releaseNotes for cdxgen metadata.",
|
|
359
|
+
})
|
|
310
360
|
.option("include-regex", {
|
|
311
361
|
description:
|
|
312
362
|
"glob pattern to include. This overrides the default pattern used during auto-detection.",
|
|
@@ -322,6 +372,10 @@ const args = _yargs
|
|
|
322
372
|
default: false,
|
|
323
373
|
description: "Serialize and export BOM as protobuf binary.",
|
|
324
374
|
})
|
|
375
|
+
.option("format", {
|
|
376
|
+
description:
|
|
377
|
+
"Export format(s). Supports cyclonedx, spdx, repeated --format flags, or a comma-separated list such as cyclonedx,spdx.",
|
|
378
|
+
})
|
|
325
379
|
.option("proto-bin-file", {
|
|
326
380
|
description: "Path for the serialized protobuf binary.",
|
|
327
381
|
default: "bom.cdx",
|
|
@@ -433,12 +487,41 @@ const args = _yargs
|
|
|
433
487
|
default: "high",
|
|
434
488
|
hidden: true,
|
|
435
489
|
})
|
|
490
|
+
.option("bom-audit-scope", {
|
|
491
|
+
description:
|
|
492
|
+
"Predictive audit target scope. Use 'required' to scan only dependencies with scope=required (missing scope is treated as required).",
|
|
493
|
+
type: "string",
|
|
494
|
+
choices: ["all", "required"],
|
|
495
|
+
default: "all",
|
|
496
|
+
hidden: true,
|
|
497
|
+
})
|
|
498
|
+
.option("bom-audit-max-targets", {
|
|
499
|
+
description:
|
|
500
|
+
"Optional upper bound for predictive audit targets. By default cdxgen scans required dependencies first and expands to at least 50 targets.",
|
|
501
|
+
type: "number",
|
|
502
|
+
hidden: true,
|
|
503
|
+
})
|
|
504
|
+
.option("bom-audit-include-trusted", {
|
|
505
|
+
description:
|
|
506
|
+
"Include packages already marked with trusted publishing metadata in predictive BOM audit target selection.",
|
|
507
|
+
type: "boolean",
|
|
508
|
+
default: false,
|
|
509
|
+
hidden: true,
|
|
510
|
+
})
|
|
511
|
+
.option("bom-audit-only-trusted", {
|
|
512
|
+
description:
|
|
513
|
+
"Restrict predictive BOM audit target selection to packages marked with trusted publishing metadata.",
|
|
514
|
+
type: "boolean",
|
|
515
|
+
default: false,
|
|
516
|
+
hidden: true,
|
|
517
|
+
})
|
|
436
518
|
.completion("completion", "Generate bash/zsh completion")
|
|
437
519
|
.array("type")
|
|
438
520
|
.array("excludeType")
|
|
439
521
|
.array("filter")
|
|
440
522
|
.array("only")
|
|
441
523
|
.array("author")
|
|
524
|
+
.array("format")
|
|
442
525
|
.array("standard")
|
|
443
526
|
.array("feature-flags")
|
|
444
527
|
.array("technique")
|
|
@@ -486,6 +569,12 @@ if (args.help) {
|
|
|
486
569
|
_yargs.showHelp();
|
|
487
570
|
process.exit(0);
|
|
488
571
|
}
|
|
572
|
+
if (args.bomAuditIncludeTrusted && args.bomAuditOnlyTrusted) {
|
|
573
|
+
console.error(
|
|
574
|
+
"Use either --bom-audit-include-trusted or --bom-audit-only-trusted, not both.",
|
|
575
|
+
);
|
|
576
|
+
process.exit(1);
|
|
577
|
+
}
|
|
489
578
|
|
|
490
579
|
// Native Enterprise Network Configuration (Node.js v22.21+, Bun, Deno)
|
|
491
580
|
// https://nodejs.org/en/learn/http/enterprise-network-configuration
|
|
@@ -518,6 +607,8 @@ if (!process.env.NODE_USE_SYSTEM_CA) {
|
|
|
518
607
|
}
|
|
519
608
|
|
|
520
609
|
const filePath = args._[0] || process.cwd();
|
|
610
|
+
const sourceInputIsRemoteOrPurl =
|
|
611
|
+
maybeRemotePath(filePath) || maybePurlSource(filePath);
|
|
521
612
|
if (!args.projectName) {
|
|
522
613
|
if (filePath !== ".") {
|
|
523
614
|
args.projectName = basename(filePath);
|
|
@@ -527,9 +618,8 @@ if (!args.projectName) {
|
|
|
527
618
|
}
|
|
528
619
|
thoughtLog(`Let's try to generate a CycloneDX BOM for the path '${filePath}'`);
|
|
529
620
|
if (
|
|
530
|
-
|
|
531
|
-
filePath.includes("\r") ||
|
|
532
|
-
filePath.includes("\n")
|
|
621
|
+
!sourceInputIsRemoteOrPurl &&
|
|
622
|
+
(filePath.includes(" ") || filePath.includes("\r") || filePath.includes("\n"))
|
|
533
623
|
) {
|
|
534
624
|
console.log(
|
|
535
625
|
`'${filePath}' contains spaces. This could lead to bugs when invoking external build tools.`,
|
|
@@ -545,6 +635,10 @@ if (process.argv[1].includes("obom") && !args.type) {
|
|
|
545
635
|
"Ok, the user wants to generate an Operations Bill-of-Materials (OBOM).",
|
|
546
636
|
);
|
|
547
637
|
}
|
|
638
|
+
if (process.argv[1].includes("spdxgen") && !args.format) {
|
|
639
|
+
args.format = "spdx";
|
|
640
|
+
thoughtLog("Ok, defaulting the export format to SPDX.");
|
|
641
|
+
}
|
|
548
642
|
|
|
549
643
|
/**
|
|
550
644
|
* Command line options
|
|
@@ -557,19 +651,23 @@ const options = Object.assign({}, args, {
|
|
|
557
651
|
deep: args.deep || args.evidence,
|
|
558
652
|
output:
|
|
559
653
|
isSecureMode && args.output === "bom.json"
|
|
560
|
-
?
|
|
654
|
+
? sourceInputIsRemoteOrPurl
|
|
655
|
+
? resolve(args.output)
|
|
656
|
+
: resolve(join(filePath, args.output))
|
|
561
657
|
: args.output,
|
|
562
658
|
exclude: args.exclude || args.excludeRegex,
|
|
563
659
|
include: args.include || args.includeRegex,
|
|
564
660
|
});
|
|
565
|
-
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
)
|
|
572
|
-
|
|
661
|
+
const outputPlan = createOutputPlan(options);
|
|
662
|
+
for (const outputFile of Object.values(outputPlan.outputs)) {
|
|
663
|
+
const outputDirectory = getOutputDirectory(outputFile);
|
|
664
|
+
if (
|
|
665
|
+
outputDirectory &&
|
|
666
|
+
outputDirectory !== process.cwd() &&
|
|
667
|
+
!safeExistsSync(outputDirectory)
|
|
668
|
+
) {
|
|
669
|
+
fs.mkdirSync(outputDirectory, { recursive: true });
|
|
670
|
+
}
|
|
573
671
|
}
|
|
574
672
|
// Filter duplicate types. Eg: -t gradle -t gradle
|
|
575
673
|
if (options.projectType && Array.isArray(options.projectType)) {
|
|
@@ -929,6 +1027,129 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
929
1027
|
safeExistsSync(process.env.SBOM_SIGN_PRIVATE_KEY)) ||
|
|
930
1028
|
process.env.SBOM_SIGN_PRIVATE_KEY_BASE64));
|
|
931
1029
|
|
|
1030
|
+
const stringifyJson = (jsonPayload, jsonPretty) =>
|
|
1031
|
+
typeof jsonPayload === "string" || jsonPayload instanceof String
|
|
1032
|
+
? jsonPayload
|
|
1033
|
+
: JSON.stringify(jsonPayload, null, jsonPretty ? 2 : null);
|
|
1034
|
+
|
|
1035
|
+
const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
1036
|
+
const jsonPayload = stringifyJson(bomJson, options.jsonPretty);
|
|
1037
|
+
fs.writeFileSync(jsonFile, jsonPayload);
|
|
1038
|
+
if (jsonFile.endsWith("bom.json")) {
|
|
1039
|
+
thoughtLog(
|
|
1040
|
+
`Let's save the file to "${jsonFile}". Should I suggest the '.cdx.json' file extension for better semantics?`,
|
|
1041
|
+
);
|
|
1042
|
+
} else {
|
|
1043
|
+
thoughtLog(`Let's save the file to "${jsonFile}".`);
|
|
1044
|
+
}
|
|
1045
|
+
if (!jsonPayload || !needsBomSigning(options)) {
|
|
1046
|
+
return jsonPayload;
|
|
1047
|
+
}
|
|
1048
|
+
let alg = process.env.SBOM_SIGN_ALGORITHM || "RS512";
|
|
1049
|
+
if (alg.includes("none")) {
|
|
1050
|
+
alg = "RS512";
|
|
1051
|
+
}
|
|
1052
|
+
let privateKeyToUse;
|
|
1053
|
+
let jwkPublicKey;
|
|
1054
|
+
let publicKeyFile;
|
|
1055
|
+
if (options.generateKeyAndSign) {
|
|
1056
|
+
const jdirName = dirname(jsonFile);
|
|
1057
|
+
publicKeyFile = join(jdirName, "public.key");
|
|
1058
|
+
const privateKeyFile = join(jdirName, "private.key");
|
|
1059
|
+
const privateKeyB64File = join(jdirName, "private.key.base64");
|
|
1060
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", {
|
|
1061
|
+
modulusLength: 4096,
|
|
1062
|
+
publicKeyEncoding: {
|
|
1063
|
+
type: "spki",
|
|
1064
|
+
format: "pem",
|
|
1065
|
+
},
|
|
1066
|
+
privateKeyEncoding: {
|
|
1067
|
+
type: "pkcs8",
|
|
1068
|
+
format: "pem",
|
|
1069
|
+
},
|
|
1070
|
+
});
|
|
1071
|
+
fs.writeFileSync(publicKeyFile, publicKey);
|
|
1072
|
+
fs.writeFileSync(privateKeyFile, privateKey);
|
|
1073
|
+
fs.writeFileSync(
|
|
1074
|
+
privateKeyB64File,
|
|
1075
|
+
Buffer.from(privateKey, "utf8").toString("base64"),
|
|
1076
|
+
);
|
|
1077
|
+
console.log(
|
|
1078
|
+
"Created public/private key pairs for testing purposes",
|
|
1079
|
+
publicKeyFile,
|
|
1080
|
+
privateKeyFile,
|
|
1081
|
+
privateKeyB64File,
|
|
1082
|
+
);
|
|
1083
|
+
privateKeyToUse = privateKey;
|
|
1084
|
+
jwkPublicKey = crypto.createPublicKey(publicKey).export({ format: "jwk" });
|
|
1085
|
+
} else {
|
|
1086
|
+
if (process.env?.SBOM_SIGN_PRIVATE_KEY) {
|
|
1087
|
+
privateKeyToUse = fs.readFileSync(
|
|
1088
|
+
process.env.SBOM_SIGN_PRIVATE_KEY,
|
|
1089
|
+
"utf8",
|
|
1090
|
+
);
|
|
1091
|
+
} else if (process.env?.SBOM_SIGN_PRIVATE_KEY_BASE64) {
|
|
1092
|
+
privateKeyToUse = Buffer.from(
|
|
1093
|
+
process.env.SBOM_SIGN_PRIVATE_KEY_BASE64,
|
|
1094
|
+
"base64",
|
|
1095
|
+
).toString("utf8");
|
|
1096
|
+
}
|
|
1097
|
+
if (
|
|
1098
|
+
process.env.SBOM_SIGN_PUBLIC_KEY &&
|
|
1099
|
+
safeExistsSync(process.env.SBOM_SIGN_PUBLIC_KEY)
|
|
1100
|
+
) {
|
|
1101
|
+
jwkPublicKey = crypto
|
|
1102
|
+
.createPublicKey(
|
|
1103
|
+
fs.readFileSync(process.env.SBOM_SIGN_PUBLIC_KEY, "utf8"),
|
|
1104
|
+
)
|
|
1105
|
+
.export({ format: "jwk" });
|
|
1106
|
+
} else if (process.env?.SBOM_SIGN_PUBLIC_KEY_BASE64) {
|
|
1107
|
+
jwkPublicKey = Buffer.from(
|
|
1108
|
+
process.env.SBOM_SIGN_PUBLIC_KEY_BASE64,
|
|
1109
|
+
"base64",
|
|
1110
|
+
).toString("utf8");
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
try {
|
|
1114
|
+
const bomJsonUnsignedObj = JSON.parse(jsonPayload);
|
|
1115
|
+
const signOptions = {
|
|
1116
|
+
privateKey: privateKeyToUse,
|
|
1117
|
+
algorithm: alg,
|
|
1118
|
+
publicKeyJwk: jwkPublicKey,
|
|
1119
|
+
mode: process.env.SBOM_SIGN_MODE || "replace",
|
|
1120
|
+
signComponents: true,
|
|
1121
|
+
signServices: true,
|
|
1122
|
+
signAnnotations: true,
|
|
1123
|
+
};
|
|
1124
|
+
thoughtLog(`Signing the BOM file "${jsonFile}".`);
|
|
1125
|
+
const signedBom = signBom(bomJsonUnsignedObj, signOptions);
|
|
1126
|
+
fs.writeFileSync(
|
|
1127
|
+
jsonFile,
|
|
1128
|
+
JSON.stringify(signedBom, null, options.jsonPretty ? 2 : null),
|
|
1129
|
+
);
|
|
1130
|
+
if (publicKeyFile) {
|
|
1131
|
+
const publicKeyStr = fs.readFileSync(publicKeyFile, "utf8");
|
|
1132
|
+
const signatureVerification = verifyBom(signedBom, publicKeyStr);
|
|
1133
|
+
if (signatureVerification) {
|
|
1134
|
+
console.log(
|
|
1135
|
+
"SBOM signature is verifiable natively with the public key and the algorithm",
|
|
1136
|
+
publicKeyFile,
|
|
1137
|
+
alg,
|
|
1138
|
+
);
|
|
1139
|
+
} else {
|
|
1140
|
+
console.log("SBOM signature verification was unsuccessful");
|
|
1141
|
+
console.log("Check if the public key was exported in PEM format");
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
} catch (ex) {
|
|
1145
|
+
console.log("SBOM signing was unsuccessful:", ex.message);
|
|
1146
|
+
console.log(
|
|
1147
|
+
"Check if the private key was exported in PEM format and the algorithm is JSF-compliant.",
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
return jsonPayload;
|
|
1151
|
+
};
|
|
1152
|
+
|
|
932
1153
|
/**
|
|
933
1154
|
* Method to start the bom creation process
|
|
934
1155
|
*/
|
|
@@ -954,24 +1175,130 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
954
1175
|
const serverModule = await import("../lib/server/server.js");
|
|
955
1176
|
return serverModule.start(options);
|
|
956
1177
|
}
|
|
957
|
-
|
|
958
|
-
|
|
1178
|
+
let sourcePath = filePath;
|
|
1179
|
+
let purlResolution;
|
|
1180
|
+
if (maybePurlSource(sourcePath)) {
|
|
1181
|
+
const purlValidationError = validatePurlSource(sourcePath);
|
|
1182
|
+
if (purlValidationError) {
|
|
1183
|
+
console.error(purlValidationError.error, purlValidationError.details);
|
|
1184
|
+
process.exit(1);
|
|
1185
|
+
}
|
|
1186
|
+
purlResolution = await resolveGitUrlFromPurl(sourcePath);
|
|
1187
|
+
if (!purlResolution?.repoUrl) {
|
|
1188
|
+
console.error(
|
|
1189
|
+
"Unable to resolve the provided package URL to a repository URL.",
|
|
1190
|
+
);
|
|
1191
|
+
process.exit(1);
|
|
1192
|
+
}
|
|
1193
|
+
console.warn(
|
|
1194
|
+
`${PURL_REGISTRY_LOOKUP_WARNING} Registry: ${purlResolution.registry}, purl type: ${purlResolution.type}, resolved URL: ${sanitizeRemoteUrlForLogs(purlResolution.repoUrl)}`,
|
|
1195
|
+
);
|
|
1196
|
+
sourcePath = purlResolution.repoUrl;
|
|
1197
|
+
}
|
|
1198
|
+
if (
|
|
1199
|
+
maybeRemotePath(sourcePath) &&
|
|
1200
|
+
isSecureMode &&
|
|
1201
|
+
!process.env.CDXGEN_GIT_ALLOWED_HOSTS &&
|
|
1202
|
+
!process.env.CDXGEN_SERVER_ALLOWED_HOSTS
|
|
1203
|
+
) {
|
|
1204
|
+
console.error(
|
|
1205
|
+
"SECURE MODE: Configure CDXGEN_GIT_ALLOWED_HOSTS (or CDXGEN_SERVER_ALLOWED_HOSTS) before using git URL or purl sources.",
|
|
1206
|
+
);
|
|
1207
|
+
process.exit(1);
|
|
1208
|
+
}
|
|
1209
|
+
if (!maybeRemotePath(sourcePath) && !isAllowedPath(resolve(sourcePath))) {
|
|
1210
|
+
console.error(
|
|
1211
|
+
"Path is not allowed as per CDXGEN_ALLOWED_PATHS/CDXGEN_SERVER_ALLOWED_PATHS.",
|
|
1212
|
+
);
|
|
1213
|
+
process.exit(1);
|
|
1214
|
+
}
|
|
1215
|
+
if (!maybeRemotePath(sourcePath) && !isAllowedWinPath(resolve(sourcePath))) {
|
|
1216
|
+
console.error("Path is not allowed on this platform.");
|
|
1217
|
+
process.exit(1);
|
|
1218
|
+
}
|
|
1219
|
+
if (maybeRemotePath(sourcePath)) {
|
|
1220
|
+
const validationError = validateAndRejectGitSource(sourcePath);
|
|
1221
|
+
if (validationError) {
|
|
1222
|
+
console.error(validationError.error, validationError.details);
|
|
1223
|
+
process.exit(1);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
const checkPath = maybeRemotePath(sourcePath) ? getTmpDir() : sourcePath;
|
|
1227
|
+
if (maybeRemotePath(sourcePath)) {
|
|
1228
|
+
options.releaseNotesGitUrl = sourcePath;
|
|
1229
|
+
}
|
|
1230
|
+
if (!checkPermissions(checkPath, options)) {
|
|
959
1231
|
if (isSecureMode) {
|
|
960
1232
|
process.exit(1);
|
|
961
1233
|
}
|
|
962
1234
|
return;
|
|
963
1235
|
}
|
|
964
|
-
|
|
1236
|
+
let srcDir = sourcePath;
|
|
1237
|
+
let cleanup = false;
|
|
1238
|
+
let gitRef = options.gitBranch;
|
|
1239
|
+
if (maybeRemotePath(sourcePath)) {
|
|
1240
|
+
if (!gitRef && purlResolution?.version) {
|
|
1241
|
+
gitRef = findGitRefForPurlVersion(sourcePath, purlResolution);
|
|
1242
|
+
if (!gitRef) {
|
|
1243
|
+
console.warn(
|
|
1244
|
+
`Unable to find a matching git tag for version '${purlResolution.version}'. Falling back to repository default branch.`,
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
srcDir = gitClone(sourcePath, gitRef);
|
|
1249
|
+
if (purlResolution?.type === "npm") {
|
|
1250
|
+
const cloneRootDir = srcDir;
|
|
1251
|
+
const purlSourceDir = resolvePurlSourceDirectory(srcDir, purlResolution);
|
|
1252
|
+
if (purlSourceDir) {
|
|
1253
|
+
if (purlSourceDir !== cloneRootDir) {
|
|
1254
|
+
const relativeDir = relative(cloneRootDir, purlSourceDir);
|
|
1255
|
+
if (relativeDir.startsWith("..") || isAbsolute(relativeDir)) {
|
|
1256
|
+
console.warn(
|
|
1257
|
+
`Ignoring detected npm package directory outside clone root: ${purlSourceDir}`,
|
|
1258
|
+
);
|
|
1259
|
+
} else {
|
|
1260
|
+
console.warn(
|
|
1261
|
+
`Using npm package directory '${purlSourceDir}' for purl '${purlResolution.namespace ? `${purlResolution.namespace}/` : ""}${purlResolution.name}'.`,
|
|
1262
|
+
);
|
|
1263
|
+
srcDir = purlSourceDir;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
cleanup = true;
|
|
1269
|
+
}
|
|
1270
|
+
prepareEnv(srcDir, options);
|
|
965
1271
|
thoughtLog("Getting ready to generate the BOM ⚡️.");
|
|
966
|
-
|
|
1272
|
+
const originalFetchPackageMetadata = process.env.CDXGEN_FETCH_PKG_METADATA;
|
|
1273
|
+
if (options.bomAudit) {
|
|
1274
|
+
process.env.CDXGEN_FETCH_PKG_METADATA = "true";
|
|
1275
|
+
}
|
|
1276
|
+
let bomNSData;
|
|
1277
|
+
try {
|
|
1278
|
+
bomNSData = (await createBom(srcDir, options)) || {};
|
|
1279
|
+
} finally {
|
|
1280
|
+
if (originalFetchPackageMetadata === undefined) {
|
|
1281
|
+
delete process.env.CDXGEN_FETCH_PKG_METADATA;
|
|
1282
|
+
} else {
|
|
1283
|
+
process.env.CDXGEN_FETCH_PKG_METADATA = originalFetchPackageMetadata;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
967
1286
|
if (bomNSData?.bomJson) {
|
|
968
1287
|
thoughtLog(
|
|
969
1288
|
"Tweaking the generated BOM data with useful annotations and properties.",
|
|
970
1289
|
);
|
|
971
1290
|
}
|
|
972
1291
|
// Add extra metadata and annotations with post processing
|
|
973
|
-
bomNSData = postProcess(bomNSData, options,
|
|
1292
|
+
bomNSData = postProcess(bomNSData, options, srcDir);
|
|
974
1293
|
if (options.bomAudit && bomNSData?.bomJson) {
|
|
1294
|
+
const { finalizeAuditReport, runAuditFromBoms } = await import(
|
|
1295
|
+
"../lib/audit/index.js"
|
|
1296
|
+
);
|
|
1297
|
+
const { createProgressTracker } = await import("../lib/audit/progress.js");
|
|
1298
|
+
const { collectAuditTargets } = await import("../lib/audit/targets.js");
|
|
1299
|
+
const { formatPredictiveAnnotations, renderConsoleReport } = await import(
|
|
1300
|
+
"../lib/audit/reporters.js"
|
|
1301
|
+
);
|
|
975
1302
|
const {
|
|
976
1303
|
auditBom,
|
|
977
1304
|
formatAnnotations,
|
|
@@ -999,160 +1326,125 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
999
1326
|
console.error(
|
|
1000
1327
|
"Review findings above or adjust --bom-audit-fail-severity to proceed.",
|
|
1001
1328
|
);
|
|
1329
|
+
if (cleanup) {
|
|
1330
|
+
cleanupSourceDir(srcDir);
|
|
1331
|
+
}
|
|
1002
1332
|
process.exit(1);
|
|
1003
1333
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
privateKeyB64File,
|
|
1062
|
-
Buffer.from(privateKey, "utf8").toString("base64"),
|
|
1063
|
-
);
|
|
1064
|
-
console.log(
|
|
1065
|
-
"Created public/private key pairs for testing purposes",
|
|
1066
|
-
publicKeyFile,
|
|
1067
|
-
privateKeyFile,
|
|
1068
|
-
privateKeyB64File,
|
|
1069
|
-
);
|
|
1070
|
-
privateKeyToUse = privateKey;
|
|
1071
|
-
jwkPublicKey = crypto
|
|
1072
|
-
.createPublicKey(publicKey)
|
|
1073
|
-
.export({ format: "jwk" });
|
|
1074
|
-
} else {
|
|
1075
|
-
if (process.env?.SBOM_SIGN_PRIVATE_KEY) {
|
|
1076
|
-
privateKeyToUse = fs.readFileSync(
|
|
1077
|
-
process.env.SBOM_SIGN_PRIVATE_KEY,
|
|
1078
|
-
"utf8",
|
|
1079
|
-
);
|
|
1080
|
-
} else if (process.env?.SBOM_SIGN_PRIVATE_KEY_BASE64) {
|
|
1081
|
-
privateKeyToUse = Buffer.from(
|
|
1082
|
-
process.env.SBOM_SIGN_PRIVATE_KEY_BASE64,
|
|
1083
|
-
"base64",
|
|
1084
|
-
).toString("utf8");
|
|
1085
|
-
}
|
|
1086
|
-
if (
|
|
1087
|
-
process.env.SBOM_SIGN_PUBLIC_KEY &&
|
|
1088
|
-
safeExistsSync(process.env.SBOM_SIGN_PUBLIC_KEY)
|
|
1089
|
-
) {
|
|
1090
|
-
jwkPublicKey = crypto
|
|
1091
|
-
.createPublicKey(
|
|
1092
|
-
fs.readFileSync(process.env.SBOM_SIGN_PUBLIC_KEY, "utf8"),
|
|
1093
|
-
)
|
|
1094
|
-
.export({ format: "jwk" });
|
|
1095
|
-
} else if (process.env?.SBOM_SIGN_PUBLIC_KEY_BASE64) {
|
|
1096
|
-
jwkPublicKey = Buffer.from(
|
|
1097
|
-
process.env.SBOM_SIGN_PUBLIC_KEY_BASE64,
|
|
1098
|
-
"base64",
|
|
1099
|
-
).toString("utf8");
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
try {
|
|
1103
|
-
const bomJsonUnsignedObj = JSON.parse(jsonPayload);
|
|
1104
|
-
const signOptions = {
|
|
1105
|
-
privateKey: privateKeyToUse,
|
|
1106
|
-
algorithm: alg,
|
|
1107
|
-
publicKeyJwk: jwkPublicKey,
|
|
1108
|
-
mode: process.env.SBOM_SIGN_MODE || "replace",
|
|
1109
|
-
signComponents: true,
|
|
1110
|
-
signServices: true,
|
|
1111
|
-
signAnnotations: true,
|
|
1112
|
-
};
|
|
1113
|
-
thoughtLog(`Signing the BOM file "${jsonFile}".`);
|
|
1114
|
-
const signedBom = signBom(bomJsonUnsignedObj, signOptions);
|
|
1115
|
-
fs.writeFileSync(
|
|
1116
|
-
jsonFile,
|
|
1117
|
-
JSON.stringify(signedBom, null, options.jsonPretty ? 2 : null),
|
|
1118
|
-
);
|
|
1119
|
-
if (publicKeyFile) {
|
|
1120
|
-
const publicKeyStr = fs.readFileSync(publicKeyFile, "utf8");
|
|
1121
|
-
const signatureVerification = verifyBom(signedBom, publicKeyStr);
|
|
1122
|
-
if (signatureVerification) {
|
|
1123
|
-
console.log(
|
|
1124
|
-
"SBOM signature is verifiable natively with the public key and the algorithm",
|
|
1125
|
-
publicKeyFile,
|
|
1126
|
-
alg,
|
|
1127
|
-
);
|
|
1128
|
-
} else {
|
|
1129
|
-
console.log("SBOM signature verification was unsuccessful");
|
|
1130
|
-
console.log("Check if the public key was exported in PEM format");
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
} catch (ex) {
|
|
1134
|
-
console.log("SBOM signing was unsuccessful:", ex.message);
|
|
1135
|
-
console.log(
|
|
1136
|
-
"Check if the private key was exported in PEM format and the algorithm is JSF-compliant.",
|
|
1137
|
-
);
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1334
|
+
|
|
1335
|
+
thoughtLog("Let's run predictive dependency audit...");
|
|
1336
|
+
const progressTracker = createProgressTracker();
|
|
1337
|
+
const predictiveAuditScope =
|
|
1338
|
+
options.bomAuditScope === "required" ? "required" : undefined;
|
|
1339
|
+
const predictiveAuditTrusted = options.bomAuditOnlyTrusted
|
|
1340
|
+
? "only"
|
|
1341
|
+
: options.bomAuditIncludeTrusted
|
|
1342
|
+
? "include"
|
|
1343
|
+
: undefined;
|
|
1344
|
+
const requiredAuditTargetCount = collectAuditTargets(
|
|
1345
|
+
[
|
|
1346
|
+
{
|
|
1347
|
+
bomJson: bomNSData.bomJson,
|
|
1348
|
+
source: filePath,
|
|
1349
|
+
},
|
|
1350
|
+
],
|
|
1351
|
+
{
|
|
1352
|
+
scope: "required",
|
|
1353
|
+
trusted: predictiveAuditTrusted,
|
|
1354
|
+
},
|
|
1355
|
+
).targets.length;
|
|
1356
|
+
const predictiveAuditMaxTargets =
|
|
1357
|
+
typeof options.bomAuditMaxTargets === "number" &&
|
|
1358
|
+
options.bomAuditMaxTargets > 0
|
|
1359
|
+
? options.bomAuditMaxTargets
|
|
1360
|
+
: predictiveAuditScope === "required"
|
|
1361
|
+
? undefined
|
|
1362
|
+
: Math.max(50, requiredAuditTargetCount);
|
|
1363
|
+
let predictiveReport;
|
|
1364
|
+
try {
|
|
1365
|
+
predictiveReport = await runAuditFromBoms(
|
|
1366
|
+
[
|
|
1367
|
+
{
|
|
1368
|
+
bomJson: bomNSData.bomJson,
|
|
1369
|
+
source: filePath,
|
|
1370
|
+
},
|
|
1371
|
+
],
|
|
1372
|
+
{
|
|
1373
|
+
categories: options.bomAuditCategories
|
|
1374
|
+
? options.bomAuditCategories
|
|
1375
|
+
.split(",")
|
|
1376
|
+
.map((category) => category.trim())
|
|
1377
|
+
.filter(Boolean)
|
|
1378
|
+
: undefined,
|
|
1379
|
+
failSeverity: options.bomAuditFailSeverity,
|
|
1380
|
+
maxTargets: predictiveAuditMaxTargets,
|
|
1381
|
+
minSeverity: options.bomAuditMinSeverity,
|
|
1382
|
+
onProgress: progressTracker.onProgress,
|
|
1383
|
+
scope: predictiveAuditScope,
|
|
1384
|
+
trusted: predictiveAuditTrusted,
|
|
1385
|
+
trustedSelectionHelp:
|
|
1386
|
+
"Use --bom-audit-include-trusted to include them or --bom-audit-only-trusted to audit just those packages.",
|
|
1387
|
+
},
|
|
1388
|
+
);
|
|
1389
|
+
} finally {
|
|
1390
|
+
progressTracker.stop();
|
|
1140
1391
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1392
|
+
if (predictiveReport.summary.totalTargets > 0) {
|
|
1393
|
+
process.stderr.write(
|
|
1394
|
+
renderConsoleReport(predictiveReport, {
|
|
1395
|
+
minSeverity: options.bomAuditMinSeverity,
|
|
1396
|
+
}),
|
|
1397
|
+
);
|
|
1398
|
+
} else if (DEBUG_MODE) {
|
|
1399
|
+
console.log("Predictive BOM audit: No supported npm/PyPI targets found");
|
|
1145
1400
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1401
|
+
const predictiveAnnotations = formatPredictiveAnnotations(
|
|
1402
|
+
predictiveReport,
|
|
1403
|
+
bomNSData.bomJson,
|
|
1404
|
+
{
|
|
1405
|
+
minSeverity: options.bomAuditMinSeverity,
|
|
1406
|
+
},
|
|
1407
|
+
);
|
|
1408
|
+
if (predictiveAnnotations.length && options.specVersion >= 1.4) {
|
|
1409
|
+
bomNSData.bomJson.annotations = [
|
|
1410
|
+
...(bomNSData.bomJson.annotations || []),
|
|
1411
|
+
...predictiveAnnotations,
|
|
1412
|
+
];
|
|
1413
|
+
thoughtLog(
|
|
1414
|
+
`Embedded ${predictiveAnnotations.length} predictive audit annotations`,
|
|
1150
1415
|
);
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
|
|
1416
|
+
}
|
|
1417
|
+
const predictiveResult = finalizeAuditReport(predictiveReport, {
|
|
1418
|
+
failSeverity: options.bomAuditFailSeverity,
|
|
1419
|
+
minSeverity: options.bomAuditMinSeverity,
|
|
1420
|
+
report: "console",
|
|
1421
|
+
});
|
|
1422
|
+
if (isSecureMode && predictiveResult.exitCode === 3) {
|
|
1423
|
+
console.error(
|
|
1424
|
+
"\nSecure mode: Predictive audit findings exceeded the configured threshold.",
|
|
1425
|
+
);
|
|
1426
|
+
console.error(
|
|
1427
|
+
"Review findings above or adjust --bom-audit-fail-severity to proceed.",
|
|
1428
|
+
);
|
|
1429
|
+
if (cleanup) {
|
|
1430
|
+
cleanupSourceDir(srcDir);
|
|
1431
|
+
}
|
|
1432
|
+
process.exit(1);
|
|
1154
1433
|
}
|
|
1155
1434
|
}
|
|
1435
|
+
let internalCycloneDxInputPath = outputPlan.outputs.cyclonedx;
|
|
1436
|
+
if ((options.evidence || options.includeCrypto) && bomNSData?.bomJson) {
|
|
1437
|
+
if (!internalCycloneDxInputPath) {
|
|
1438
|
+
internalCycloneDxInputPath = join(
|
|
1439
|
+
getTmpDir(),
|
|
1440
|
+
`cdxgen-${Date.now()}-${basename(filePath)}.cdx.json`,
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
fs.writeFileSync(
|
|
1444
|
+
internalCycloneDxInputPath,
|
|
1445
|
+
stringifyJson(bomNSData.bomJson, options.jsonPretty),
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1156
1448
|
// Evidence generation
|
|
1157
1449
|
if (options.evidence || options.includeCrypto) {
|
|
1158
1450
|
// Set the evinse output file to be the same as output file
|
|
@@ -1163,7 +1455,7 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
1163
1455
|
options.projectType = options.projectType || ["java"];
|
|
1164
1456
|
const evinseOptions = {
|
|
1165
1457
|
_: args._,
|
|
1166
|
-
input: options.output,
|
|
1458
|
+
input: internalCycloneDxInputPath || options.output,
|
|
1167
1459
|
output: options.evinseOutput,
|
|
1168
1460
|
language: options.projectType,
|
|
1169
1461
|
skipMavenCollector: false,
|
|
@@ -1202,10 +1494,58 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
1202
1494
|
if (options.validate && bomNSData?.bomJson) {
|
|
1203
1495
|
thoughtLog("Wait, let's check the generated BOM file for any issues.");
|
|
1204
1496
|
if (!validateBom(bomNSData.bomJson)) {
|
|
1497
|
+
if (cleanup) {
|
|
1498
|
+
cleanupSourceDir(srcDir);
|
|
1499
|
+
}
|
|
1205
1500
|
process.exit(1);
|
|
1206
1501
|
}
|
|
1207
1502
|
thoughtLog("✅ BOM file looks valid.");
|
|
1208
1503
|
}
|
|
1504
|
+
if (
|
|
1505
|
+
outputPlan.formats.has("spdx") &&
|
|
1506
|
+
bomNSData?.bomJson &&
|
|
1507
|
+
bomNSData?.bomJson?.bomFormat === "CycloneDX"
|
|
1508
|
+
) {
|
|
1509
|
+
thoughtLog(
|
|
1510
|
+
"Preparing the SPDX 3.0.1 export from the validated CycloneDX BOM.",
|
|
1511
|
+
);
|
|
1512
|
+
bomNSData.spdxJson = convertCycloneDxToSpdx(bomNSData.bomJson, options);
|
|
1513
|
+
if (options.validate && !validateSpdx(bomNSData.spdxJson)) {
|
|
1514
|
+
process.exit(1);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
if (
|
|
1518
|
+
options.output &&
|
|
1519
|
+
(typeof options.output === "string" || options.output instanceof String)
|
|
1520
|
+
) {
|
|
1521
|
+
if (outputPlan.outputs.cyclonedx && bomNSData.bomJson) {
|
|
1522
|
+
writeCycloneDxOutput(
|
|
1523
|
+
outputPlan.outputs.cyclonedx,
|
|
1524
|
+
bomNSData.bomJson,
|
|
1525
|
+
options,
|
|
1526
|
+
);
|
|
1527
|
+
if (bomNSData.nsMapping && Object.keys(bomNSData.nsMapping).length) {
|
|
1528
|
+
const nsFile = `${outputPlan.outputs.cyclonedx}.map`;
|
|
1529
|
+
fs.writeFileSync(nsFile, JSON.stringify(bomNSData.nsMapping));
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
if (outputPlan.outputs.spdx && bomNSData.spdxJson) {
|
|
1533
|
+
fs.writeFileSync(
|
|
1534
|
+
outputPlan.outputs.spdx,
|
|
1535
|
+
stringifyJson(bomNSData.spdxJson, options.jsonPretty),
|
|
1536
|
+
);
|
|
1537
|
+
thoughtLog(`Let's save the SPDX file to "${outputPlan.outputs.spdx}".`);
|
|
1538
|
+
}
|
|
1539
|
+
} else if (!options.print) {
|
|
1540
|
+
if (outputPlan.formats.has("spdx") && bomNSData?.spdxJson) {
|
|
1541
|
+
console.log(stringifyJson(bomNSData.spdxJson, options.jsonPretty));
|
|
1542
|
+
} else if (bomNSData.bomJson) {
|
|
1543
|
+
console.log(stringifyJson(bomNSData.bomJson, options.jsonPretty));
|
|
1544
|
+
} else {
|
|
1545
|
+
console.log("Unable to produce BOM for", filePath);
|
|
1546
|
+
console.log("Try running the command with -t <type> or -r argument");
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1209
1549
|
thoughtEnd();
|
|
1210
1550
|
// Automatically submit the bom data
|
|
1211
1551
|
// biome-ignore lint/suspicious/noDoubleEquals: yargs passes true for empty values
|
|
@@ -1214,6 +1554,9 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
1214
1554
|
await submitBom(options, bomNSData.bomJson);
|
|
1215
1555
|
} catch (err) {
|
|
1216
1556
|
console.log(err);
|
|
1557
|
+
if (cleanup) {
|
|
1558
|
+
cleanupSourceDir(srcDir);
|
|
1559
|
+
}
|
|
1217
1560
|
process.exit(1);
|
|
1218
1561
|
}
|
|
1219
1562
|
}
|
|
@@ -1256,4 +1599,7 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
1256
1599
|
console.log(allowListSuggestion);
|
|
1257
1600
|
}
|
|
1258
1601
|
}
|
|
1602
|
+
if (cleanup) {
|
|
1603
|
+
cleanupSourceDir(srcDir);
|
|
1604
|
+
}
|
|
1259
1605
|
})();
|