@cyclonedx/cdxgen 12.3.0 → 12.3.2
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 +15 -5
- package/bin/audit.js +7 -0
- package/bin/cdxgen.js +241 -81
- package/bin/repl.js +138 -0
- package/data/rules/ai-agent-governance.yaml +249 -0
- package/data/rules/dependency-sources.yaml +41 -0
- package/data/rules/mcp-servers.yaml +304 -0
- package/data/rules/package-integrity.yaml +123 -0
- package/lib/audit/index.js +353 -29
- package/lib/audit/index.poku.js +247 -7
- package/lib/audit/reporters.js +26 -0
- package/lib/audit/scoring.js +262 -13
- package/lib/audit/scoring.poku.js +179 -0
- package/lib/audit/targets.js +391 -2
- package/lib/audit/targets.poku.js +416 -3
- package/lib/cli/index.js +588 -45
- package/lib/cli/index.poku.js +735 -1
- package/lib/evinser/evinser.js +8 -5
- package/lib/helpers/agentFormulationParser.js +318 -0
- package/lib/helpers/aiInventory.js +262 -0
- package/lib/helpers/aiInventory.poku.js +111 -0
- package/lib/helpers/analyzer.js +1769 -0
- package/lib/helpers/analyzer.poku.js +284 -3
- package/lib/helpers/auditCategories.js +76 -0
- package/lib/helpers/ciParsers/githubActions.js +140 -16
- package/lib/helpers/ciParsers/githubActions.poku.js +110 -0
- package/lib/helpers/communityAiConfigParser.js +672 -0
- package/lib/helpers/communityAiConfigParser.poku.js +63 -0
- package/lib/helpers/depsUtils.js +108 -0
- package/lib/helpers/depsUtils.poku.js +72 -1
- package/lib/helpers/display.js +325 -3
- package/lib/helpers/display.poku.js +301 -0
- package/lib/helpers/formulationParsers.js +28 -0
- package/lib/helpers/formulationParsers.poku.js +504 -1
- package/lib/helpers/jsonLike.js +102 -0
- package/lib/helpers/jsonLike.poku.js +34 -0
- package/lib/helpers/mcp.js +248 -0
- package/lib/helpers/mcp.poku.js +101 -0
- package/lib/helpers/mcpConfigParser.js +656 -0
- package/lib/helpers/mcpConfigParser.poku.js +126 -0
- package/lib/helpers/mcpDiscovery.js +84 -0
- package/lib/helpers/mcpDiscovery.poku.js +21 -0
- package/lib/helpers/protobom.js +3 -3
- package/lib/helpers/provenanceUtils.js +29 -4
- package/lib/helpers/provenanceUtils.poku.js +29 -3
- package/lib/helpers/registryProvenance.js +210 -0
- package/lib/helpers/registryProvenance.poku.js +144 -0
- package/lib/helpers/rustFormulationParser.js +330 -0
- package/lib/helpers/source.js +21 -2
- package/lib/helpers/source.poku.js +38 -0
- package/lib/helpers/utils.js +1331 -83
- package/lib/helpers/utils.poku.js +599 -188
- package/lib/helpers/vsixutils.js +12 -4
- package/lib/helpers/vsixutils.poku.js +34 -0
- package/lib/managers/binary.js +36 -12
- package/lib/managers/binary.poku.js +68 -0
- package/lib/managers/docker.js +59 -9
- package/lib/managers/docker.poku.js +61 -0
- package/lib/managers/piptree.js +12 -7
- package/lib/managers/piptree.poku.js +44 -0
- package/lib/stages/postgen/annotator.js +2 -1
- package/lib/stages/postgen/annotator.poku.js +15 -0
- package/lib/stages/postgen/auditBom.js +20 -6
- package/lib/stages/postgen/auditBom.poku.js +694 -1
- package/lib/stages/postgen/postgen.js +262 -11
- package/lib/stages/postgen/postgen.poku.js +306 -2
- package/lib/stages/postgen/ruleEngine.js +49 -1
- package/lib/stages/postgen/spdxConverter.poku.js +70 -0
- package/lib/stages/pregen/pregen.js +6 -4
- package/package.json +1 -1
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/reporters.d.ts.map +1 -1
- package/types/lib/audit/scoring.d.ts.map +1 -1
- package/types/lib/audit/targets.d.ts +12 -0
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +2 -8
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/agentFormulationParser.d.ts +19 -0
- package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -0
- package/types/lib/helpers/aiInventory.d.ts +23 -0
- package/types/lib/helpers/aiInventory.d.ts.map +1 -0
- package/types/lib/helpers/analyzer.d.ts +10 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/auditCategories.d.ts +12 -0
- package/types/lib/helpers/auditCategories.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/communityAiConfigParser.d.ts +29 -0
- package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts +8 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +17 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
- package/types/lib/helpers/jsonLike.d.ts +4 -0
- package/types/lib/helpers/jsonLike.d.ts.map +1 -0
- package/types/lib/helpers/mcp.d.ts +29 -0
- package/types/lib/helpers/mcp.d.ts.map +1 -0
- package/types/lib/helpers/mcpConfigParser.d.ts +30 -0
- package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -0
- package/types/lib/helpers/mcpDiscovery.d.ts +5 -0
- package/types/lib/helpers/mcpDiscovery.d.ts.map +1 -0
- package/types/lib/helpers/provenanceUtils.d.ts +5 -3
- package/types/lib/helpers/provenanceUtils.d.ts.map +1 -1
- package/types/lib/helpers/registryProvenance.d.ts +9 -0
- package/types/lib/helpers/registryProvenance.d.ts.map +1 -1
- package/types/lib/helpers/rustFormulationParser.d.ts +17 -0
- package/types/lib/helpers/rustFormulationParser.d.ts.map +1 -0
- package/types/lib/helpers/source.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +31 -1
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/vsixutils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/piptree.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -69,7 +69,17 @@ Most SBOM tools are like simple barcode scanners. For easy applications, they ca
|
|
|
69
69
|
- _Precision:_ Try using multiple techniques to improve precision, even if it takes extra time.
|
|
70
70
|
- _Personas:_ Cater to the needs of a range of personas such as security researchers, compliance auditors, developers, and SOC.
|
|
71
71
|
- _Machine Learning:_ Optimize the generated data for Machine Learning (ML) purposes by considering the various model properties.
|
|
72
|
-
- _Safety:_ Execute external build tools and handle untrusted inputs defensively, with hardened defaults
|
|
72
|
+
- _Safety:_ Execute external build tools and handle untrusted inputs defensively, with hardened defaults, a [secure mode](docs/PERMISSIONS.md) for sensitive environments, and a read-only `--dry-run` mode for review-first workflows.
|
|
73
|
+
|
|
74
|
+
### Review-first dry runs
|
|
75
|
+
|
|
76
|
+
When you want to inspect what cdxgen would do before allowing side effects, use `--dry-run`.
|
|
77
|
+
|
|
78
|
+
```shell
|
|
79
|
+
cdxgen --dry-run -p -t js .
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Dry-run mode keeps cdxgen read-only: it reads local files, blocks writes/exec/temp creation/cloning/submission, and prints an activity summary table for both beginners and power users.
|
|
73
83
|
|
|
74
84
|
## Documentation
|
|
75
85
|
|
|
@@ -170,7 +180,7 @@ Common asset names:
|
|
|
170
180
|
#### Linux
|
|
171
181
|
|
|
172
182
|
```bash
|
|
173
|
-
VERSION="v12.3.
|
|
183
|
+
VERSION="v12.3.1"
|
|
174
184
|
ASSET="cdx-audit-linux-amd64"
|
|
175
185
|
BASE_URL="https://github.com/cdxgen/cdxgen/releases/download/${VERSION}"
|
|
176
186
|
|
|
@@ -184,7 +194,7 @@ chmod +x "${ASSET}"
|
|
|
184
194
|
#### macOS
|
|
185
195
|
|
|
186
196
|
```bash
|
|
187
|
-
VERSION="v12.3.
|
|
197
|
+
VERSION="v12.3.1"
|
|
188
198
|
ASSET="cdx-audit-darwin-arm64"
|
|
189
199
|
BASE_URL="https://github.com/cdxgen/cdxgen/releases/download/${VERSION}"
|
|
190
200
|
|
|
@@ -198,7 +208,7 @@ chmod +x "${ASSET}"
|
|
|
198
208
|
#### Windows (PowerShell)
|
|
199
209
|
|
|
200
210
|
```powershell
|
|
201
|
-
$Version = "v12.3.
|
|
211
|
+
$Version = "v12.3.1"
|
|
202
212
|
$Asset = "cdx-audit-windows-amd64.exe"
|
|
203
213
|
$BaseUrl = "https://github.com/cdxgen/cdxgen/releases/download/$Version"
|
|
204
214
|
|
|
@@ -223,7 +233,7 @@ steps:
|
|
|
223
233
|
env:
|
|
224
234
|
GH_TOKEN: ${{ github.token }}
|
|
225
235
|
run: |
|
|
226
|
-
gh release download v12.3.
|
|
236
|
+
gh release download v12.3.1 \
|
|
227
237
|
--repo cdxgen/cdxgen \
|
|
228
238
|
--pattern 'cdx-audit-linux-amd64' \
|
|
229
239
|
--pattern 'cdx-audit-linux-amd64.sha256'
|
package/bin/audit.js
CHANGED
|
@@ -92,6 +92,12 @@ const args = yargs(hideBin(process.argv))
|
|
|
92
92
|
"Restrict predictive audit target selection to packages marked with trusted publishing metadata.",
|
|
93
93
|
type: "boolean",
|
|
94
94
|
})
|
|
95
|
+
.option("prioritize-direct-runtime", {
|
|
96
|
+
default: true,
|
|
97
|
+
description:
|
|
98
|
+
"Prioritize direct runtime dependencies ahead of optional, development-only, or platform-specific transitive packages during target selection.",
|
|
99
|
+
type: "boolean",
|
|
100
|
+
})
|
|
95
101
|
.check((argv) => {
|
|
96
102
|
if (!argv.bom && !argv.bomDir) {
|
|
97
103
|
throw new Error("Specify --bom or --bom-dir.");
|
|
@@ -163,6 +169,7 @@ function writeOrPrint(output, outputPath) {
|
|
|
163
169
|
maxTargets: args.maxTargets,
|
|
164
170
|
minSeverity: args.minSeverity,
|
|
165
171
|
onProgress: progressTracker.onProgress,
|
|
172
|
+
prioritizeDirectRuntime: args.prioritizeDirectRuntime,
|
|
166
173
|
report: args.report,
|
|
167
174
|
reportsDir: args.reportsDir,
|
|
168
175
|
scope: args.scope === "required" ? "required" : undefined,
|
package/bin/cdxgen.js
CHANGED
|
@@ -22,8 +22,10 @@ import { createBom, submitBom } from "../lib/cli/index.js";
|
|
|
22
22
|
import { signBom, verifyBom } from "../lib/helpers/bomSigner.js";
|
|
23
23
|
import {
|
|
24
24
|
displaySelfThreatModel,
|
|
25
|
+
printActivitySummary,
|
|
25
26
|
printCallStack,
|
|
26
27
|
printDependencyTree,
|
|
28
|
+
printEnvironmentAuditFindings,
|
|
27
29
|
printFormulation,
|
|
28
30
|
printOccurrences,
|
|
29
31
|
printReachables,
|
|
@@ -58,13 +60,19 @@ import {
|
|
|
58
60
|
getTmpDir,
|
|
59
61
|
isBun,
|
|
60
62
|
isDeno,
|
|
63
|
+
isDryRun,
|
|
61
64
|
isMac,
|
|
62
65
|
isNode,
|
|
63
66
|
isSecureMode,
|
|
64
67
|
isWin,
|
|
68
|
+
recordActivity,
|
|
65
69
|
remoteHostsAccessed,
|
|
66
70
|
retrieveCdxgenVersion,
|
|
67
71
|
safeExistsSync,
|
|
72
|
+
safeMkdirSync,
|
|
73
|
+
safeWriteSync,
|
|
74
|
+
setActivityContext,
|
|
75
|
+
setDryRunMode,
|
|
68
76
|
toCamel,
|
|
69
77
|
} from "../lib/helpers/utils.js";
|
|
70
78
|
import { postProcess } from "../lib/stages/postgen/postgen.js";
|
|
@@ -230,6 +238,18 @@ const args = _yargs
|
|
|
230
238
|
default: isSecureMode,
|
|
231
239
|
description: "Fail if any dependency extractor fails.",
|
|
232
240
|
})
|
|
241
|
+
.option("dry-run", {
|
|
242
|
+
type: "boolean",
|
|
243
|
+
default: isDryRun,
|
|
244
|
+
description:
|
|
245
|
+
"Read-only mode. cdxgen only performs file reads and reports blocked writes, command execution, temp creation, network access, and submissions.",
|
|
246
|
+
})
|
|
247
|
+
.option("activity-report", {
|
|
248
|
+
choices: ["json", "jsonl"],
|
|
249
|
+
description: "Render the activity report as JSON or JSON Lines.",
|
|
250
|
+
hidden: true,
|
|
251
|
+
type: "string",
|
|
252
|
+
})
|
|
233
253
|
.option("no-babel", {
|
|
234
254
|
type: "boolean",
|
|
235
255
|
description:
|
|
@@ -442,9 +462,8 @@ const args = _yargs
|
|
|
442
462
|
})
|
|
443
463
|
.option("tlp-classification", {
|
|
444
464
|
description:
|
|
445
|
-
|
|
465
|
+
"Traffic Light Protocol (TLP) is a classification system for identifying the potential risk associated with an artefact, including whether it is subject to certain types of legal, financial, or technical threats. Refer to [https://www.first.org/tlp/](https://www.first.org/tlp/) for further information.",
|
|
446
466
|
choices: ["CLEAR", "GREEN", "AMBER", "AMBER_AND_STRICT", "RED"],
|
|
447
|
-
default: "CLEAR",
|
|
448
467
|
hidden: true,
|
|
449
468
|
})
|
|
450
469
|
.option("env-audit", {
|
|
@@ -658,6 +677,13 @@ const options = Object.assign({}, args, {
|
|
|
658
677
|
exclude: args.exclude || args.excludeRegex,
|
|
659
678
|
include: args.include || args.includeRegex,
|
|
660
679
|
});
|
|
680
|
+
setDryRunMode(options.dryRun);
|
|
681
|
+
setActivityContext({
|
|
682
|
+
projectType: Array.isArray(options.projectType)
|
|
683
|
+
? options.projectType.join(",")
|
|
684
|
+
: options.projectType,
|
|
685
|
+
sourcePath: filePath,
|
|
686
|
+
});
|
|
661
687
|
const outputPlan = createOutputPlan(options);
|
|
662
688
|
for (const outputFile of Object.values(outputPlan.outputs)) {
|
|
663
689
|
const outputDirectory = getOutputDirectory(outputFile);
|
|
@@ -666,7 +692,7 @@ for (const outputFile of Object.values(outputPlan.outputs)) {
|
|
|
666
692
|
outputDirectory !== process.cwd() &&
|
|
667
693
|
!safeExistsSync(outputDirectory)
|
|
668
694
|
) {
|
|
669
|
-
|
|
695
|
+
safeMkdirSync(outputDirectory, { recursive: true });
|
|
670
696
|
}
|
|
671
697
|
}
|
|
672
698
|
// Filter duplicate types. Eg: -t gradle -t gradle
|
|
@@ -709,6 +735,12 @@ if (process.argv[1].includes("cdxgen-secure")) {
|
|
|
709
735
|
options.installDeps = false;
|
|
710
736
|
process.env.CDXGEN_SECURE_MODE = true;
|
|
711
737
|
}
|
|
738
|
+
if (isDryRun) {
|
|
739
|
+
thoughtLog(
|
|
740
|
+
"Ok, the user wants cdxgen to run in dry-run mode. I must avoid writes, child processes, temp directories, network submissions, and cloning.",
|
|
741
|
+
);
|
|
742
|
+
options.installDeps = false;
|
|
743
|
+
}
|
|
712
744
|
if (options.standard) {
|
|
713
745
|
options.specVersion = 1.7;
|
|
714
746
|
}
|
|
@@ -905,9 +937,15 @@ const checkPermissions = (filePath, options) => {
|
|
|
905
937
|
console.log(
|
|
906
938
|
"\x1b[1;35mSecure mode requires permission-related arguments. These can be passed as CLI arguments directly to the node runtime or set the NODE_OPTIONS environment variable as shown below.\x1b[0m",
|
|
907
939
|
);
|
|
908
|
-
const childProcessArgs =
|
|
909
|
-
|
|
910
|
-
|
|
940
|
+
const childProcessArgs = isDryRun
|
|
941
|
+
? ""
|
|
942
|
+
: options?.lifecycle !== "pre-build"
|
|
943
|
+
? " --allow-child-process"
|
|
944
|
+
: "";
|
|
945
|
+
const fsWriteArgs = isDryRun
|
|
946
|
+
? ""
|
|
947
|
+
: ` --allow-fs-write="${getTmpDir()}/*" --allow-fs-write="${options.output}"`;
|
|
948
|
+
const nodeOptionsVal = `--permission --allow-fs-read="${getTmpDir()}/*" --allow-fs-read="${fullFilePath}/*"${fsWriteArgs}${childProcessArgs}`;
|
|
911
949
|
console.log(
|
|
912
950
|
`${isWin ? "$env:" : "export "}NODE_OPTIONS='${nodeOptionsVal}'`,
|
|
913
951
|
);
|
|
@@ -966,12 +1004,12 @@ const checkPermissions = (filePath, options) => {
|
|
|
966
1004
|
);
|
|
967
1005
|
return false;
|
|
968
1006
|
}
|
|
969
|
-
if (!process.permission.has("fs.write", options.output)) {
|
|
1007
|
+
if (!isDryRun && !process.permission.has("fs.write", options.output)) {
|
|
970
1008
|
console.log(
|
|
971
1009
|
`\x1b[1;35mSECURE MODE: FileSystemWrite permission is required to create the output BOM file. Please invoke cdxgen with the argument --allow-fs-write="${options.output}"\x1b[0m`,
|
|
972
1010
|
);
|
|
973
1011
|
}
|
|
974
|
-
if (options.evidence) {
|
|
1012
|
+
if (!isDryRun && options.evidence) {
|
|
975
1013
|
const slicesFilesKeys = [
|
|
976
1014
|
"deps-slices-file",
|
|
977
1015
|
"usages-slices-file",
|
|
@@ -995,7 +1033,7 @@ const checkPermissions = (filePath, options) => {
|
|
|
995
1033
|
}
|
|
996
1034
|
}
|
|
997
1035
|
}
|
|
998
|
-
if (!process.permission.has("fs.write", getTmpDir())) {
|
|
1036
|
+
if (!isDryRun && !process.permission.has("fs.write", getTmpDir())) {
|
|
999
1037
|
console.log(
|
|
1000
1038
|
`FileSystemWrite permission may be required for the TEMP directory. Please invoke cdxgen with the argument --allow-fs-write="${join(getTmpDir(), "*")}" in case of any crashes.`,
|
|
1001
1039
|
);
|
|
@@ -1005,12 +1043,16 @@ const checkPermissions = (filePath, options) => {
|
|
|
1005
1043
|
);
|
|
1006
1044
|
}
|
|
1007
1045
|
}
|
|
1008
|
-
if (!process.permission.has("child") && !isSecureMode) {
|
|
1046
|
+
if (!isDryRun && !process.permission.has("child") && !isSecureMode) {
|
|
1009
1047
|
console.log(
|
|
1010
1048
|
"ChildProcess permission is missing. This is required to spawn commands for some languages. Please invoke cdxgen with the argument --allow-child-process in case of issues.",
|
|
1011
1049
|
);
|
|
1012
1050
|
}
|
|
1013
|
-
if (
|
|
1051
|
+
if (
|
|
1052
|
+
!isDryRun &&
|
|
1053
|
+
process.permission.has("child") &&
|
|
1054
|
+
options?.lifecycle === "pre-build"
|
|
1055
|
+
) {
|
|
1014
1056
|
console.log(
|
|
1015
1057
|
"SECURE MODE: ChildProcess permission is not required for pre-build SBOM generation. Please invoke cdxgen without the argument --allow-child-process.",
|
|
1016
1058
|
);
|
|
@@ -1034,7 +1076,7 @@ const stringifyJson = (jsonPayload, jsonPretty) =>
|
|
|
1034
1076
|
|
|
1035
1077
|
const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
1036
1078
|
const jsonPayload = stringifyJson(bomJson, options.jsonPretty);
|
|
1037
|
-
|
|
1079
|
+
safeWriteSync(jsonFile, jsonPayload);
|
|
1038
1080
|
if (jsonFile.endsWith("bom.json")) {
|
|
1039
1081
|
thoughtLog(
|
|
1040
1082
|
`Let's save the file to "${jsonFile}". Should I suggest the '.cdx.json' file extension for better semantics?`,
|
|
@@ -1045,6 +1087,15 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
|
1045
1087
|
if (!jsonPayload || !needsBomSigning(options)) {
|
|
1046
1088
|
return jsonPayload;
|
|
1047
1089
|
}
|
|
1090
|
+
if (isDryRun) {
|
|
1091
|
+
recordActivity({
|
|
1092
|
+
kind: "sign",
|
|
1093
|
+
reason: "Dry run mode skips BOM signing and key generation.",
|
|
1094
|
+
status: "blocked",
|
|
1095
|
+
target: jsonFile,
|
|
1096
|
+
});
|
|
1097
|
+
return jsonPayload;
|
|
1098
|
+
}
|
|
1048
1099
|
let alg = process.env.SBOM_SIGN_ALGORITHM || "RS512";
|
|
1049
1100
|
if (alg.includes("none")) {
|
|
1050
1101
|
alg = "RS512";
|
|
@@ -1068,9 +1119,9 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
|
1068
1119
|
format: "pem",
|
|
1069
1120
|
},
|
|
1070
1121
|
});
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1122
|
+
safeWriteSync(publicKeyFile, publicKey);
|
|
1123
|
+
safeWriteSync(privateKeyFile, privateKey);
|
|
1124
|
+
safeWriteSync(
|
|
1074
1125
|
privateKeyB64File,
|
|
1075
1126
|
Buffer.from(privateKey, "utf8").toString("base64"),
|
|
1076
1127
|
);
|
|
@@ -1122,8 +1173,13 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
|
1122
1173
|
signAnnotations: true,
|
|
1123
1174
|
};
|
|
1124
1175
|
thoughtLog(`Signing the BOM file "${jsonFile}".`);
|
|
1176
|
+
recordActivity({
|
|
1177
|
+
kind: "sign",
|
|
1178
|
+
status: "completed",
|
|
1179
|
+
target: jsonFile,
|
|
1180
|
+
});
|
|
1125
1181
|
const signedBom = signBom(bomJsonUnsignedObj, signOptions);
|
|
1126
|
-
|
|
1182
|
+
safeWriteSync(
|
|
1127
1183
|
jsonFile,
|
|
1128
1184
|
JSON.stringify(signedBom, null, options.jsonPretty ? 2 : null),
|
|
1129
1185
|
);
|
|
@@ -1159,9 +1215,7 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
|
1159
1215
|
// Our quest to audit and check the SBOM generation environment to prevent our users from getting exploited
|
|
1160
1216
|
// during SBOM generation.
|
|
1161
1217
|
if (envAuditFindings?.length) {
|
|
1162
|
-
|
|
1163
|
-
console.log(`SECURE MODE: ${f.variable}: ${f.message}`);
|
|
1164
|
-
}
|
|
1218
|
+
printEnvironmentAuditFindings(envAuditFindings);
|
|
1165
1219
|
// Only abort in secure mode for high or critical findings; low/medium are informational.
|
|
1166
1220
|
if (
|
|
1167
1221
|
isSecureMode &&
|
|
@@ -1177,6 +1231,18 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
|
1177
1231
|
}
|
|
1178
1232
|
let sourcePath = filePath;
|
|
1179
1233
|
let purlResolution;
|
|
1234
|
+
if (isDryRun && maybePurlSource(sourcePath)) {
|
|
1235
|
+
recordActivity({
|
|
1236
|
+
kind: "clone",
|
|
1237
|
+
reason:
|
|
1238
|
+
"Dry run mode blocks package-url source resolution and repository cloning.",
|
|
1239
|
+
status: "blocked",
|
|
1240
|
+
target: sourcePath,
|
|
1241
|
+
});
|
|
1242
|
+
console.warn("Dry run mode skips purl source resolution.");
|
|
1243
|
+
printActivitySummary(options.activityReport);
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1180
1246
|
if (maybePurlSource(sourcePath)) {
|
|
1181
1247
|
const purlValidationError = validatePurlSource(sourcePath);
|
|
1182
1248
|
if (purlValidationError) {
|
|
@@ -1237,6 +1303,17 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
|
1237
1303
|
let cleanup = false;
|
|
1238
1304
|
let gitRef = options.gitBranch;
|
|
1239
1305
|
if (maybeRemotePath(sourcePath)) {
|
|
1306
|
+
if (isDryRun) {
|
|
1307
|
+
recordActivity({
|
|
1308
|
+
kind: "clone",
|
|
1309
|
+
reason: "Dry run mode blocks cloning git URL sources.",
|
|
1310
|
+
status: "blocked",
|
|
1311
|
+
target: sourcePath,
|
|
1312
|
+
});
|
|
1313
|
+
console.warn("Dry run mode skips remote git source cloning.");
|
|
1314
|
+
printActivitySummary(options.activityReport);
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1240
1317
|
if (!gitRef && purlResolution?.version) {
|
|
1241
1318
|
gitRef = findGitRefForPurlVersion(sourcePath, purlResolution);
|
|
1242
1319
|
if (!gitRef) {
|
|
@@ -1267,6 +1344,7 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
|
1267
1344
|
}
|
|
1268
1345
|
cleanup = true;
|
|
1269
1346
|
}
|
|
1347
|
+
setActivityContext({ sourcePath: srcDir });
|
|
1270
1348
|
prepareEnv(srcDir, options);
|
|
1271
1349
|
thoughtLog("Getting ready to generate the BOM ⚡️.");
|
|
1272
1350
|
const originalFetchPackageMetadata = process.env.CDXGEN_FETCH_PKG_METADATA;
|
|
@@ -1290,6 +1368,12 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
|
1290
1368
|
}
|
|
1291
1369
|
// Add extra metadata and annotations with post processing
|
|
1292
1370
|
bomNSData = postProcess(bomNSData, options, srcDir);
|
|
1371
|
+
setActivityContext({
|
|
1372
|
+
projectType: Array.isArray(options.projectType)
|
|
1373
|
+
? options.projectType.join(",")
|
|
1374
|
+
: options.projectType,
|
|
1375
|
+
sourcePath: srcDir,
|
|
1376
|
+
});
|
|
1293
1377
|
if (options.bomAudit && bomNSData?.bomJson) {
|
|
1294
1378
|
const { finalizeAuditReport, runAuditFromBoms } = await import(
|
|
1295
1379
|
"../lib/audit/index.js"
|
|
@@ -1440,53 +1524,73 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
|
1440
1524
|
`cdxgen-${Date.now()}-${basename(filePath)}.cdx.json`,
|
|
1441
1525
|
);
|
|
1442
1526
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1527
|
+
if (isDryRun) {
|
|
1528
|
+
recordActivity({
|
|
1529
|
+
kind: "write",
|
|
1530
|
+
reason:
|
|
1531
|
+
"Dry run mode skips evidence input materialization because it writes a temporary BOM file.",
|
|
1532
|
+
status: "blocked",
|
|
1533
|
+
target: internalCycloneDxInputPath,
|
|
1534
|
+
});
|
|
1535
|
+
} else {
|
|
1536
|
+
safeWriteSync(
|
|
1537
|
+
internalCycloneDxInputPath,
|
|
1538
|
+
stringifyJson(bomNSData.bomJson, options.jsonPretty),
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1447
1541
|
}
|
|
1448
1542
|
// Evidence generation
|
|
1449
1543
|
if (options.evidence || options.includeCrypto) {
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
output
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
);
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1544
|
+
if (isDryRun) {
|
|
1545
|
+
recordActivity({
|
|
1546
|
+
kind: "write",
|
|
1547
|
+
reason:
|
|
1548
|
+
"Dry run mode skips evidence and crypto enrichment because those flows require temp files and additional processing.",
|
|
1549
|
+
status: "blocked",
|
|
1550
|
+
target: options.evinseOutput || options.output || "evinse",
|
|
1551
|
+
});
|
|
1552
|
+
} else {
|
|
1553
|
+
// Set the evinse output file to be the same as output file
|
|
1554
|
+
if (!options.evinseOutput) {
|
|
1555
|
+
options.evinseOutput = options.output;
|
|
1556
|
+
}
|
|
1557
|
+
const evinserModule = await import("../lib/evinser/evinser.js");
|
|
1558
|
+
options.projectType = options.projectType || ["java"];
|
|
1559
|
+
const evinseOptions = {
|
|
1560
|
+
_: args._,
|
|
1561
|
+
input: internalCycloneDxInputPath || options.output,
|
|
1562
|
+
output: options.evinseOutput,
|
|
1563
|
+
language: options.projectType,
|
|
1564
|
+
skipMavenCollector: false,
|
|
1565
|
+
force: false,
|
|
1566
|
+
withReachables: options.deep,
|
|
1567
|
+
usagesSlicesFile: options.usagesSlicesFile,
|
|
1568
|
+
dataFlowSlicesFile: options.dataFlowSlicesFile,
|
|
1569
|
+
reachablesSlicesFile: options.reachablesSlicesFile,
|
|
1570
|
+
semanticsSlicesFile: options.semanticsSlicesFile,
|
|
1571
|
+
openapiSpecFile: options.openapiSpecFile,
|
|
1572
|
+
includeCrypto: options.includeCrypto,
|
|
1573
|
+
specVersion: options.specVersion,
|
|
1574
|
+
profile: options.profile,
|
|
1575
|
+
jsonPretty: options.jsonPretty,
|
|
1576
|
+
};
|
|
1577
|
+
const dbObjMap = await evinserModule.prepareDB(evinseOptions);
|
|
1578
|
+
if (dbObjMap) {
|
|
1579
|
+
const sliceArtefacts = await evinserModule.analyzeProject(
|
|
1580
|
+
dbObjMap,
|
|
1581
|
+
evinseOptions,
|
|
1582
|
+
);
|
|
1583
|
+
const evinseJson = evinserModule.createEvinseFile(
|
|
1584
|
+
sliceArtefacts,
|
|
1585
|
+
evinseOptions,
|
|
1586
|
+
);
|
|
1587
|
+
bomNSData.bomJson = evinseJson;
|
|
1588
|
+
if (options.print && evinseJson) {
|
|
1589
|
+
printOccurrences(evinseJson);
|
|
1590
|
+
printCallStack(evinseJson);
|
|
1591
|
+
printReachables(sliceArtefacts);
|
|
1592
|
+
printServices(evinseJson);
|
|
1593
|
+
}
|
|
1490
1594
|
}
|
|
1491
1595
|
}
|
|
1492
1596
|
}
|
|
@@ -1498,8 +1602,9 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
|
1498
1602
|
cleanupSourceDir(srcDir);
|
|
1499
1603
|
}
|
|
1500
1604
|
process.exit(1);
|
|
1605
|
+
} else {
|
|
1606
|
+
thoughtLog("✅ BOM file looks valid.");
|
|
1501
1607
|
}
|
|
1502
|
-
thoughtLog("✅ BOM file looks valid.");
|
|
1503
1608
|
}
|
|
1504
1609
|
if (
|
|
1505
1610
|
outputPlan.formats.has("spdx") &&
|
|
@@ -1509,16 +1614,31 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
|
1509
1614
|
thoughtLog(
|
|
1510
1615
|
"Preparing the SPDX 3.0.1 export from the validated CycloneDX BOM.",
|
|
1511
1616
|
);
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1617
|
+
if (isDryRun) {
|
|
1618
|
+
recordActivity({
|
|
1619
|
+
kind: "convert",
|
|
1620
|
+
reason:
|
|
1621
|
+
"Dry run mode skips SPDX conversion because the export path is read-only.",
|
|
1622
|
+
status: "blocked",
|
|
1623
|
+
target: "spdx",
|
|
1624
|
+
});
|
|
1625
|
+
} else {
|
|
1626
|
+
bomNSData.spdxJson = convertCycloneDxToSpdx(bomNSData.bomJson, options);
|
|
1627
|
+
recordActivity({
|
|
1628
|
+
kind: "convert",
|
|
1629
|
+
status: "completed",
|
|
1630
|
+
target: "spdx",
|
|
1631
|
+
});
|
|
1632
|
+
if (options.validate && !validateSpdx(bomNSData.spdxJson)) {
|
|
1633
|
+
process.exit(1);
|
|
1634
|
+
}
|
|
1515
1635
|
}
|
|
1516
1636
|
}
|
|
1517
1637
|
if (
|
|
1518
1638
|
options.output &&
|
|
1519
1639
|
(typeof options.output === "string" || options.output instanceof String)
|
|
1520
1640
|
) {
|
|
1521
|
-
if (outputPlan.outputs.cyclonedx && bomNSData.bomJson) {
|
|
1641
|
+
if (!isDryRun && outputPlan.outputs.cyclonedx && bomNSData.bomJson) {
|
|
1522
1642
|
writeCycloneDxOutput(
|
|
1523
1643
|
outputPlan.outputs.cyclonedx,
|
|
1524
1644
|
bomNSData.bomJson,
|
|
@@ -1526,15 +1646,29 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
|
1526
1646
|
);
|
|
1527
1647
|
if (bomNSData.nsMapping && Object.keys(bomNSData.nsMapping).length) {
|
|
1528
1648
|
const nsFile = `${outputPlan.outputs.cyclonedx}.map`;
|
|
1529
|
-
|
|
1649
|
+
safeWriteSync(nsFile, JSON.stringify(bomNSData.nsMapping));
|
|
1530
1650
|
}
|
|
1651
|
+
} else if (isDryRun && outputPlan.outputs.cyclonedx) {
|
|
1652
|
+
recordActivity({
|
|
1653
|
+
kind: "write",
|
|
1654
|
+
reason: "Dry run mode skips CycloneDX file output.",
|
|
1655
|
+
status: "blocked",
|
|
1656
|
+
target: outputPlan.outputs.cyclonedx,
|
|
1657
|
+
});
|
|
1531
1658
|
}
|
|
1532
|
-
if (outputPlan.outputs.spdx && bomNSData.spdxJson) {
|
|
1533
|
-
|
|
1659
|
+
if (!isDryRun && outputPlan.outputs.spdx && bomNSData.spdxJson) {
|
|
1660
|
+
safeWriteSync(
|
|
1534
1661
|
outputPlan.outputs.spdx,
|
|
1535
1662
|
stringifyJson(bomNSData.spdxJson, options.jsonPretty),
|
|
1536
1663
|
);
|
|
1537
1664
|
thoughtLog(`Let's save the SPDX file to "${outputPlan.outputs.spdx}".`);
|
|
1665
|
+
} else if (isDryRun && outputPlan.outputs.spdx) {
|
|
1666
|
+
recordActivity({
|
|
1667
|
+
kind: "write",
|
|
1668
|
+
reason: "Dry run mode skips SPDX file output.",
|
|
1669
|
+
status: "blocked",
|
|
1670
|
+
target: outputPlan.outputs.spdx,
|
|
1671
|
+
});
|
|
1538
1672
|
}
|
|
1539
1673
|
} else if (!options.print) {
|
|
1540
1674
|
if (outputPlan.formats.has("spdx") && bomNSData?.spdxJson) {
|
|
@@ -1550,21 +1684,44 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
|
1550
1684
|
// Automatically submit the bom data
|
|
1551
1685
|
// biome-ignore lint/suspicious/noDoubleEquals: yargs passes true for empty values
|
|
1552
1686
|
if (options.serverUrl && options.serverUrl != true && options.apiKey) {
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1687
|
+
if (isDryRun) {
|
|
1688
|
+
recordActivity({
|
|
1689
|
+
kind: "submit",
|
|
1690
|
+
reason: "Dry run mode skips remote BOM submission.",
|
|
1691
|
+
status: "blocked",
|
|
1692
|
+
target: options.serverUrl,
|
|
1693
|
+
});
|
|
1694
|
+
} else {
|
|
1695
|
+
try {
|
|
1696
|
+
recordActivity({
|
|
1697
|
+
kind: "submit",
|
|
1698
|
+
status: "completed",
|
|
1699
|
+
target: options.serverUrl,
|
|
1700
|
+
});
|
|
1701
|
+
await submitBom(options, bomNSData.bomJson);
|
|
1702
|
+
} catch (err) {
|
|
1703
|
+
console.log(err);
|
|
1704
|
+
if (cleanup) {
|
|
1705
|
+
cleanupSourceDir(srcDir);
|
|
1706
|
+
}
|
|
1707
|
+
process.exit(1);
|
|
1559
1708
|
}
|
|
1560
|
-
process.exit(1);
|
|
1561
1709
|
}
|
|
1562
1710
|
}
|
|
1563
1711
|
// Protobuf serialization
|
|
1564
1712
|
if (options.exportProto) {
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1713
|
+
if (isDryRun) {
|
|
1714
|
+
recordActivity({
|
|
1715
|
+
kind: "write",
|
|
1716
|
+
reason: "Dry run mode skips protobuf export.",
|
|
1717
|
+
status: "blocked",
|
|
1718
|
+
target: options.protoBinFile,
|
|
1719
|
+
});
|
|
1720
|
+
} else {
|
|
1721
|
+
const protobomModule = await import("../lib/helpers/protobom.js");
|
|
1722
|
+
protobomModule.writeBinary(bomNSData.bomJson, options.protoBinFile);
|
|
1723
|
+
thoughtLog("BOM file is also available in .proto format!");
|
|
1724
|
+
}
|
|
1568
1725
|
}
|
|
1569
1726
|
if (options.print && bomNSData.bomJson?.components) {
|
|
1570
1727
|
printSummary(bomNSData.bomJson);
|
|
@@ -1579,6 +1736,9 @@ const writeCycloneDxOutput = (jsonFile, bomJson, options) => {
|
|
|
1579
1736
|
printDependencyTree(bomNSData.bomJson, "provides");
|
|
1580
1737
|
}
|
|
1581
1738
|
}
|
|
1739
|
+
if (isDryRun || DEBUG_MODE) {
|
|
1740
|
+
printActivitySummary(options.activityReport);
|
|
1741
|
+
}
|
|
1582
1742
|
if (
|
|
1583
1743
|
(DEBUG_MODE || TRACE_MODE) &&
|
|
1584
1744
|
(!process.env?.CDXGEN_ALLOWED_HOSTS ||
|