@cyclonedx/cdxgen 12.1.5 → 12.2.1
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 +51 -40
- package/bin/cdxgen.js +194 -97
- package/bin/evinse.js +4 -4
- package/bin/repl.js +1 -1
- 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 +449 -429
- package/lib/cli/index.poku.js +117 -0
- package/lib/evinser/db.js +137 -0
- package/lib/{helpers → evinser}/db.poku.js +2 -6
- package/lib/evinser/evinser.js +2 -14
- package/lib/helpers/analyzer.js +606 -3
- package/lib/helpers/analyzer.poku.js +230 -0
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/bomSigner.poku.js +156 -0
- 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 +219 -0
- package/lib/helpers/depsUtils.poku.js +207 -0
- package/lib/helpers/display.js +426 -5
- package/lib/helpers/envcontext.js +18 -3
- 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 +9 -0
- package/lib/helpers/remote/dependency-track.js +84 -0
- package/lib/helpers/remote/dependency-track.poku.js +119 -0
- package/lib/helpers/table.js +384 -0
- package/lib/helpers/table.poku.js +186 -0
- package/lib/helpers/utils.js +865 -416
- package/lib/helpers/utils.poku.js +172 -265
- 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 +3 -9
- package/lib/parsers/npmrc.js +17 -13
- package/lib/parsers/npmrc.poku.js +41 -5
- package/lib/server/openapi.yaml +34 -1
- package/lib/server/server.js +50 -13
- package/lib/server/server.poku.js +332 -144
- package/lib/stages/postgen/annotator.js +1 -1
- package/lib/stages/postgen/auditBom.js +196 -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/{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 -9
- 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/analyzer.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 +10 -1
- package/types/lib/helpers/pythonutils.d.ts.map +1 -1
- 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/table.d.ts +6 -0
- package/types/lib/helpers/table.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +533 -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 +4 -1
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +22 -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/lib/stages/pregen/env-audit.js +0 -34
- package/lib/stages/pregen/env-audit.poku.js +0 -290
- 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/lib/stages/pregen/env-audit.d.ts +0 -2
- package/types/lib/stages/pregen/env-audit.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,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import process from "node:process";
|
|
3
4
|
|
|
4
|
-
import { createStream, table } from "table";
|
|
5
|
+
import { createStream, table } from "./table.js";
|
|
6
|
+
import { isSecureMode, safeExistsSync, toCamel } from "./utils.js";
|
|
5
7
|
|
|
6
8
|
// https://github.com/yangshun/tree-node-cli/blob/master/src/index.js
|
|
7
9
|
const SYMBOLS_ANSI = {
|
|
@@ -19,6 +21,16 @@ const highlightStr = (s, highlight) => {
|
|
|
19
21
|
}
|
|
20
22
|
return s;
|
|
21
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* Prints the BOM components as a streaming table to the console.
|
|
26
|
+
* Delegates to {@link printOSTable} automatically when the BOM metadata indicates
|
|
27
|
+
* an operating-system or platform component type.
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
30
|
+
* @param {string[]} [filterTypes] Optional list of component types to include; all types shown when omitted
|
|
31
|
+
* @param {string} [highlight] Optional string to highlight in the output
|
|
32
|
+
* @returns {void}
|
|
33
|
+
*/
|
|
22
34
|
export function printTable(
|
|
23
35
|
bomJson,
|
|
24
36
|
filterTypes = undefined,
|
|
@@ -78,6 +90,7 @@ export function printTable(
|
|
|
78
90
|
]);
|
|
79
91
|
}
|
|
80
92
|
}
|
|
93
|
+
stream.end();
|
|
81
94
|
console.log();
|
|
82
95
|
if (!filterTypes) {
|
|
83
96
|
console.log(
|
|
@@ -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: {
|
|
@@ -116,8 +135,16 @@ export function printOSTable(bomJson) {
|
|
|
116
135
|
(comp.tags || []).join(", "),
|
|
117
136
|
]);
|
|
118
137
|
}
|
|
138
|
+
stream.end();
|
|
119
139
|
console.log();
|
|
120
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Prints the services listed in the BOM as a formatted table.
|
|
143
|
+
* Includes endpoint URLs, authentication flag, and cross-trust-boundary flag.
|
|
144
|
+
*
|
|
145
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
146
|
+
* @returns {void}
|
|
147
|
+
*/
|
|
121
148
|
export function printServices(bomJson) {
|
|
122
149
|
const data = [["Name", "Endpoints", "Authenticated", "X Trust Boundary"]];
|
|
123
150
|
if (!bomJson?.services) {
|
|
@@ -142,8 +169,14 @@ export function printServices(bomJson) {
|
|
|
142
169
|
}
|
|
143
170
|
}
|
|
144
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Prints the formulation components from the BOM as a formatted table.
|
|
174
|
+
*
|
|
175
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
176
|
+
* @returns {void}
|
|
177
|
+
*/
|
|
145
178
|
export function printFormulation(bomJson) {
|
|
146
|
-
const data = [["
|
|
179
|
+
const data = [["Type", "Name", "Version"]];
|
|
147
180
|
if (!bomJson?.formulation) {
|
|
148
181
|
return;
|
|
149
182
|
}
|
|
@@ -178,6 +211,13 @@ const locationComparator = (a, b) => {
|
|
|
178
211
|
return a.localeCompare(b);
|
|
179
212
|
};
|
|
180
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Prints component evidence occurrences (file locations) as a streaming table.
|
|
216
|
+
* Only components that have `evidence.occurrences` are included.
|
|
217
|
+
*
|
|
218
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
219
|
+
* @returns {void}
|
|
220
|
+
*/
|
|
181
221
|
export function printOccurrences(bomJson) {
|
|
182
222
|
if (!bomJson?.components) {
|
|
183
223
|
return;
|
|
@@ -214,9 +254,17 @@ export function printOccurrences(bomJson) {
|
|
|
214
254
|
stream.write(row);
|
|
215
255
|
}
|
|
216
256
|
}
|
|
257
|
+
stream.end();
|
|
217
258
|
console.log();
|
|
218
259
|
}
|
|
219
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Prints the call stack evidence for each component in the BOM as a formatted table.
|
|
263
|
+
* Only components that have `evidence.callstack.frames` are included.
|
|
264
|
+
*
|
|
265
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
266
|
+
* @returns {void}
|
|
267
|
+
*/
|
|
220
268
|
export function printCallStack(bomJson) {
|
|
221
269
|
const data = [["Group", "Name", "Version", "Call Stack"]];
|
|
222
270
|
if (!bomJson?.components) {
|
|
@@ -260,6 +308,15 @@ export function printCallStack(bomJson) {
|
|
|
260
308
|
console.log(table(data, config));
|
|
261
309
|
}
|
|
262
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* Prints the dependency tree from the BOM as an ASCII tree diagram.
|
|
313
|
+
* Uses the `table` library for small trees and plain console output for larger ones.
|
|
314
|
+
*
|
|
315
|
+
* @param {Object} bomJson CycloneDX BOM JSON object containing a `dependencies` array
|
|
316
|
+
* @param {string} [mode="dependsOn"] Dependency relation to traverse (`"dependsOn"` or `"provides"`)
|
|
317
|
+
* @param {string} [highlight] Optional string to highlight in the tree output
|
|
318
|
+
* @returns {void}
|
|
319
|
+
*/
|
|
263
320
|
export function printDependencyTree(
|
|
264
321
|
bomJson,
|
|
265
322
|
mode = "dependsOn",
|
|
@@ -362,9 +419,16 @@ const recursePrint = (depMap, subtree, level, shownList, treeGraphics) => {
|
|
|
362
419
|
}
|
|
363
420
|
};
|
|
364
421
|
|
|
422
|
+
/**
|
|
423
|
+
* Prints a table of reachable components derived from a reachability slices file.
|
|
424
|
+
* Aggregates per-purl reachable-flow counts and sorts them descending.
|
|
425
|
+
*
|
|
426
|
+
* @param {Object} sliceArtefacts Slice artefact paths, must include `reachablesSlicesFile`
|
|
427
|
+
* @returns {void}
|
|
428
|
+
*/
|
|
365
429
|
export function printReachables(sliceArtefacts) {
|
|
366
430
|
const reachablesSlicesFile = sliceArtefacts.reachablesSlicesFile;
|
|
367
|
-
if (!
|
|
431
|
+
if (!safeExistsSync(reachablesSlicesFile)) {
|
|
368
432
|
return;
|
|
369
433
|
}
|
|
370
434
|
const purlCounts = {};
|
|
@@ -398,6 +462,12 @@ export function printReachables(sliceArtefacts) {
|
|
|
398
462
|
}
|
|
399
463
|
}
|
|
400
464
|
|
|
465
|
+
/**
|
|
466
|
+
* Prints a formatted table of CycloneDX vulnerability objects.
|
|
467
|
+
*
|
|
468
|
+
* @param {Object[]} vulnerabilities Array of CycloneDX vulnerability objects
|
|
469
|
+
* @returns {void}
|
|
470
|
+
*/
|
|
401
471
|
export function printVulnerabilities(vulnerabilities) {
|
|
402
472
|
if (!vulnerabilities) {
|
|
403
473
|
return;
|
|
@@ -426,6 +496,14 @@ export function printVulnerabilities(vulnerabilities) {
|
|
|
426
496
|
console.log(`${vulnerabilities.length} vulnerabilities found.`);
|
|
427
497
|
}
|
|
428
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Prints an OWASP donation banner when running in a CI environment.
|
|
501
|
+
* The banner is suppressed when `options.noBanner` is set or the repository
|
|
502
|
+
* belongs to the cdxgen project itself.
|
|
503
|
+
*
|
|
504
|
+
* @param {Object} options CLI options
|
|
505
|
+
* @returns {void}
|
|
506
|
+
*/
|
|
429
507
|
export function printSponsorBanner(options) {
|
|
430
508
|
if (
|
|
431
509
|
process?.env?.CI &&
|
|
@@ -439,7 +517,7 @@ export function printSponsorBanner(options) {
|
|
|
439
517
|
},
|
|
440
518
|
};
|
|
441
519
|
let message =
|
|
442
|
-
"OWASP foundation relies on donations to fund our projects.\nDonation link: https://owasp.org/donate/?reponame=www-project-
|
|
520
|
+
"OWASP foundation relies on donations to fund our projects.\nDonation link: https://owasp.org/donate/?reponame=www-project-cdxgen&title=OWASP+cdxgen";
|
|
443
521
|
if (options.serverUrl && options.apiKey) {
|
|
444
522
|
message = `${message}\nDependency Track: https://owasp.org/donate/?reponame=www-project-dependency-track&title=OWASP+Dependency-Track`;
|
|
445
523
|
}
|
|
@@ -448,6 +526,13 @@ export function printSponsorBanner(options) {
|
|
|
448
526
|
}
|
|
449
527
|
}
|
|
450
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Prints a BOM summary table including generator tool names, component package types,
|
|
531
|
+
* and component namespaces extracted from BOM metadata properties.
|
|
532
|
+
*
|
|
533
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
534
|
+
* @returns {void}
|
|
535
|
+
*/
|
|
451
536
|
export function printSummary(bomJson) {
|
|
452
537
|
const config = {
|
|
453
538
|
header: {
|
|
@@ -497,3 +582,339 @@ export function printSummary(bomJson) {
|
|
|
497
582
|
const data = [[message]];
|
|
498
583
|
console.log(table(data, config));
|
|
499
584
|
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* @typedef {{type: string, variable: string, severity: string, message: string, mitigation: string}} EnvAuditFinding
|
|
588
|
+
*/
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Runs the pre-generation environment audit and renders the results as formatted
|
|
592
|
+
* tables to the console. Called when the --env-audit CLI flag is set.
|
|
593
|
+
*
|
|
594
|
+
* @param {string} filePath Project path being scanned
|
|
595
|
+
* @param {Object} config Loaded .cdxgenrc / config-file values
|
|
596
|
+
* @param {Object} options Effective CLI options
|
|
597
|
+
* @param {EnvAuditFinding[]} envAuditFindings Audit findings to display
|
|
598
|
+
*/
|
|
599
|
+
export function displaySelfThreatModel(
|
|
600
|
+
filePath,
|
|
601
|
+
config,
|
|
602
|
+
options,
|
|
603
|
+
envAuditFindings,
|
|
604
|
+
) {
|
|
605
|
+
const TLP = options.tlpClassification || "CLEAR";
|
|
606
|
+
const risks = [];
|
|
607
|
+
let riskScore = 0;
|
|
608
|
+
|
|
609
|
+
const addRisk = (level, reason, category = "configuration") => {
|
|
610
|
+
const scores = { low: 1, medium: 3, high: 5, critical: 8 };
|
|
611
|
+
riskScore = Math.min(10, riskScore + scores[level]);
|
|
612
|
+
risks.push({ level, reason, category });
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
// Config file risks
|
|
616
|
+
if (Object.keys(config).length > 0) {
|
|
617
|
+
addRisk(
|
|
618
|
+
"medium",
|
|
619
|
+
"A .cdxgenrc config file was loaded from the working directory. It may override security-relevant settings without being visible on the command line.",
|
|
620
|
+
"configuration",
|
|
621
|
+
);
|
|
622
|
+
const sensitive = ["server-url", "api-key", "include-formulation"];
|
|
623
|
+
for (const key of sensitive) {
|
|
624
|
+
if (config[key] || config[toCamel(key)]) {
|
|
625
|
+
addRisk(
|
|
626
|
+
key === "api-key" ? "high" : "medium",
|
|
627
|
+
`Config file sets '${key}', which affects SBOM content or remote submission behavior.`,
|
|
628
|
+
"configuration",
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Remote submission risks
|
|
635
|
+
if (options.serverUrl) {
|
|
636
|
+
const isHttps = options.serverUrl.startsWith("https://");
|
|
637
|
+
addRisk(
|
|
638
|
+
isHttps ? "medium" : "critical",
|
|
639
|
+
`SBOM will be submitted to ${options.serverUrl}${!isHttps ? " over plain HTTP — contents may be intercepted or tampered in transit." : "."}`,
|
|
640
|
+
"network",
|
|
641
|
+
);
|
|
642
|
+
if (options.skipDtTlsCheck) {
|
|
643
|
+
addRisk(
|
|
644
|
+
"high",
|
|
645
|
+
"TLS certificate validation is disabled for Dependency-Track uploads. SBOM contents may be intercepted or tampered in transit.",
|
|
646
|
+
"network",
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Data exposure risks
|
|
652
|
+
if (options.includeFormulation) {
|
|
653
|
+
addRisk(
|
|
654
|
+
"medium",
|
|
655
|
+
"Formulation mode is active. The SBOM will include build metadata such as git history, committer identities, and CI environment variables.",
|
|
656
|
+
"data-exposure",
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
if (options.evidence || options.deep) {
|
|
660
|
+
addRisk(
|
|
661
|
+
"medium",
|
|
662
|
+
"Evidence / deep mode will invoke build tools and parse source files to collect call graph and reachability evidence. Malicious build scripts may execute.",
|
|
663
|
+
"data-exposure",
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
if (options.installDeps) {
|
|
667
|
+
addRisk(
|
|
668
|
+
"high",
|
|
669
|
+
"Dependency auto-install is enabled. Lifecycle hooks (install scripts) from third-party packages will execute in the current environment.",
|
|
670
|
+
"data-exposure",
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Output path outside the project directory
|
|
675
|
+
if (options.output) {
|
|
676
|
+
const resolvedOutput = path.resolve(options.output);
|
|
677
|
+
const resolvedProject = path.resolve(filePath);
|
|
678
|
+
if (
|
|
679
|
+
!resolvedOutput.startsWith(resolvedProject + path.sep) &&
|
|
680
|
+
resolvedOutput !== resolvedProject
|
|
681
|
+
) {
|
|
682
|
+
addRisk(
|
|
683
|
+
"medium",
|
|
684
|
+
`Output path '${options.output}' resolves to '${resolvedOutput}', which is outside the project directory '${resolvedProject}'. Ensure this is intentional.`,
|
|
685
|
+
"configuration",
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Environment variable risks (config-layer only; env-audit covers the rest)
|
|
691
|
+
if (process.env.CDXGEN_SERVER_URL) {
|
|
692
|
+
addRisk(
|
|
693
|
+
"low",
|
|
694
|
+
"CDXGEN_SERVER_URL is set in the environment and will override any --server-url value.",
|
|
695
|
+
"environment",
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Integrate environment audit findings
|
|
700
|
+
if (envAuditFindings?.length) {
|
|
701
|
+
for (const f of envAuditFindings) {
|
|
702
|
+
const categoryMap = {
|
|
703
|
+
"code-execution": "runtime",
|
|
704
|
+
"debug-exposure": "runtime",
|
|
705
|
+
"environment-variable": "environment",
|
|
706
|
+
"network-interception": "network",
|
|
707
|
+
"credential-exposure": "environment",
|
|
708
|
+
"permission-misuse": "runtime",
|
|
709
|
+
privilege: "runtime",
|
|
710
|
+
};
|
|
711
|
+
addRisk(
|
|
712
|
+
f.severity,
|
|
713
|
+
`${f.variable}: ${f.message}`,
|
|
714
|
+
categoryMap[f.type] || "configuration",
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const nodeOptions = process.env.NODE_OPTIONS || "";
|
|
720
|
+
const riskLevel =
|
|
721
|
+
riskScore >= 8
|
|
722
|
+
? "CRITICAL"
|
|
723
|
+
: riskScore >= 5
|
|
724
|
+
? "HIGH"
|
|
725
|
+
: riskScore >= 3
|
|
726
|
+
? "MEDIUM"
|
|
727
|
+
: "LOW";
|
|
728
|
+
|
|
729
|
+
const riskColor = {
|
|
730
|
+
CRITICAL: "\x1b[1;31m",
|
|
731
|
+
HIGH: "\x1b[1;33m",
|
|
732
|
+
MEDIUM: "\x1b[1;36m",
|
|
733
|
+
LOW: "\x1b[1;32m",
|
|
734
|
+
};
|
|
735
|
+
const reset = "\x1b[0m";
|
|
736
|
+
const tlpGuidance = {
|
|
737
|
+
CLEAR: "May be shared publicly. No restrictions.",
|
|
738
|
+
GREEN: "Limited to community/peers. Not for public posting.",
|
|
739
|
+
AMBER:
|
|
740
|
+
"Limited to organisation and trusted partners. Handle-in-confidence.",
|
|
741
|
+
AMBER_AND_STRICT: "Organisation only. No external sharing.",
|
|
742
|
+
RED: "Named recipients only. Do not forward or store beyond session.",
|
|
743
|
+
};
|
|
744
|
+
const headerData = [
|
|
745
|
+
["TLP Classification", `${TLP} — ${tlpGuidance[TLP]}`],
|
|
746
|
+
["Risk Score", `${riskScore}/10`],
|
|
747
|
+
["Risk Level", `${riskColor[riskLevel]}${riskLevel}${reset}`],
|
|
748
|
+
];
|
|
749
|
+
const headerConfig = {
|
|
750
|
+
header: {
|
|
751
|
+
alignment: "center",
|
|
752
|
+
content:
|
|
753
|
+
"SBOM Generation Environment Assessment\nPre-generation security audit by cdxgen",
|
|
754
|
+
},
|
|
755
|
+
columns: [{ width: 30, alignment: "right" }, { width: 70 }],
|
|
756
|
+
columnDefault: { wrapWord: true },
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
console.log(table(headerData, headerConfig));
|
|
760
|
+
if (risks.length > 0) {
|
|
761
|
+
const findingsData = [["#", "Severity", "Category", "Finding"]];
|
|
762
|
+
risks.forEach(({ level, reason, category }, i) => {
|
|
763
|
+
const severityColor =
|
|
764
|
+
level === "critical"
|
|
765
|
+
? "\x1b[1;31m"
|
|
766
|
+
: level === "high"
|
|
767
|
+
? "\x1b[1;33m"
|
|
768
|
+
: level === "medium"
|
|
769
|
+
? "\x1b[1;36m"
|
|
770
|
+
: "\x1b[1;32m";
|
|
771
|
+
findingsData.push([
|
|
772
|
+
`${i + 1}`,
|
|
773
|
+
`${severityColor}${level.toUpperCase()}${reset}`,
|
|
774
|
+
category,
|
|
775
|
+
reason,
|
|
776
|
+
]);
|
|
777
|
+
});
|
|
778
|
+
const findingsConfig = {
|
|
779
|
+
header: {
|
|
780
|
+
alignment: "center",
|
|
781
|
+
content: `Findings (${risks.length})`,
|
|
782
|
+
},
|
|
783
|
+
columns: [
|
|
784
|
+
{ width: 5, alignment: "right" },
|
|
785
|
+
{ width: 12 },
|
|
786
|
+
{ width: 17 },
|
|
787
|
+
{ width: 66 },
|
|
788
|
+
],
|
|
789
|
+
columnDefault: { wrapWord: true },
|
|
790
|
+
};
|
|
791
|
+
console.log(table(findingsData, findingsConfig));
|
|
792
|
+
} else {
|
|
793
|
+
const noFindingsData = [
|
|
794
|
+
[
|
|
795
|
+
`${riskColor[riskLevel]}✅ No risks detected in the current configuration.${reset}`,
|
|
796
|
+
],
|
|
797
|
+
];
|
|
798
|
+
const noFindingsConfig = {
|
|
799
|
+
header: { alignment: "center", content: "📋 Findings" },
|
|
800
|
+
columns: [{ width: 100, alignment: "center" }],
|
|
801
|
+
};
|
|
802
|
+
console.log(table(noFindingsData, noFindingsConfig));
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const configData = [
|
|
806
|
+
["Setting", "Value"],
|
|
807
|
+
["Project", options.projectName || filePath],
|
|
808
|
+
["Type(s)", options.projectType?.join(", ") || "auto-detect"],
|
|
809
|
+
["Profile", options.profile || "generic"],
|
|
810
|
+
["Path", filePath],
|
|
811
|
+
["Output", options.output || "(stdout)"],
|
|
812
|
+
["Recursive", options.recursive ? "yes" : "no"],
|
|
813
|
+
["Remote Submission", options.serverUrl || "none"],
|
|
814
|
+
["Formulation", options.includeFormulation ? "yes" : "no"],
|
|
815
|
+
["Evidence / Deep Mode", options.evidence || options.deep ? "yes" : "no"],
|
|
816
|
+
["Auto-install Dependencies", options.installDeps ? "yes" : "no"],
|
|
817
|
+
["NODE_OPTIONS", nodeOptions || "(not set)"],
|
|
818
|
+
];
|
|
819
|
+
const effConfigTableConfig = {
|
|
820
|
+
header: { alignment: "center", content: "Effective Configuration" },
|
|
821
|
+
columns: [{ width: 28 }, { width: 72 }],
|
|
822
|
+
columnDefault: { wrapWord: true },
|
|
823
|
+
};
|
|
824
|
+
console.log(table(configData, effConfigTableConfig));
|
|
825
|
+
|
|
826
|
+
const recommendations = [];
|
|
827
|
+
if (["AMBER", "AMBER_AND_STRICT", "RED"].includes(TLP)) {
|
|
828
|
+
recommendations.push([
|
|
829
|
+
"High",
|
|
830
|
+
"Omit --include-formulation to avoid embedding committer identities and CI secrets in the SBOM.",
|
|
831
|
+
]);
|
|
832
|
+
if (TLP === "RED") {
|
|
833
|
+
recommendations.push([
|
|
834
|
+
"Critical",
|
|
835
|
+
"Run cdxgen inside an isolated container or VM with no access to production credentials.",
|
|
836
|
+
]);
|
|
837
|
+
recommendations.push([
|
|
838
|
+
"Critical",
|
|
839
|
+
"Do not set --server-url; review and handle the output SBOM manually before sharing.",
|
|
840
|
+
]);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (riskScore >= 5) {
|
|
844
|
+
recommendations.push([
|
|
845
|
+
"High",
|
|
846
|
+
"Address the findings above before scanning untrusted repositories.",
|
|
847
|
+
]);
|
|
848
|
+
recommendations.push([
|
|
849
|
+
"Medium",
|
|
850
|
+
"Pass --no-install-deps to prevent package manager hooks from executing.",
|
|
851
|
+
]);
|
|
852
|
+
}
|
|
853
|
+
if (envAuditFindings.some((f) => f.type === "code-execution")) {
|
|
854
|
+
recommendations.push([
|
|
855
|
+
"High",
|
|
856
|
+
"Remove code-execution flags (--require, --eval, --loader, --import) from NODE_OPTIONS and JAVA_TOOL_OPTIONS.",
|
|
857
|
+
]);
|
|
858
|
+
}
|
|
859
|
+
if (envAuditFindings.some((f) => f.variable === "NODE_PATH")) {
|
|
860
|
+
recommendations.push([
|
|
861
|
+
"High",
|
|
862
|
+
"Unset NODE_PATH to prevent module-resolution poisoning by malicious packages.",
|
|
863
|
+
]);
|
|
864
|
+
}
|
|
865
|
+
if (envAuditFindings.some((f) => f.type === "privilege")) {
|
|
866
|
+
recommendations.push([
|
|
867
|
+
"High",
|
|
868
|
+
"Do not run cdxgen as root. Create a dedicated low-privilege user or use a rootless container.",
|
|
869
|
+
]);
|
|
870
|
+
}
|
|
871
|
+
if (/--permission\b/i.test(nodeOptions)) {
|
|
872
|
+
recommendations.push([
|
|
873
|
+
"Medium",
|
|
874
|
+
"Audit every --allow-* scope; use absolute paths rather than wildcards to minimise the permission surface.",
|
|
875
|
+
]);
|
|
876
|
+
}
|
|
877
|
+
recommendations.push([
|
|
878
|
+
"Info",
|
|
879
|
+
"Minimal safe invocation: cdxgen --no-install-deps --output ./sbom.cdx.json <path>",
|
|
880
|
+
]);
|
|
881
|
+
const recommendationsData = [["Priority", "Action"]];
|
|
882
|
+
recommendations.forEach(([priority, action]) => {
|
|
883
|
+
const priorityColor =
|
|
884
|
+
priority === "Critical"
|
|
885
|
+
? "\x1b[1;31m"
|
|
886
|
+
: priority === "High"
|
|
887
|
+
? "\x1b[1;33m"
|
|
888
|
+
: priority === "Medium"
|
|
889
|
+
? "\x1b[1;36m"
|
|
890
|
+
: "\x1b[1;32m";
|
|
891
|
+
recommendationsData.push([`${priorityColor}${priority}${reset}`, action]);
|
|
892
|
+
});
|
|
893
|
+
const recommendationsConfig = {
|
|
894
|
+
header: {
|
|
895
|
+
alignment: "center",
|
|
896
|
+
content: `Recommendations for TLP:${TLP}`,
|
|
897
|
+
},
|
|
898
|
+
columns: [{ width: 12 }, { width: 88 }],
|
|
899
|
+
columnDefault: { wrapWord: true },
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
console.log(table(recommendationsData, recommendationsConfig));
|
|
903
|
+
// Only abort in secure mode when at least one finding is high or critical severity.
|
|
904
|
+
// Accumulated low/medium findings may push riskScore above 5 but should not abort.
|
|
905
|
+
if (
|
|
906
|
+
isSecureMode &&
|
|
907
|
+
envAuditFindings?.some((f) => ["high", "critical"].includes(f.severity))
|
|
908
|
+
) {
|
|
909
|
+
const abortData = [
|
|
910
|
+
[
|
|
911
|
+
`${riskColor[riskLevel]}🚫 SECURE MODE: High-risk configuration detected. Aborting SBOM generation.${reset}`,
|
|
912
|
+
],
|
|
913
|
+
];
|
|
914
|
+
const abortConfig = {
|
|
915
|
+
columns: [{ width: 100, alignment: "center" }],
|
|
916
|
+
};
|
|
917
|
+
console.log(table(abortData, abortConfig));
|
|
918
|
+
process.exit(1);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
@@ -37,6 +37,7 @@ export const SDKMAN_JAVA_TOOL_ALIASES = {
|
|
|
37
37
|
java23: process.env.JAVA23_TOOL || "23.0.2-tem",
|
|
38
38
|
java24: process.env.JAVA24_TOOL || "24.0.2-tem",
|
|
39
39
|
java25: process.env.JAVA25_TOOL || "25.0.2-tem",
|
|
40
|
+
java26: process.env.JAVA26_TOOL || "26-tem",
|
|
40
41
|
};
|
|
41
42
|
|
|
42
43
|
/**
|
|
@@ -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")
|