@cyclonedx/cdxgen 12.1.4 → 12.2.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 +47 -39
- package/bin/cdxgen.js +181 -90
- package/bin/evinse.js +4 -4
- package/bin/repl.js +3 -3
- package/bin/sign.js +102 -0
- package/bin/validate.js +233 -0
- package/bin/verify.js +69 -28
- package/data/queries.json +1 -1
- package/data/rules/ci-permissions.yaml +186 -0
- package/data/rules/dependency-sources.yaml +123 -0
- package/data/rules/package-integrity.yaml +135 -0
- package/data/rules/vscode-extensions.yaml +228 -0
- package/lib/cli/index.js +484 -440
- package/lib/evinser/db.js +137 -0
- package/lib/{helpers → evinser}/db.poku.js +2 -6
- package/lib/evinser/evinser.js +5 -18
- package/lib/evinser/swiftsem.js +1 -1
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/bomSigner.poku.js +156 -0
- package/lib/helpers/caxa.js +1 -1
- package/lib/helpers/ciParsers/azurePipelines.js +295 -0
- package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
- package/lib/helpers/ciParsers/circleCi.js +286 -0
- package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
- package/lib/helpers/ciParsers/common.js +24 -0
- package/lib/helpers/ciParsers/githubActions.js +636 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
- package/lib/helpers/ciParsers/gitlabCi.js +213 -0
- package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
- package/lib/helpers/ciParsers/jenkins.js +181 -0
- package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
- package/lib/helpers/depsUtils.js +203 -0
- package/lib/helpers/depsUtils.poku.js +150 -0
- package/lib/helpers/display.js +429 -14
- package/lib/helpers/envcontext.js +23 -8
- package/lib/helpers/formulationParsers.js +351 -0
- package/lib/helpers/logger.js +14 -0
- package/lib/helpers/protobom.js +9 -9
- package/lib/helpers/pythonutils.js +305 -0
- package/lib/helpers/pythonutils.poku.js +469 -0
- package/lib/helpers/utils.js +970 -528
- package/lib/helpers/utils.poku.js +139 -256
- package/lib/helpers/versutils.js +202 -0
- package/lib/helpers/versutils.poku.js +315 -0
- package/lib/helpers/vsixutils.js +1061 -0
- package/lib/helpers/vsixutils.poku.js +2247 -0
- package/lib/managers/binary.js +19 -19
- package/lib/managers/docker.js +108 -1
- package/lib/managers/oci.js +10 -0
- package/lib/managers/piptree.js +4 -10
- package/lib/parsers/npmrc.js +92 -0
- package/lib/parsers/npmrc.poku.js +528 -0
- package/lib/server/openapi.yaml +1 -10
- package/lib/server/server.js +58 -16
- package/lib/server/server.poku.js +123 -144
- package/lib/stages/postgen/annotator.js +1 -1
- package/lib/stages/postgen/auditBom.js +197 -0
- package/lib/stages/postgen/auditBom.poku.js +378 -0
- package/lib/stages/postgen/postgen.js +54 -1
- package/lib/stages/postgen/postgen.poku.js +90 -1
- package/lib/stages/postgen/ruleEngine.js +369 -0
- package/lib/stages/pregen/envAudit.js +299 -0
- package/lib/stages/pregen/envAudit.poku.js +572 -0
- package/lib/stages/pregen/pregen.js +12 -8
- package/lib/third-party/arborist/lib/deepest-nesting-target.js +1 -1
- package/lib/third-party/arborist/lib/node.js +3 -3
- package/lib/third-party/arborist/lib/shrinkwrap.js +1 -1
- package/lib/third-party/arborist/lib/tree-check.js +1 -1
- package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
- package/lib/validator/complianceEngine.js +241 -0
- package/lib/validator/complianceEngine.poku.js +168 -0
- package/lib/validator/complianceRules.js +1610 -0
- package/lib/validator/complianceRules.poku.js +328 -0
- package/lib/validator/index.js +222 -0
- package/lib/validator/index.poku.js +144 -0
- package/lib/validator/reporters/annotations.js +121 -0
- package/lib/validator/reporters/console.js +149 -0
- package/lib/validator/reporters/index.js +41 -0
- package/lib/validator/reporters/json.js +37 -0
- package/lib/validator/reporters/sarif.js +184 -0
- package/lib/validator/reporters.poku.js +150 -0
- package/package.json +8 -8
- package/types/bin/sign.d.ts +3 -0
- package/types/bin/sign.d.ts.map +1 -0
- package/types/bin/validate.d.ts +3 -0
- package/types/bin/validate.d.ts.map +1 -0
- package/types/helpers/utils.d.ts +0 -1
- package/types/lib/cli/index.d.ts +49 -52
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/db.d.ts +34 -0
- package/types/lib/evinser/db.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +63 -16
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +27 -0
- package/types/lib/helpers/bomSigner.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/common.d.ts +11 -0
- package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts +21 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -0
- package/types/lib/helpers/display.d.ts +111 -11
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts +19 -7
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts +50 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
- package/types/lib/helpers/logger.d.ts +15 -1
- package/types/lib/helpers/logger.d.ts.map +1 -1
- package/types/lib/helpers/protobom.d.ts +2 -2
- package/types/lib/helpers/pythonutils.d.ts +18 -0
- package/types/lib/helpers/pythonutils.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +532 -128
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/versutils.d.ts +8 -0
- package/types/lib/helpers/versutils.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +130 -0
- package/types/lib/helpers/vsixutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +12 -31
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +11 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/managers/piptree.d.ts.map +1 -1
- package/types/lib/parsers/npmrc.d.ts +26 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -0
- package/types/lib/server/server.d.ts +21 -2
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +20 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +8 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
- package/types/lib/stages/pregen/envAudit.d.ts +8 -0
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -0
- package/types/lib/validator/complianceEngine.d.ts +66 -0
- package/types/lib/validator/complianceEngine.d.ts.map +1 -0
- package/types/lib/validator/complianceRules.d.ts +70 -0
- package/types/lib/validator/complianceRules.d.ts.map +1 -0
- package/types/lib/validator/index.d.ts +70 -0
- package/types/lib/validator/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/annotations.d.ts +31 -0
- package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
- package/types/lib/validator/reporters/console.d.ts +30 -0
- package/types/lib/validator/reporters/console.d.ts.map +1 -0
- package/types/lib/validator/reporters/index.d.ts +21 -0
- package/types/lib/validator/reporters/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/json.d.ts +11 -0
- package/types/lib/validator/reporters/json.d.ts.map +1 -0
- package/types/lib/validator/reporters/sarif.d.ts +16 -0
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
- package/lib/helpers/db.js +0 -162
- package/types/helpers/db.d.ts +0 -35
- package/types/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/db.d.ts +0 -35
- package/types/lib/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/validator.d.ts.map +0 -1
- package/types/managers/binary.d.ts +0 -37
- package/types/managers/binary.d.ts.map +0 -1
- package/types/managers/docker.d.ts +0 -56
- package/types/managers/docker.d.ts.map +0 -1
- package/types/managers/oci.d.ts +0 -2
- package/types/managers/oci.d.ts.map +0 -1
- package/types/managers/piptree.d.ts +0 -2
- package/types/managers/piptree.d.ts.map +0 -1
- package/types/server/server.d.ts +0 -34
- package/types/server/server.d.ts.map +0 -1
- package/types/stages/postgen/annotator.d.ts +0 -27
- package/types/stages/postgen/annotator.d.ts.map +0 -1
- package/types/stages/postgen/postgen.d.ts +0 -51
- package/types/stages/postgen/postgen.d.ts.map +0 -1
- package/types/stages/pregen/pregen.d.ts +0 -59
- package/types/stages/pregen/pregen.d.ts.map +0 -1
package/lib/helpers/display.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import process from "node:process";
|
|
3
4
|
|
|
4
5
|
import { createStream, table } from "table";
|
|
5
6
|
|
|
7
|
+
import { isSecureMode, safeExistsSync, toCamel } from "./utils.js";
|
|
8
|
+
|
|
6
9
|
// https://github.com/yangshun/tree-node-cli/blob/master/src/index.js
|
|
7
10
|
const SYMBOLS_ANSI = {
|
|
8
11
|
BRANCH: "├── ",
|
|
@@ -19,12 +22,22 @@ const highlightStr = (s, highlight) => {
|
|
|
19
22
|
}
|
|
20
23
|
return s;
|
|
21
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* Prints the BOM components as a streaming table to the console.
|
|
27
|
+
* Delegates to {@link printOSTable} automatically when the BOM metadata indicates
|
|
28
|
+
* an operating-system or platform component type.
|
|
29
|
+
*
|
|
30
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
31
|
+
* @param {string[]} [filterTypes] Optional list of component types to include; all types shown when omitted
|
|
32
|
+
* @param {string} [highlight] Optional string to highlight in the output
|
|
33
|
+
* @returns {void}
|
|
34
|
+
*/
|
|
22
35
|
export function printTable(
|
|
23
36
|
bomJson,
|
|
24
37
|
filterTypes = undefined,
|
|
25
38
|
highlight = undefined,
|
|
26
39
|
) {
|
|
27
|
-
if (!bomJson
|
|
40
|
+
if (!bomJson?.components) {
|
|
28
41
|
return;
|
|
29
42
|
}
|
|
30
43
|
if (
|
|
@@ -98,6 +111,12 @@ const formatProps = (props) => {
|
|
|
98
111
|
}
|
|
99
112
|
return retList.join("\n");
|
|
100
113
|
};
|
|
114
|
+
/**
|
|
115
|
+
* Prints OS package components from the BOM as a formatted streaming table.
|
|
116
|
+
*
|
|
117
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
118
|
+
* @returns {void}
|
|
119
|
+
*/
|
|
101
120
|
export function printOSTable(bomJson) {
|
|
102
121
|
const config = {
|
|
103
122
|
columnDefault: {
|
|
@@ -118,9 +137,16 @@ export function printOSTable(bomJson) {
|
|
|
118
137
|
}
|
|
119
138
|
console.log();
|
|
120
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Prints the services listed in the BOM as a formatted table.
|
|
142
|
+
* Includes endpoint URLs, authentication flag, and cross-trust-boundary flag.
|
|
143
|
+
*
|
|
144
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
145
|
+
* @returns {void}
|
|
146
|
+
*/
|
|
121
147
|
export function printServices(bomJson) {
|
|
122
148
|
const data = [["Name", "Endpoints", "Authenticated", "X Trust Boundary"]];
|
|
123
|
-
if (!bomJson
|
|
149
|
+
if (!bomJson?.services) {
|
|
124
150
|
return;
|
|
125
151
|
}
|
|
126
152
|
for (const aservice of bomJson.services) {
|
|
@@ -142,9 +168,15 @@ export function printServices(bomJson) {
|
|
|
142
168
|
}
|
|
143
169
|
}
|
|
144
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Prints the formulation components from the BOM as a formatted table.
|
|
173
|
+
*
|
|
174
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
175
|
+
* @returns {void}
|
|
176
|
+
*/
|
|
145
177
|
export function printFormulation(bomJson) {
|
|
146
|
-
const data = [["
|
|
147
|
-
if (!bomJson
|
|
178
|
+
const data = [["Type", "Name", "Version"]];
|
|
179
|
+
if (!bomJson?.formulation) {
|
|
148
180
|
return;
|
|
149
181
|
}
|
|
150
182
|
for (const aform of bomJson.formulation) {
|
|
@@ -178,8 +210,15 @@ const locationComparator = (a, b) => {
|
|
|
178
210
|
return a.localeCompare(b);
|
|
179
211
|
};
|
|
180
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Prints component evidence occurrences (file locations) as a streaming table.
|
|
215
|
+
* Only components that have `evidence.occurrences` are included.
|
|
216
|
+
*
|
|
217
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
218
|
+
* @returns {void}
|
|
219
|
+
*/
|
|
181
220
|
export function printOccurrences(bomJson) {
|
|
182
|
-
if (!bomJson
|
|
221
|
+
if (!bomJson?.components) {
|
|
183
222
|
return;
|
|
184
223
|
}
|
|
185
224
|
const data = ["Group", "Name", "Version", "Occurrences"];
|
|
@@ -217,17 +256,20 @@ export function printOccurrences(bomJson) {
|
|
|
217
256
|
console.log();
|
|
218
257
|
}
|
|
219
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Prints the call stack evidence for each component in the BOM as a formatted table.
|
|
261
|
+
* Only components that have `evidence.callstack.frames` are included.
|
|
262
|
+
*
|
|
263
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
264
|
+
* @returns {void}
|
|
265
|
+
*/
|
|
220
266
|
export function printCallStack(bomJson) {
|
|
221
267
|
const data = [["Group", "Name", "Version", "Call Stack"]];
|
|
222
|
-
if (!bomJson
|
|
268
|
+
if (!bomJson?.components) {
|
|
223
269
|
return;
|
|
224
270
|
}
|
|
225
271
|
for (const comp of bomJson.components) {
|
|
226
|
-
if (
|
|
227
|
-
!comp.evidence ||
|
|
228
|
-
!comp.evidence.callstack ||
|
|
229
|
-
!comp.evidence.callstack.frames
|
|
230
|
-
) {
|
|
272
|
+
if (!comp.evidence?.callstack?.frames) {
|
|
231
273
|
continue;
|
|
232
274
|
}
|
|
233
275
|
const frames = Array.from(
|
|
@@ -264,6 +306,15 @@ export function printCallStack(bomJson) {
|
|
|
264
306
|
console.log(table(data, config));
|
|
265
307
|
}
|
|
266
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Prints the dependency tree from the BOM as an ASCII tree diagram.
|
|
311
|
+
* Uses the `table` library for small trees and plain console output for larger ones.
|
|
312
|
+
*
|
|
313
|
+
* @param {Object} bomJson CycloneDX BOM JSON object containing a `dependencies` array
|
|
314
|
+
* @param {string} [mode="dependsOn"] Dependency relation to traverse (`"dependsOn"` or `"provides"`)
|
|
315
|
+
* @param {string} [highlight] Optional string to highlight in the tree output
|
|
316
|
+
* @returns {void}
|
|
317
|
+
*/
|
|
267
318
|
export function printDependencyTree(
|
|
268
319
|
bomJson,
|
|
269
320
|
mode = "dependsOn",
|
|
@@ -366,9 +417,16 @@ const recursePrint = (depMap, subtree, level, shownList, treeGraphics) => {
|
|
|
366
417
|
}
|
|
367
418
|
};
|
|
368
419
|
|
|
420
|
+
/**
|
|
421
|
+
* Prints a table of reachable components derived from a reachability slices file.
|
|
422
|
+
* Aggregates per-purl reachable-flow counts and sorts them descending.
|
|
423
|
+
*
|
|
424
|
+
* @param {Object} sliceArtefacts Slice artefact paths, must include `reachablesSlicesFile`
|
|
425
|
+
* @returns {void}
|
|
426
|
+
*/
|
|
369
427
|
export function printReachables(sliceArtefacts) {
|
|
370
428
|
const reachablesSlicesFile = sliceArtefacts.reachablesSlicesFile;
|
|
371
|
-
if (!
|
|
429
|
+
if (!safeExistsSync(reachablesSlicesFile)) {
|
|
372
430
|
return;
|
|
373
431
|
}
|
|
374
432
|
const purlCounts = {};
|
|
@@ -402,6 +460,12 @@ export function printReachables(sliceArtefacts) {
|
|
|
402
460
|
}
|
|
403
461
|
}
|
|
404
462
|
|
|
463
|
+
/**
|
|
464
|
+
* Prints a formatted table of CycloneDX vulnerability objects.
|
|
465
|
+
*
|
|
466
|
+
* @param {Object[]} vulnerabilities Array of CycloneDX vulnerability objects
|
|
467
|
+
* @returns {void}
|
|
468
|
+
*/
|
|
405
469
|
export function printVulnerabilities(vulnerabilities) {
|
|
406
470
|
if (!vulnerabilities) {
|
|
407
471
|
return;
|
|
@@ -430,6 +494,14 @@ export function printVulnerabilities(vulnerabilities) {
|
|
|
430
494
|
console.log(`${vulnerabilities.length} vulnerabilities found.`);
|
|
431
495
|
}
|
|
432
496
|
|
|
497
|
+
/**
|
|
498
|
+
* Prints an OWASP donation banner when running in a CI environment.
|
|
499
|
+
* The banner is suppressed when `options.noBanner` is set or the repository
|
|
500
|
+
* belongs to the cdxgen project itself.
|
|
501
|
+
*
|
|
502
|
+
* @param {Object} options CLI options
|
|
503
|
+
* @returns {void}
|
|
504
|
+
*/
|
|
433
505
|
export function printSponsorBanner(options) {
|
|
434
506
|
if (
|
|
435
507
|
process?.env?.CI &&
|
|
@@ -443,7 +515,7 @@ export function printSponsorBanner(options) {
|
|
|
443
515
|
},
|
|
444
516
|
};
|
|
445
517
|
let message =
|
|
446
|
-
"OWASP foundation relies on donations to fund our projects.\nDonation link: https://owasp.org/donate/?reponame=www-project-
|
|
518
|
+
"OWASP foundation relies on donations to fund our projects.\nDonation link: https://owasp.org/donate/?reponame=www-project-cdxgen&title=OWASP+cdxgen";
|
|
447
519
|
if (options.serverUrl && options.apiKey) {
|
|
448
520
|
message = `${message}\nDependency Track: https://owasp.org/donate/?reponame=www-project-dependency-track&title=OWASP+Dependency-Track`;
|
|
449
521
|
}
|
|
@@ -452,6 +524,13 @@ export function printSponsorBanner(options) {
|
|
|
452
524
|
}
|
|
453
525
|
}
|
|
454
526
|
|
|
527
|
+
/**
|
|
528
|
+
* Prints a BOM summary table including generator tool names, component package types,
|
|
529
|
+
* and component namespaces extracted from BOM metadata properties.
|
|
530
|
+
*
|
|
531
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
532
|
+
* @returns {void}
|
|
533
|
+
*/
|
|
455
534
|
export function printSummary(bomJson) {
|
|
456
535
|
const config = {
|
|
457
536
|
header: {
|
|
@@ -501,3 +580,339 @@ export function printSummary(bomJson) {
|
|
|
501
580
|
const data = [[message]];
|
|
502
581
|
console.log(table(data, config));
|
|
503
582
|
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* @typedef {{type: string, variable: string, severity: string, message: string, mitigation: string}} EnvAuditFinding
|
|
586
|
+
*/
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Runs the pre-generation environment audit and renders the results as formatted
|
|
590
|
+
* tables to the console. Called when the --env-audit CLI flag is set.
|
|
591
|
+
*
|
|
592
|
+
* @param {string} filePath Project path being scanned
|
|
593
|
+
* @param {Object} config Loaded .cdxgenrc / config-file values
|
|
594
|
+
* @param {Object} options Effective CLI options
|
|
595
|
+
* @param {EnvAuditFinding[]} envAuditFindings Audit findings to display
|
|
596
|
+
*/
|
|
597
|
+
export function displaySelfThreatModel(
|
|
598
|
+
filePath,
|
|
599
|
+
config,
|
|
600
|
+
options,
|
|
601
|
+
envAuditFindings,
|
|
602
|
+
) {
|
|
603
|
+
const TLP = options.tlpClassification || "CLEAR";
|
|
604
|
+
const risks = [];
|
|
605
|
+
let riskScore = 0;
|
|
606
|
+
|
|
607
|
+
const addRisk = (level, reason, category = "configuration") => {
|
|
608
|
+
const scores = { low: 1, medium: 3, high: 5, critical: 8 };
|
|
609
|
+
riskScore = Math.min(10, riskScore + scores[level]);
|
|
610
|
+
risks.push({ level, reason, category });
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// Config file risks
|
|
614
|
+
if (Object.keys(config).length > 0) {
|
|
615
|
+
addRisk(
|
|
616
|
+
"medium",
|
|
617
|
+
"A .cdxgenrc config file was loaded from the working directory. It may override security-relevant settings without being visible on the command line.",
|
|
618
|
+
"configuration",
|
|
619
|
+
);
|
|
620
|
+
const sensitive = ["server-url", "api-key", "include-formulation"];
|
|
621
|
+
for (const key of sensitive) {
|
|
622
|
+
if (config[key] || config[toCamel(key)]) {
|
|
623
|
+
addRisk(
|
|
624
|
+
key === "api-key" ? "high" : "medium",
|
|
625
|
+
`Config file sets '${key}', which affects SBOM content or remote submission behavior.`,
|
|
626
|
+
"configuration",
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Remote submission risks
|
|
633
|
+
if (options.serverUrl) {
|
|
634
|
+
const isHttps = options.serverUrl.startsWith("https://");
|
|
635
|
+
addRisk(
|
|
636
|
+
isHttps ? "medium" : "critical",
|
|
637
|
+
`SBOM will be submitted to ${options.serverUrl}${!isHttps ? " over plain HTTP — contents may be intercepted or tampered in transit." : "."}`,
|
|
638
|
+
"network",
|
|
639
|
+
);
|
|
640
|
+
if (options.skipDtTlsCheck) {
|
|
641
|
+
addRisk(
|
|
642
|
+
"high",
|
|
643
|
+
"TLS certificate validation is disabled for Dependency-Track uploads. SBOM contents may be intercepted or tampered in transit.",
|
|
644
|
+
"network",
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Data exposure risks
|
|
650
|
+
if (options.includeFormulation) {
|
|
651
|
+
addRisk(
|
|
652
|
+
"medium",
|
|
653
|
+
"Formulation mode is active. The SBOM will include build metadata such as git history, committer identities, and CI environment variables.",
|
|
654
|
+
"data-exposure",
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
if (options.evidence || options.deep) {
|
|
658
|
+
addRisk(
|
|
659
|
+
"medium",
|
|
660
|
+
"Evidence / deep mode will invoke build tools and parse source files to collect call graph and reachability evidence. Malicious build scripts may execute.",
|
|
661
|
+
"data-exposure",
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
if (options.installDeps) {
|
|
665
|
+
addRisk(
|
|
666
|
+
"high",
|
|
667
|
+
"Dependency auto-install is enabled. Lifecycle hooks (install scripts) from third-party packages will execute in the current environment.",
|
|
668
|
+
"data-exposure",
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Output path outside the project directory
|
|
673
|
+
if (options.output) {
|
|
674
|
+
const resolvedOutput = path.resolve(options.output);
|
|
675
|
+
const resolvedProject = path.resolve(filePath);
|
|
676
|
+
if (
|
|
677
|
+
!resolvedOutput.startsWith(resolvedProject + path.sep) &&
|
|
678
|
+
resolvedOutput !== resolvedProject
|
|
679
|
+
) {
|
|
680
|
+
addRisk(
|
|
681
|
+
"medium",
|
|
682
|
+
`Output path '${options.output}' resolves to '${resolvedOutput}', which is outside the project directory '${resolvedProject}'. Ensure this is intentional.`,
|
|
683
|
+
"configuration",
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Environment variable risks (config-layer only; env-audit covers the rest)
|
|
689
|
+
if (process.env.CDXGEN_SERVER_URL) {
|
|
690
|
+
addRisk(
|
|
691
|
+
"low",
|
|
692
|
+
"CDXGEN_SERVER_URL is set in the environment and will override any --server-url value.",
|
|
693
|
+
"environment",
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Integrate environment audit findings
|
|
698
|
+
if (envAuditFindings?.length) {
|
|
699
|
+
for (const f of envAuditFindings) {
|
|
700
|
+
const categoryMap = {
|
|
701
|
+
"code-execution": "runtime",
|
|
702
|
+
"debug-exposure": "runtime",
|
|
703
|
+
"environment-variable": "environment",
|
|
704
|
+
"network-interception": "network",
|
|
705
|
+
"credential-exposure": "environment",
|
|
706
|
+
"permission-misuse": "runtime",
|
|
707
|
+
privilege: "runtime",
|
|
708
|
+
};
|
|
709
|
+
addRisk(
|
|
710
|
+
f.severity,
|
|
711
|
+
`${f.variable}: ${f.message}`,
|
|
712
|
+
categoryMap[f.type] || "configuration",
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const nodeOptions = process.env.NODE_OPTIONS || "";
|
|
718
|
+
const riskLevel =
|
|
719
|
+
riskScore >= 8
|
|
720
|
+
? "CRITICAL"
|
|
721
|
+
: riskScore >= 5
|
|
722
|
+
? "HIGH"
|
|
723
|
+
: riskScore >= 3
|
|
724
|
+
? "MEDIUM"
|
|
725
|
+
: "LOW";
|
|
726
|
+
|
|
727
|
+
const riskColor = {
|
|
728
|
+
CRITICAL: "\x1b[1;31m",
|
|
729
|
+
HIGH: "\x1b[1;33m",
|
|
730
|
+
MEDIUM: "\x1b[1;36m",
|
|
731
|
+
LOW: "\x1b[1;32m",
|
|
732
|
+
};
|
|
733
|
+
const reset = "\x1b[0m";
|
|
734
|
+
const tlpGuidance = {
|
|
735
|
+
CLEAR: "May be shared publicly. No restrictions.",
|
|
736
|
+
GREEN: "Limited to community/peers. Not for public posting.",
|
|
737
|
+
AMBER:
|
|
738
|
+
"Limited to organisation and trusted partners. Handle-in-confidence.",
|
|
739
|
+
AMBER_AND_STRICT: "Organisation only. No external sharing.",
|
|
740
|
+
RED: "Named recipients only. Do not forward or store beyond session.",
|
|
741
|
+
};
|
|
742
|
+
const headerData = [
|
|
743
|
+
["TLP Classification", `${TLP} — ${tlpGuidance[TLP]}`],
|
|
744
|
+
["Risk Score", `${riskScore}/10`],
|
|
745
|
+
["Risk Level", `${riskColor[riskLevel]}${riskLevel}${reset}`],
|
|
746
|
+
];
|
|
747
|
+
const headerConfig = {
|
|
748
|
+
header: {
|
|
749
|
+
alignment: "center",
|
|
750
|
+
content:
|
|
751
|
+
"SBOM Generation Environment Assessment\nPre-generation security audit by cdxgen",
|
|
752
|
+
},
|
|
753
|
+
columns: [{ width: 30, alignment: "right" }, { width: 70 }],
|
|
754
|
+
columnDefault: { wrapWord: true },
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
console.log(table(headerData, headerConfig));
|
|
758
|
+
if (risks.length > 0) {
|
|
759
|
+
const findingsData = [["#", "Severity", "Category", "Finding"]];
|
|
760
|
+
risks.forEach(({ level, reason, category }, i) => {
|
|
761
|
+
const severityColor =
|
|
762
|
+
level === "critical"
|
|
763
|
+
? "\x1b[1;31m"
|
|
764
|
+
: level === "high"
|
|
765
|
+
? "\x1b[1;33m"
|
|
766
|
+
: level === "medium"
|
|
767
|
+
? "\x1b[1;36m"
|
|
768
|
+
: "\x1b[1;32m";
|
|
769
|
+
findingsData.push([
|
|
770
|
+
`${i + 1}`,
|
|
771
|
+
`${severityColor}${level.toUpperCase()}${reset}`,
|
|
772
|
+
category,
|
|
773
|
+
reason,
|
|
774
|
+
]);
|
|
775
|
+
});
|
|
776
|
+
const findingsConfig = {
|
|
777
|
+
header: {
|
|
778
|
+
alignment: "center",
|
|
779
|
+
content: `Findings (${risks.length})`,
|
|
780
|
+
},
|
|
781
|
+
columns: [
|
|
782
|
+
{ width: 5, alignment: "right" },
|
|
783
|
+
{ width: 12 },
|
|
784
|
+
{ width: 17 },
|
|
785
|
+
{ width: 66 },
|
|
786
|
+
],
|
|
787
|
+
columnDefault: { wrapWord: true },
|
|
788
|
+
};
|
|
789
|
+
console.log(table(findingsData, findingsConfig));
|
|
790
|
+
} else {
|
|
791
|
+
const noFindingsData = [
|
|
792
|
+
[
|
|
793
|
+
`${riskColor[riskLevel]}✅ No risks detected in the current configuration.${reset}`,
|
|
794
|
+
],
|
|
795
|
+
];
|
|
796
|
+
const noFindingsConfig = {
|
|
797
|
+
header: { alignment: "center", content: "📋 Findings" },
|
|
798
|
+
columns: [{ width: 100, alignment: "center" }],
|
|
799
|
+
};
|
|
800
|
+
console.log(table(noFindingsData, noFindingsConfig));
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const configData = [
|
|
804
|
+
["Setting", "Value"],
|
|
805
|
+
["Project", options.projectName || filePath],
|
|
806
|
+
["Type(s)", options.projectType?.join(", ") || "auto-detect"],
|
|
807
|
+
["Profile", options.profile || "generic"],
|
|
808
|
+
["Path", filePath],
|
|
809
|
+
["Output", options.output || "(stdout)"],
|
|
810
|
+
["Recursive", options.recursive ? "yes" : "no"],
|
|
811
|
+
["Remote Submission", options.serverUrl || "none"],
|
|
812
|
+
["Formulation", options.includeFormulation ? "yes" : "no"],
|
|
813
|
+
["Evidence / Deep Mode", options.evidence || options.deep ? "yes" : "no"],
|
|
814
|
+
["Auto-install Dependencies", options.installDeps ? "yes" : "no"],
|
|
815
|
+
["NODE_OPTIONS", nodeOptions || "(not set)"],
|
|
816
|
+
];
|
|
817
|
+
const effConfigTableConfig = {
|
|
818
|
+
header: { alignment: "center", content: "Effective Configuration" },
|
|
819
|
+
columns: [{ width: 28 }, { width: 72 }],
|
|
820
|
+
columnDefault: { wrapWord: true },
|
|
821
|
+
};
|
|
822
|
+
console.log(table(configData, effConfigTableConfig));
|
|
823
|
+
|
|
824
|
+
const recommendations = [];
|
|
825
|
+
if (["AMBER", "AMBER_AND_STRICT", "RED"].includes(TLP)) {
|
|
826
|
+
recommendations.push([
|
|
827
|
+
"High",
|
|
828
|
+
"Omit --include-formulation to avoid embedding committer identities and CI secrets in the SBOM.",
|
|
829
|
+
]);
|
|
830
|
+
if (TLP === "RED") {
|
|
831
|
+
recommendations.push([
|
|
832
|
+
"Critical",
|
|
833
|
+
"Run cdxgen inside an isolated container or VM with no access to production credentials.",
|
|
834
|
+
]);
|
|
835
|
+
recommendations.push([
|
|
836
|
+
"Critical",
|
|
837
|
+
"Do not set --server-url; review and handle the output SBOM manually before sharing.",
|
|
838
|
+
]);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (riskScore >= 5) {
|
|
842
|
+
recommendations.push([
|
|
843
|
+
"High",
|
|
844
|
+
"Address the findings above before scanning untrusted repositories.",
|
|
845
|
+
]);
|
|
846
|
+
recommendations.push([
|
|
847
|
+
"Medium",
|
|
848
|
+
"Pass --no-install-deps to prevent package manager hooks from executing.",
|
|
849
|
+
]);
|
|
850
|
+
}
|
|
851
|
+
if (envAuditFindings.some((f) => f.type === "code-execution")) {
|
|
852
|
+
recommendations.push([
|
|
853
|
+
"High",
|
|
854
|
+
"Remove code-execution flags (--require, --eval, --loader, --import) from NODE_OPTIONS and JAVA_TOOL_OPTIONS.",
|
|
855
|
+
]);
|
|
856
|
+
}
|
|
857
|
+
if (envAuditFindings.some((f) => f.variable === "NODE_PATH")) {
|
|
858
|
+
recommendations.push([
|
|
859
|
+
"High",
|
|
860
|
+
"Unset NODE_PATH to prevent module-resolution poisoning by malicious packages.",
|
|
861
|
+
]);
|
|
862
|
+
}
|
|
863
|
+
if (envAuditFindings.some((f) => f.type === "privilege")) {
|
|
864
|
+
recommendations.push([
|
|
865
|
+
"High",
|
|
866
|
+
"Do not run cdxgen as root. Create a dedicated low-privilege user or use a rootless container.",
|
|
867
|
+
]);
|
|
868
|
+
}
|
|
869
|
+
if (/--permission\b/i.test(nodeOptions)) {
|
|
870
|
+
recommendations.push([
|
|
871
|
+
"Medium",
|
|
872
|
+
"Audit every --allow-* scope; use absolute paths rather than wildcards to minimise the permission surface.",
|
|
873
|
+
]);
|
|
874
|
+
}
|
|
875
|
+
recommendations.push([
|
|
876
|
+
"Info",
|
|
877
|
+
"Minimal safe invocation: cdxgen --no-install-deps --output ./sbom.cdx.json <path>",
|
|
878
|
+
]);
|
|
879
|
+
const recommendationsData = [["Priority", "Action"]];
|
|
880
|
+
recommendations.forEach(([priority, action]) => {
|
|
881
|
+
const priorityColor =
|
|
882
|
+
priority === "Critical"
|
|
883
|
+
? "\x1b[1;31m"
|
|
884
|
+
: priority === "High"
|
|
885
|
+
? "\x1b[1;33m"
|
|
886
|
+
: priority === "Medium"
|
|
887
|
+
? "\x1b[1;36m"
|
|
888
|
+
: "\x1b[1;32m";
|
|
889
|
+
recommendationsData.push([`${priorityColor}${priority}${reset}`, action]);
|
|
890
|
+
});
|
|
891
|
+
const recommendationsConfig = {
|
|
892
|
+
header: {
|
|
893
|
+
alignment: "center",
|
|
894
|
+
content: `Recommendations for TLP:${TLP}`,
|
|
895
|
+
},
|
|
896
|
+
columns: [{ width: 12 }, { width: 88 }],
|
|
897
|
+
columnDefault: { wrapWord: true },
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
console.log(table(recommendationsData, recommendationsConfig));
|
|
901
|
+
// Only abort in secure mode when at least one finding is high or critical severity.
|
|
902
|
+
// Accumulated low/medium findings may push riskScore above 5 but should not abort.
|
|
903
|
+
if (
|
|
904
|
+
isSecureMode &&
|
|
905
|
+
envAuditFindings?.some((f) => ["high", "critical"].includes(f.severity))
|
|
906
|
+
) {
|
|
907
|
+
const abortData = [
|
|
908
|
+
[
|
|
909
|
+
`${riskColor[riskLevel]}🚫 SECURE MODE: High-risk configuration detected. Aborting SBOM generation.${reset}`,
|
|
910
|
+
],
|
|
911
|
+
];
|
|
912
|
+
const abortConfig = {
|
|
913
|
+
columns: [{ width: 100, alignment: "center" }],
|
|
914
|
+
};
|
|
915
|
+
console.log(table(abortData, abortConfig));
|
|
916
|
+
process.exit(1);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
@@ -30,13 +30,14 @@ export const GIT_COMMAND = process.env.GIT_CMD || "git";
|
|
|
30
30
|
// sdkman tool aliases
|
|
31
31
|
export const SDKMAN_JAVA_TOOL_ALIASES = {
|
|
32
32
|
java8: process.env.JAVA8_TOOL || "8.0.452-amzn", // Temurin no longer offers java8 :(
|
|
33
|
-
java11: process.env.JAVA11_TOOL || "11.0.
|
|
34
|
-
java17: process.env.JAVA17_TOOL || "17.0.
|
|
35
|
-
java21: process.env.JAVA21_TOOL || "21.0.
|
|
33
|
+
java11: process.env.JAVA11_TOOL || "11.0.30-tem",
|
|
34
|
+
java17: process.env.JAVA17_TOOL || "17.0.18-tem",
|
|
35
|
+
java21: process.env.JAVA21_TOOL || "21.0.10-tem",
|
|
36
36
|
java22: process.env.JAVA22_TOOL || "22.0.2-tem",
|
|
37
37
|
java23: process.env.JAVA23_TOOL || "23.0.2-tem",
|
|
38
38
|
java24: process.env.JAVA24_TOOL || "24.0.2-tem",
|
|
39
|
-
java25: process.env.JAVA25_TOOL || "25.0.
|
|
39
|
+
java25: process.env.JAVA25_TOOL || "25.0.2-tem",
|
|
40
|
+
java26: process.env.JAVA26_TOOL || "26-tem",
|
|
40
41
|
};
|
|
41
42
|
|
|
42
43
|
/**
|
|
@@ -206,7 +207,7 @@ export function collectPythonInfo(dir) {
|
|
|
206
207
|
]);
|
|
207
208
|
const moduleDesc =
|
|
208
209
|
getCommandOutput(getPythonCommand(), dir, [
|
|
209
|
-
"-
|
|
210
|
+
"-I",
|
|
210
211
|
"-m",
|
|
211
212
|
"pip",
|
|
212
213
|
"--version",
|
|
@@ -257,7 +258,7 @@ export function collectGccInfo(dir) {
|
|
|
257
258
|
type: "platform",
|
|
258
259
|
name: "gcc",
|
|
259
260
|
version: versionDesc.split("\n")[0],
|
|
260
|
-
description: moduleDesc.replaceAll("\n", "\\n"),
|
|
261
|
+
description: (moduleDesc || "").replaceAll("\n", "\\n"),
|
|
261
262
|
};
|
|
262
263
|
}
|
|
263
264
|
return undefined;
|
|
@@ -277,7 +278,7 @@ export function collectRustInfo(dir) {
|
|
|
277
278
|
type: "platform",
|
|
278
279
|
name: "rustc",
|
|
279
280
|
version: versionDesc.trim(),
|
|
280
|
-
description: moduleDesc.trim(),
|
|
281
|
+
description: (moduleDesc || "").trim(),
|
|
281
282
|
};
|
|
282
283
|
}
|
|
283
284
|
return undefined;
|
|
@@ -405,6 +406,12 @@ const getCommandOutput = (cmd, dir, args) => {
|
|
|
405
406
|
}
|
|
406
407
|
if (DEBUG_MODE) {
|
|
407
408
|
if (dir) {
|
|
409
|
+
if (safeExistsSync(join(dir, commandToUse))) {
|
|
410
|
+
console.warn(
|
|
411
|
+
`SECURE MODE: Found ${commandToUse} inside ${dir}. This command will not be executed.`,
|
|
412
|
+
);
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
408
415
|
console.log(`Executing ${commandToUse} in ${dir}`);
|
|
409
416
|
} else {
|
|
410
417
|
console.log(`Executing ${commandToUse}`);
|
|
@@ -417,7 +424,7 @@ const getCommandOutput = (cmd, dir, args) => {
|
|
|
417
424
|
});
|
|
418
425
|
const stdout = result.stdout ? result.stdout.toString() : "";
|
|
419
426
|
const stderr = result.stderr ? result.stderr.toString() : "";
|
|
420
|
-
return `${stdout}
|
|
427
|
+
return `${stdout}\n${stderr}`.trim() || undefined;
|
|
421
428
|
};
|
|
422
429
|
|
|
423
430
|
/**
|
|
@@ -690,6 +697,14 @@ export function isRbenvAvailable() {
|
|
|
690
697
|
}
|
|
691
698
|
}
|
|
692
699
|
|
|
700
|
+
/**
|
|
701
|
+
* Returns the rbenv binary directory for the given Ruby version.
|
|
702
|
+
* Respects the `RBENV_ROOT` environment variable when set; otherwise falls back
|
|
703
|
+
* to `~/.rbenv/versions/<rubyVersion>/bin`.
|
|
704
|
+
*
|
|
705
|
+
* @param {string} rubyVersion Ruby version string (e.g. `"3.2.2"`)
|
|
706
|
+
* @returns {string} Absolute path to the rbenv bin directory for that version
|
|
707
|
+
*/
|
|
693
708
|
export function rubyVersionDir(rubyVersion) {
|
|
694
709
|
return process.env.RBENV_ROOT
|
|
695
710
|
? join(process.env.RBENV_ROOT, "versions", rubyVersion, "bin")
|