@cyclonedx/cdxgen 12.2.0 → 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 +242 -90
- package/bin/audit.js +191 -0
- package/bin/cdxgen.js +532 -168
- 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 +276 -68
- package/lib/cli/index.poku.js +368 -0
- package/lib/helpers/analyzer.js +1052 -5
- package/lib/helpers/analyzer.poku.js +301 -0
- 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/depsUtils.js +16 -0
- package/lib/helpers/depsUtils.poku.js +58 -1
- package/lib/helpers/display.js +245 -61
- 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/remote/dependency-track.js +84 -0
- package/lib/helpers/remote/dependency-track.poku.js +119 -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/table.js +384 -0
- package/lib/helpers/table.poku.js +186 -0
- package/lib/helpers/unicodeScan.js +147 -0
- package/lib/helpers/unicodeScan.poku.js +45 -0
- package/lib/helpers/utils.js +882 -136
- package/lib/helpers/utils.poku.js +995 -91
- 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 +50 -0
- package/lib/server/server.js +228 -331
- package/lib/server/server.poku.js +220 -5
- package/lib/stages/postgen/annotator.js +7 -0
- package/lib/stages/postgen/annotator.poku.js +40 -0
- package/lib/stages/postgen/auditBom.js +20 -5
- 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 +13 -2
- package/lib/validator/reporters.poku.js +13 -0
- package/package.json +10 -8
- 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/depsUtils.d.ts.map +1 -1
- 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/remote/dependency-track.d.ts +16 -0
- package/types/lib/helpers/remote/dependency-track.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/table.d.ts +6 -0
- package/types/lib/helpers/table.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 +30 -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 -35
- 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",
|
|
@@ -171,6 +202,24 @@ const args = _yargs
|
|
|
171
202
|
description: "Dependency track parent project id",
|
|
172
203
|
type: "string",
|
|
173
204
|
})
|
|
205
|
+
.option("parent-project-name", {
|
|
206
|
+
description: "Dependency track parent project name",
|
|
207
|
+
type: "string",
|
|
208
|
+
})
|
|
209
|
+
.option("parent-project-version", {
|
|
210
|
+
description: "Dependency track parent project version",
|
|
211
|
+
type: "string",
|
|
212
|
+
})
|
|
213
|
+
.option("auto-create", {
|
|
214
|
+
description: "Dependency track autoCreate value for BOM uploads",
|
|
215
|
+
type: "boolean",
|
|
216
|
+
hidden: true,
|
|
217
|
+
})
|
|
218
|
+
.option("is-latest", {
|
|
219
|
+
description: "Dependency track isLatest value for BOM uploads",
|
|
220
|
+
type: "boolean",
|
|
221
|
+
hidden: true,
|
|
222
|
+
})
|
|
174
223
|
.option("required-only", {
|
|
175
224
|
type: "boolean",
|
|
176
225
|
description:
|
|
@@ -249,7 +298,7 @@ const args = _yargs
|
|
|
249
298
|
hidden: true,
|
|
250
299
|
})
|
|
251
300
|
.option("spec-version", {
|
|
252
|
-
description: "CycloneDX Specification version to use. Defaults to 1.
|
|
301
|
+
description: "CycloneDX Specification version to use. Defaults to 1.7",
|
|
253
302
|
default: 1.7,
|
|
254
303
|
type: "number",
|
|
255
304
|
choices: [1.4, 1.5, 1.6, 1.7],
|
|
@@ -289,6 +338,25 @@ const args = _yargs
|
|
|
289
338
|
hidden: true,
|
|
290
339
|
choices: ["pre-build", "build", "post-build"],
|
|
291
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
|
+
})
|
|
292
360
|
.option("include-regex", {
|
|
293
361
|
description:
|
|
294
362
|
"glob pattern to include. This overrides the default pattern used during auto-detection.",
|
|
@@ -304,6 +372,10 @@ const args = _yargs
|
|
|
304
372
|
default: false,
|
|
305
373
|
description: "Serialize and export BOM as protobuf binary.",
|
|
306
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
|
+
})
|
|
307
379
|
.option("proto-bin-file", {
|
|
308
380
|
description: "Path for the serialized protobuf binary.",
|
|
309
381
|
default: "bom.cdx",
|
|
@@ -415,12 +487,41 @@ const args = _yargs
|
|
|
415
487
|
default: "high",
|
|
416
488
|
hidden: true,
|
|
417
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
|
+
})
|
|
418
518
|
.completion("completion", "Generate bash/zsh completion")
|
|
419
519
|
.array("type")
|
|
420
520
|
.array("excludeType")
|
|
421
521
|
.array("filter")
|
|
422
522
|
.array("only")
|
|
423
523
|
.array("author")
|
|
524
|
+
.array("format")
|
|
424
525
|
.array("standard")
|
|
425
526
|
.array("feature-flags")
|
|
426
527
|
.array("technique")
|
|
@@ -468,6 +569,12 @@ if (args.help) {
|
|
|
468
569
|
_yargs.showHelp();
|
|
469
570
|
process.exit(0);
|
|
470
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
|
+
}
|
|
471
578
|
|
|
472
579
|
// Native Enterprise Network Configuration (Node.js v22.21+, Bun, Deno)
|
|
473
580
|
// https://nodejs.org/en/learn/http/enterprise-network-configuration
|
|
@@ -500,6 +607,8 @@ if (!process.env.NODE_USE_SYSTEM_CA) {
|
|
|
500
607
|
}
|
|
501
608
|
|
|
502
609
|
const filePath = args._[0] || process.cwd();
|
|
610
|
+
const sourceInputIsRemoteOrPurl =
|
|
611
|
+
maybeRemotePath(filePath) || maybePurlSource(filePath);
|
|
503
612
|
if (!args.projectName) {
|
|
504
613
|
if (filePath !== ".") {
|
|
505
614
|
args.projectName = basename(filePath);
|
|
@@ -509,9 +618,8 @@ if (!args.projectName) {
|
|
|
509
618
|
}
|
|
510
619
|
thoughtLog(`Let's try to generate a CycloneDX BOM for the path '${filePath}'`);
|
|
511
620
|
if (
|
|
512
|
-
|
|
513
|
-
filePath.includes("\r") ||
|
|
514
|
-
filePath.includes("\n")
|
|
621
|
+
!sourceInputIsRemoteOrPurl &&
|
|
622
|
+
(filePath.includes(" ") || filePath.includes("\r") || filePath.includes("\n"))
|
|
515
623
|
) {
|
|
516
624
|
console.log(
|
|
517
625
|
`'${filePath}' contains spaces. This could lead to bugs when invoking external build tools.`,
|
|
@@ -527,6 +635,10 @@ if (process.argv[1].includes("obom") && !args.type) {
|
|
|
527
635
|
"Ok, the user wants to generate an Operations Bill-of-Materials (OBOM).",
|
|
528
636
|
);
|
|
529
637
|
}
|
|
638
|
+
if (process.argv[1].includes("spdxgen") && !args.format) {
|
|
639
|
+
args.format = "spdx";
|
|
640
|
+
thoughtLog("Ok, defaulting the export format to SPDX.");
|
|
641
|
+
}
|
|
530
642
|
|
|
531
643
|
/**
|
|
532
644
|
* Command line options
|
|
@@ -539,19 +651,23 @@ const options = Object.assign({}, args, {
|
|
|
539
651
|
deep: args.deep || args.evidence,
|
|
540
652
|
output:
|
|
541
653
|
isSecureMode && args.output === "bom.json"
|
|
542
|
-
?
|
|
654
|
+
? sourceInputIsRemoteOrPurl
|
|
655
|
+
? resolve(args.output)
|
|
656
|
+
: resolve(join(filePath, args.output))
|
|
543
657
|
: args.output,
|
|
544
658
|
exclude: args.exclude || args.excludeRegex,
|
|
545
659
|
include: args.include || args.includeRegex,
|
|
546
660
|
});
|
|
547
|
-
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
)
|
|
554
|
-
|
|
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
|
+
}
|
|
555
671
|
}
|
|
556
672
|
// Filter duplicate types. Eg: -t gradle -t gradle
|
|
557
673
|
if (options.projectType && Array.isArray(options.projectType)) {
|
|
@@ -911,6 +1027,129 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
911
1027
|
safeExistsSync(process.env.SBOM_SIGN_PRIVATE_KEY)) ||
|
|
912
1028
|
process.env.SBOM_SIGN_PRIVATE_KEY_BASE64));
|
|
913
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
|
+
|
|
914
1153
|
/**
|
|
915
1154
|
* Method to start the bom creation process
|
|
916
1155
|
*/
|
|
@@ -936,24 +1175,130 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
936
1175
|
const serverModule = await import("../lib/server/server.js");
|
|
937
1176
|
return serverModule.start(options);
|
|
938
1177
|
}
|
|
939
|
-
|
|
940
|
-
|
|
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)) {
|
|
941
1231
|
if (isSecureMode) {
|
|
942
1232
|
process.exit(1);
|
|
943
1233
|
}
|
|
944
1234
|
return;
|
|
945
1235
|
}
|
|
946
|
-
|
|
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);
|
|
947
1271
|
thoughtLog("Getting ready to generate the BOM ⚡️.");
|
|
948
|
-
|
|
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
|
+
}
|
|
949
1286
|
if (bomNSData?.bomJson) {
|
|
950
1287
|
thoughtLog(
|
|
951
1288
|
"Tweaking the generated BOM data with useful annotations and properties.",
|
|
952
1289
|
);
|
|
953
1290
|
}
|
|
954
1291
|
// Add extra metadata and annotations with post processing
|
|
955
|
-
bomNSData = postProcess(bomNSData, options,
|
|
1292
|
+
bomNSData = postProcess(bomNSData, options, srcDir);
|
|
956
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
|
+
);
|
|
957
1302
|
const {
|
|
958
1303
|
auditBom,
|
|
959
1304
|
formatAnnotations,
|
|
@@ -981,159 +1326,124 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
981
1326
|
console.error(
|
|
982
1327
|
"Review findings above or adjust --bom-audit-fail-severity to proceed.",
|
|
983
1328
|
);
|
|
1329
|
+
if (cleanup) {
|
|
1330
|
+
cleanupSourceDir(srcDir);
|
|
1331
|
+
}
|
|
984
1332
|
process.exit(1);
|
|
985
1333
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
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
|
-
privateKeyB64File,
|
|
1044
|
-
Buffer.from(privateKey, "utf8").toString("base64"),
|
|
1045
|
-
);
|
|
1046
|
-
console.log(
|
|
1047
|
-
"Created public/private key pairs for testing purposes",
|
|
1048
|
-
publicKeyFile,
|
|
1049
|
-
privateKeyFile,
|
|
1050
|
-
privateKeyB64File,
|
|
1051
|
-
);
|
|
1052
|
-
privateKeyToUse = privateKey;
|
|
1053
|
-
jwkPublicKey = crypto
|
|
1054
|
-
.createPublicKey(publicKey)
|
|
1055
|
-
.export({ format: "jwk" });
|
|
1056
|
-
} else {
|
|
1057
|
-
if (process.env?.SBOM_SIGN_PRIVATE_KEY) {
|
|
1058
|
-
privateKeyToUse = fs.readFileSync(
|
|
1059
|
-
process.env.SBOM_SIGN_PRIVATE_KEY,
|
|
1060
|
-
"utf8",
|
|
1061
|
-
);
|
|
1062
|
-
} else if (process.env?.SBOM_SIGN_PRIVATE_KEY_BASE64) {
|
|
1063
|
-
privateKeyToUse = Buffer.from(
|
|
1064
|
-
process.env.SBOM_SIGN_PRIVATE_KEY_BASE64,
|
|
1065
|
-
"base64",
|
|
1066
|
-
).toString("utf8");
|
|
1067
|
-
}
|
|
1068
|
-
if (
|
|
1069
|
-
process.env.SBOM_SIGN_PUBLIC_KEY &&
|
|
1070
|
-
safeExistsSync(process.env.SBOM_SIGN_PUBLIC_KEY)
|
|
1071
|
-
) {
|
|
1072
|
-
jwkPublicKey = crypto
|
|
1073
|
-
.createPublicKey(
|
|
1074
|
-
fs.readFileSync(process.env.SBOM_SIGN_PUBLIC_KEY, "utf8"),
|
|
1075
|
-
)
|
|
1076
|
-
.export({ format: "jwk" });
|
|
1077
|
-
} else if (process.env?.SBOM_SIGN_PUBLIC_KEY_BASE64) {
|
|
1078
|
-
jwkPublicKey = Buffer.from(
|
|
1079
|
-
process.env.SBOM_SIGN_PUBLIC_KEY_BASE64,
|
|
1080
|
-
"base64",
|
|
1081
|
-
).toString("utf8");
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
try {
|
|
1085
|
-
const bomJsonUnsignedObj = JSON.parse(jsonPayload);
|
|
1086
|
-
const signOptions = {
|
|
1087
|
-
privateKey: privateKeyToUse,
|
|
1088
|
-
algorithm: alg,
|
|
1089
|
-
publicKeyJwk: jwkPublicKey,
|
|
1090
|
-
mode: process.env.SBOM_SIGN_MODE || "replace",
|
|
1091
|
-
signComponents: true,
|
|
1092
|
-
signServices: true,
|
|
1093
|
-
signAnnotations: true,
|
|
1094
|
-
};
|
|
1095
|
-
thoughtLog(`Signing the BOM file "${jsonFile}".`);
|
|
1096
|
-
const signedBom = signBom(bomJsonUnsignedObj, signOptions);
|
|
1097
|
-
fs.writeFileSync(
|
|
1098
|
-
jsonFile,
|
|
1099
|
-
JSON.stringify(signedBom, null, options.jsonPretty ? 2 : null),
|
|
1100
|
-
);
|
|
1101
|
-
if (publicKeyFile) {
|
|
1102
|
-
const publicKeyStr = fs.readFileSync(publicKeyFile, "utf8");
|
|
1103
|
-
const signatureVerification = verifyBom(signedBom, publicKeyStr);
|
|
1104
|
-
if (signatureVerification) {
|
|
1105
|
-
console.log(
|
|
1106
|
-
"SBOM signature is verifiable natively with the public key and the algorithm",
|
|
1107
|
-
publicKeyFile,
|
|
1108
|
-
alg,
|
|
1109
|
-
);
|
|
1110
|
-
} else {
|
|
1111
|
-
console.log("SBOM signature verification was unsuccessful");
|
|
1112
|
-
console.log("Check if the public key was exported in PEM format");
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
} catch (ex) {
|
|
1116
|
-
console.log("SBOM signing was unsuccessful:", ex.message);
|
|
1117
|
-
console.log(
|
|
1118
|
-
"Check if the private key was exported in PEM format and the algorithm is JSF-compliant.",
|
|
1119
|
-
);
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
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();
|
|
1122
1391
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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");
|
|
1127
1400
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
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`,
|
|
1132
1415
|
);
|
|
1133
|
-
} else {
|
|
1134
|
-
console.log("Unable to produce BOM for", filePath);
|
|
1135
|
-
console.log("Try running the command with -t <type> or -r argument");
|
|
1136
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);
|
|
1433
|
+
}
|
|
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
|
+
);
|
|
1137
1447
|
}
|
|
1138
1448
|
// Evidence generation
|
|
1139
1449
|
if (options.evidence || options.includeCrypto) {
|
|
@@ -1145,7 +1455,7 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
1145
1455
|
options.projectType = options.projectType || ["java"];
|
|
1146
1456
|
const evinseOptions = {
|
|
1147
1457
|
_: args._,
|
|
1148
|
-
input: options.output,
|
|
1458
|
+
input: internalCycloneDxInputPath || options.output,
|
|
1149
1459
|
output: options.evinseOutput,
|
|
1150
1460
|
language: options.projectType,
|
|
1151
1461
|
skipMavenCollector: false,
|
|
@@ -1184,10 +1494,58 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
1184
1494
|
if (options.validate && bomNSData?.bomJson) {
|
|
1185
1495
|
thoughtLog("Wait, let's check the generated BOM file for any issues.");
|
|
1186
1496
|
if (!validateBom(bomNSData.bomJson)) {
|
|
1497
|
+
if (cleanup) {
|
|
1498
|
+
cleanupSourceDir(srcDir);
|
|
1499
|
+
}
|
|
1187
1500
|
process.exit(1);
|
|
1188
1501
|
}
|
|
1189
1502
|
thoughtLog("✅ BOM file looks valid.");
|
|
1190
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
|
+
}
|
|
1191
1549
|
thoughtEnd();
|
|
1192
1550
|
// Automatically submit the bom data
|
|
1193
1551
|
// biome-ignore lint/suspicious/noDoubleEquals: yargs passes true for empty values
|
|
@@ -1196,6 +1554,9 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
1196
1554
|
await submitBom(options, bomNSData.bomJson);
|
|
1197
1555
|
} catch (err) {
|
|
1198
1556
|
console.log(err);
|
|
1557
|
+
if (cleanup) {
|
|
1558
|
+
cleanupSourceDir(srcDir);
|
|
1559
|
+
}
|
|
1199
1560
|
process.exit(1);
|
|
1200
1561
|
}
|
|
1201
1562
|
}
|
|
@@ -1238,4 +1599,7 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
|
|
|
1238
1599
|
console.log(allowListSuggestion);
|
|
1239
1600
|
}
|
|
1240
1601
|
}
|
|
1602
|
+
if (cleanup) {
|
|
1603
|
+
cleanupSourceDir(srcDir);
|
|
1604
|
+
}
|
|
1241
1605
|
})();
|