@cyclonedx/cdxgen 12.1.5 → 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 +175 -96
- 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 +327 -372
- 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/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 +203 -0
- package/lib/helpers/depsUtils.poku.js +150 -0
- package/lib/helpers/display.js +423 -4
- 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/utils.js +681 -406
- package/lib/helpers/utils.poku.js +55 -255
- 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 +1 -1
- package/lib/server/server.js +40 -11
- 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/{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 +10 -1
- package/types/lib/helpers/pythonutils.d.ts.map +1 -1
- 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 +4 -1
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- 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/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,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,6 +22,16 @@ 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,
|
|
@@ -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,6 +137,13 @@ 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
149
|
if (!bomJson?.services) {
|
|
@@ -142,8 +168,14 @@ 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 = [["
|
|
178
|
+
const data = [["Type", "Name", "Version"]];
|
|
147
179
|
if (!bomJson?.formulation) {
|
|
148
180
|
return;
|
|
149
181
|
}
|
|
@@ -178,6 +210,13 @@ 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
221
|
if (!bomJson?.components) {
|
|
183
222
|
return;
|
|
@@ -217,6 +256,13 @@ 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
268
|
if (!bomJson?.components) {
|
|
@@ -260,6 +306,15 @@ export function printCallStack(bomJson) {
|
|
|
260
306
|
console.log(table(data, config));
|
|
261
307
|
}
|
|
262
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
|
+
*/
|
|
263
318
|
export function printDependencyTree(
|
|
264
319
|
bomJson,
|
|
265
320
|
mode = "dependsOn",
|
|
@@ -362,9 +417,16 @@ const recursePrint = (depMap, subtree, level, shownList, treeGraphics) => {
|
|
|
362
417
|
}
|
|
363
418
|
};
|
|
364
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
|
+
*/
|
|
365
427
|
export function printReachables(sliceArtefacts) {
|
|
366
428
|
const reachablesSlicesFile = sliceArtefacts.reachablesSlicesFile;
|
|
367
|
-
if (!
|
|
429
|
+
if (!safeExistsSync(reachablesSlicesFile)) {
|
|
368
430
|
return;
|
|
369
431
|
}
|
|
370
432
|
const purlCounts = {};
|
|
@@ -398,6 +460,12 @@ export function printReachables(sliceArtefacts) {
|
|
|
398
460
|
}
|
|
399
461
|
}
|
|
400
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
|
+
*/
|
|
401
469
|
export function printVulnerabilities(vulnerabilities) {
|
|
402
470
|
if (!vulnerabilities) {
|
|
403
471
|
return;
|
|
@@ -426,6 +494,14 @@ export function printVulnerabilities(vulnerabilities) {
|
|
|
426
494
|
console.log(`${vulnerabilities.length} vulnerabilities found.`);
|
|
427
495
|
}
|
|
428
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
|
+
*/
|
|
429
505
|
export function printSponsorBanner(options) {
|
|
430
506
|
if (
|
|
431
507
|
process?.env?.CI &&
|
|
@@ -439,7 +515,7 @@ export function printSponsorBanner(options) {
|
|
|
439
515
|
},
|
|
440
516
|
};
|
|
441
517
|
let message =
|
|
442
|
-
"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";
|
|
443
519
|
if (options.serverUrl && options.apiKey) {
|
|
444
520
|
message = `${message}\nDependency Track: https://owasp.org/donate/?reponame=www-project-dependency-track&title=OWASP+Dependency-Track`;
|
|
445
521
|
}
|
|
@@ -448,6 +524,13 @@ export function printSponsorBanner(options) {
|
|
|
448
524
|
}
|
|
449
525
|
}
|
|
450
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
|
+
*/
|
|
451
534
|
export function printSummary(bomJson) {
|
|
452
535
|
const config = {
|
|
453
536
|
header: {
|
|
@@ -497,3 +580,339 @@ export function printSummary(bomJson) {
|
|
|
497
580
|
const data = [[message]];
|
|
498
581
|
console.log(table(data, config));
|
|
499
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
|
+
}
|
|
@@ -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")
|