@cdxgen/cdxgen 13.2.0 → 13.4.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 +33 -14
- package/bin/cdxgen.js +310 -138
- package/bin/evinse.js +22 -0
- package/bin/repl.js +86 -0
- package/bin/sign.js +119 -15
- package/bin/tracebom.js +69 -5
- package/bin/validate.js +107 -19
- package/bin/verify.js +172 -63
- package/data/helpers/init.gradle +46 -21
- package/data/remediations.json +85 -0
- package/data/rules/dependency-sources.yaml +67 -0
- package/data/rules/scala-evidence.yaml +115 -0
- package/lib/audit/index.js +38 -6
- package/lib/cli/bomAssembly.js +105 -15
- package/lib/cli/cliOptions.js +93 -1
- package/lib/cli/index.js +156 -58
- package/lib/cli/jsBom.js +99 -53
- package/lib/cli/jvmBom.js +708 -147
- package/lib/cli/managedBom.js +415 -224
- package/lib/cli/nativeBom.js +264 -44
- package/lib/core/activity.js +12 -0
- package/lib/core/env.js +22 -15
- package/lib/core/fs.js +176 -7
- package/lib/core/httpClient.js +159 -24
- package/lib/core/metadataProperties.js +97 -0
- package/lib/core/pluginRun.js +184 -0
- package/lib/core/processTree.js +100 -0
- package/lib/core/projectConfig.js +31 -4
- package/lib/core/runState.js +39 -0
- package/lib/core/toml.js +72 -0
- package/lib/ecosystems/asarutils.js +4 -2
- package/lib/ecosystems/bunutils.js +274 -108
- package/lib/ecosystems/cargoCache.js +231 -8
- package/lib/ecosystems/caxa.js +434 -2
- package/lib/ecosystems/cmakeResolver.js +76 -21
- package/lib/ecosystems/cppBuildContext.js +631 -0
- package/lib/ecosystems/cppEvidence.js +399 -51
- package/lib/ecosystems/cppIncludeResolver.js +294 -0
- package/lib/ecosystems/denoutils.js +93 -66
- package/lib/ecosystems/ecosystems.js +1983 -793
- package/lib/ecosystems/golem.js +28 -12
- package/lib/ecosystems/gradleutils.js +356 -37
- package/lib/ecosystems/jsEvidence.js +104 -44
- package/lib/ecosystems/kosi.js +31 -7
- package/lib/ecosystems/npmutils.js +174 -9
- package/lib/ecosystems/parsers-dotnet.js +616 -159
- package/lib/ecosystems/parsers-elm.js +46 -28
- package/lib/ecosystems/parsers-gleam.js +3 -4
- package/lib/ecosystems/parsers-go.js +371 -70
- package/lib/ecosystems/parsers-js.js +263 -76
- package/lib/ecosystems/parsers-julia.js +2 -3
- package/lib/ecosystems/parsers-jvm.js +204 -12
- package/lib/ecosystems/parsers-misc.js +929 -216
- package/lib/ecosystems/parsers-python.js +167 -24
- package/lib/ecosystems/parsers-rust.js +28 -22
- package/lib/ecosystems/parsers-terraform.js +1573 -19
- package/lib/ecosystems/pythonutils.js +102 -6
- package/lib/ecosystems/remote/huggingface.js +3 -0
- package/lib/ecosystems/rubyutils.js +1492 -182
- package/lib/ecosystems/rusi.js +54 -10
- package/lib/ecosystems/sbtutils.js +243 -187
- package/lib/ecosystems/scalacliutils.js +342 -0
- package/lib/ecosystems/scalasem.js +2618 -0
- package/lib/ecosystems/terraformRegistry.js +288 -0
- package/lib/ecosystems/utils.js +2 -0
- package/lib/ecosystems/vendoredCode.js +156 -0
- package/lib/evinser/evinser.js +1008 -191
- package/lib/evinser/swiftsem.js +1914 -302
- package/lib/helpers/agenticToolutils.js +2 -2
- package/lib/helpers/bomSigner.js +1101 -222
- package/lib/inventory/aiCollector.js +4 -1
- package/lib/inventory/analyzer.js +110 -127
- package/lib/inventory/atomUtils.js +703 -43
- package/lib/inventory/auditCategories.js +7 -0
- package/lib/inventory/buildToolRateLimit.js +144 -0
- package/lib/inventory/cacheCommands.js +2 -1
- package/lib/inventory/cdxrs.js +103 -12
- package/lib/inventory/cmakeBuildDirs.js +254 -0
- package/lib/inventory/deps.js +501 -124
- package/lib/inventory/depsUtils.js +183 -11
- package/lib/inventory/display.js +4 -5
- package/lib/inventory/dosai.js +798 -57
- package/lib/inventory/dosaiParsers.js +310 -15
- package/lib/inventory/dynamic.js +90 -6
- package/lib/inventory/evidenceUtils.js +166 -113
- package/lib/inventory/fetchBatch.js +690 -55
- package/lib/inventory/fetchDiskCache.js +597 -0
- package/lib/inventory/forgeEnricher.js +14 -5
- package/lib/inventory/jvmLocalRepos.js +917 -0
- package/lib/inventory/mcpConfigParser.js +2 -3
- package/lib/inventory/parentSelection.js +146 -0
- package/lib/inventory/protobom.js +8 -1
- package/lib/inventory/purl.js +152 -3
- package/lib/inventory/rustFormulationParser.js +3 -4
- package/lib/inventory/sbomDocument.js +2 -70
- package/lib/inventory/scalaCoords.js +153 -0
- package/lib/inventory/source.js +45 -27
- package/lib/inventory/spdx.js +101 -0
- package/lib/inventory/traceRunner.js +295 -25
- package/lib/inventory/tracedCrypto.js +294 -0
- package/lib/managers/binary.js +115 -24
- package/lib/managers/docker.js +467 -46
- package/lib/managers/oci.js +63 -3
- package/lib/parsers/cmakeCache.js +69 -0
- package/lib/parsers/cmakeLists.js +466 -0
- package/lib/parsers/cmakePresets.js +400 -0
- package/lib/parsers/compileCommands.js +557 -0
- package/lib/parsers/largeJson.js +454 -0
- package/lib/parsers/npmrc.js +19 -1
- package/lib/parsers/strictJson.js +322 -0
- package/lib/parsers/terraformConfig.js +1073 -0
- package/lib/parsers/terraformSource.js +652 -0
- package/lib/server/server.js +23 -0
- package/lib/stages/postgen/introspection/markers.js +1 -1
- package/lib/stages/postgen/postgen.js +83 -16
- package/lib/stages/postgen/specVersionCompat.js +32 -0
- package/lib/validator/bomValidator.js +6 -16
- package/lib/validator/index.js +64 -11
- package/lib/validator/reporters/console.js +3 -0
- package/lib/validator/reporters/json.js +2 -0
- package/lib/validator/reporters/sarif.js +7 -0
- package/package.json +23 -23
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +2 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +33 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +24 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +7 -4
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +3 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +9 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +55 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts +13 -8
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/metadataProperties.d.ts +39 -0
- package/types/lib/core/metadataProperties.d.ts.map +1 -0
- package/types/lib/core/pluginRun.d.ts +97 -0
- package/types/lib/core/pluginRun.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +36 -0
- package/types/lib/core/processTree.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +6 -2
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/runState.d.ts +29 -0
- package/types/lib/core/runState.d.ts.map +1 -0
- package/types/lib/core/toml.d.ts +20 -0
- package/types/lib/core/toml.d.ts.map +1 -0
- package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
- package/types/lib/ecosystems/bunutils.d.ts +12 -5
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/cargoCache.d.ts +12 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
- package/types/lib/ecosystems/caxa.d.ts +14 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +28 -3
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
- package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +17 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
- package/types/lib/ecosystems/denoutils.d.ts +9 -3
- package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +62 -13
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +70 -4
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +13 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmutils.d.ts +57 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +54 -2
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +16 -5
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +70 -4
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +53 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +27 -2
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +164 -3
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts +13 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/sbtutils.d.ts +88 -2
- package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
- package/types/lib/ecosystems/scalasem.d.ts +311 -0
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/utils.d.ts +2 -2
- package/types/lib/ecosystems/utils.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +132 -15
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +280 -15
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +182 -13
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +219 -5
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +3 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
- package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
- package/types/lib/inventory/cacheCommands.d.ts +2 -1
- package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
- package/types/lib/inventory/cdxrs.d.ts +8 -3
- package/types/lib/inventory/cdxrs.d.ts.map +1 -1
- package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
- package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +59 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts +15 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +121 -9
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts +53 -7
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts +10 -0
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +19 -4
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +83 -14
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
- package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/parentSelection.d.ts +34 -0
- package/types/lib/inventory/parentSelection.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/purl.d.ts +71 -2
- package/types/lib/inventory/purl.d.ts.map +1 -1
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts +80 -0
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +9 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +59 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +42 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts +30 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +29 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +17 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeCache.d.ts +16 -0
- package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +77 -0
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
- package/types/lib/parsers/cmakePresets.d.ts +78 -0
- package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
- package/types/lib/parsers/compileCommands.d.ts +115 -0
- package/types/lib/parsers/compileCommands.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +52 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +5 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/parsers/strictJson.d.ts +46 -0
- package/types/lib/parsers/strictJson.d.ts.map +1 -0
- package/types/lib/parsers/terraformConfig.d.ts +70 -0
- package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
- package/types/lib/parsers/terraformSource.d.ts +66 -0
- package/types/lib/parsers/terraformSource.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +18 -1
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
package/bin/repl.js
CHANGED
|
@@ -1741,6 +1741,92 @@ cdxgenRepl.defineCommand("golemcoverage", {
|
|
|
1741
1741
|
this.displayPrompt();
|
|
1742
1742
|
},
|
|
1743
1743
|
});
|
|
1744
|
+
function hasScalasemProperties(component) {
|
|
1745
|
+
return Boolean(
|
|
1746
|
+
component?.properties?.some((property) =>
|
|
1747
|
+
property?.name?.startsWith("cdx:scalasem:"),
|
|
1748
|
+
),
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
cdxgenRepl.defineCommand("scalasemsummary", {
|
|
1753
|
+
help: "summarize Scala Evinse/scalasem metadata and evidence coverage",
|
|
1754
|
+
action() {
|
|
1755
|
+
const interactiveBom = getInteractiveBom();
|
|
1756
|
+
if (!interactiveBom) {
|
|
1757
|
+
console.log("⚠ No BOM is loaded. Use .import command to import an SBOM");
|
|
1758
|
+
this.displayPrompt();
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
if (!hasScalasemProperties(interactiveBom.metadata?.component)) {
|
|
1762
|
+
console.log(
|
|
1763
|
+
"No scalasem properties found. Generate an evidence BOM with evinse -l scala or cdxgen --evidence to use this view.",
|
|
1764
|
+
);
|
|
1765
|
+
this.displayPrompt();
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
const rootComponent = interactiveBom.metadata?.component || {};
|
|
1769
|
+
const components = interactiveBom.components || [];
|
|
1770
|
+
const occurrences = components.reduce(
|
|
1771
|
+
(total, component) =>
|
|
1772
|
+
total + (component?.evidence?.occurrences?.length || 0),
|
|
1773
|
+
0,
|
|
1774
|
+
);
|
|
1775
|
+
const occurrencesWithLine = components.reduce(
|
|
1776
|
+
(total, component) =>
|
|
1777
|
+
total +
|
|
1778
|
+
(component?.evidence?.occurrences || []).filter(
|
|
1779
|
+
(occurrence) => occurrence?.line,
|
|
1780
|
+
).length,
|
|
1781
|
+
0,
|
|
1782
|
+
);
|
|
1783
|
+
const callstacks = components.filter(
|
|
1784
|
+
(component) => component?.evidence?.callstack?.frames?.length,
|
|
1785
|
+
).length;
|
|
1786
|
+
const cryptoAssets = components.filter(
|
|
1787
|
+
(component) => component?.type === "cryptographic-asset",
|
|
1788
|
+
).length;
|
|
1789
|
+
printKeyValueTable("Scala Evinse / scalasem summary", [
|
|
1790
|
+
[
|
|
1791
|
+
"Schema version",
|
|
1792
|
+
getPropertyValue(rootComponent, "cdx:scalasem:schemaVersion"),
|
|
1793
|
+
],
|
|
1794
|
+
[
|
|
1795
|
+
"Facts source",
|
|
1796
|
+
getPropertyValue(rootComponent, "cdx:scalasem:factsSource"),
|
|
1797
|
+
],
|
|
1798
|
+
[
|
|
1799
|
+
"Scala versions",
|
|
1800
|
+
getPropertyValue(rootComponent, "cdx:scalasem:scalaVersions"),
|
|
1801
|
+
],
|
|
1802
|
+
["Platforms", getPropertyValue(rootComponent, "cdx:scalasem:platforms")],
|
|
1803
|
+
[
|
|
1804
|
+
"Files analyzed",
|
|
1805
|
+
getPropertyValue(rootComponent, "cdx:scalasem:filesAnalyzed"),
|
|
1806
|
+
],
|
|
1807
|
+
["Degraded", getPropertyValue(rootComponent, "cdx:scalasem:degraded")],
|
|
1808
|
+
...(rootComponent.properties || [])
|
|
1809
|
+
.filter((property) =>
|
|
1810
|
+
property?.name?.startsWith("cdx:scalasem:diagnostic:"),
|
|
1811
|
+
)
|
|
1812
|
+
.map((property) => [
|
|
1813
|
+
`Diagnostic ${property.name.slice("cdx:scalasem:diagnostic:".length)}`,
|
|
1814
|
+
property.value,
|
|
1815
|
+
]),
|
|
1816
|
+
[
|
|
1817
|
+
"Components with occurrences",
|
|
1818
|
+
components.filter((c) => c?.evidence?.occurrences?.length).length,
|
|
1819
|
+
],
|
|
1820
|
+
["Occurrences", occurrences],
|
|
1821
|
+
["Occurrences with line", occurrencesWithLine],
|
|
1822
|
+
["Components with call stacks", callstacks],
|
|
1823
|
+
["Crypto assets", cryptoAssets],
|
|
1824
|
+
["Services", (interactiveBom.services || []).length],
|
|
1825
|
+
]);
|
|
1826
|
+
this.displayPrompt();
|
|
1827
|
+
},
|
|
1828
|
+
});
|
|
1829
|
+
|
|
1744
1830
|
cdxgenRepl.defineCommand("auditfindings", {
|
|
1745
1831
|
help: "summarize cdx-audit and bom-audit annotations from the loaded BOM",
|
|
1746
1832
|
action() {
|
package/bin/sign.js
CHANGED
|
@@ -11,11 +11,16 @@ import {
|
|
|
11
11
|
retrieveCdxgenVersion,
|
|
12
12
|
safeExistsSync,
|
|
13
13
|
} from "../lib/ecosystems/utils.js";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
checkSignatureEntries,
|
|
16
|
+
displayValue,
|
|
17
|
+
signBom,
|
|
18
|
+
} from "../lib/helpers/bomSigner.js";
|
|
15
19
|
import {
|
|
16
20
|
getNonCycloneDxErrorMessage,
|
|
17
21
|
isCycloneDxBom,
|
|
18
22
|
} from "../lib/inventory/bomUtils.js";
|
|
23
|
+
import { parseStrictJson } from "../lib/parsers/strictJson.js";
|
|
19
24
|
|
|
20
25
|
const _yargs = yargs(hideBin(process.argv));
|
|
21
26
|
|
|
@@ -31,12 +36,14 @@ const args = _yargs
|
|
|
31
36
|
})
|
|
32
37
|
.option("private-key", {
|
|
33
38
|
alias: "k",
|
|
34
|
-
description:
|
|
39
|
+
description:
|
|
40
|
+
"Private key in PEM format, or the shared secret file for HS256, HS384, and HS512.",
|
|
35
41
|
})
|
|
36
42
|
.option("algorithm", {
|
|
37
43
|
alias: "a",
|
|
38
44
|
default: readEnvironmentVariable("SBOM_SIGN_ALGORITHM") || "RS512",
|
|
39
|
-
description:
|
|
45
|
+
description:
|
|
46
|
+
"JSF Signature Algorithm (e.g., RS512, ES256, Ed25519). It must match the key type.",
|
|
40
47
|
})
|
|
41
48
|
.option("mode", {
|
|
42
49
|
alias: "m",
|
|
@@ -45,27 +52,42 @@ const args = _yargs
|
|
|
45
52
|
description:
|
|
46
53
|
"Signature mode. Use 'signers' for multi-signing, 'chain' for sequential chaining.",
|
|
47
54
|
})
|
|
55
|
+
.option("verify-existing-with", {
|
|
56
|
+
type: "array",
|
|
57
|
+
string: true,
|
|
58
|
+
description:
|
|
59
|
+
"Public key (PEM) of an earlier signer. Before a new entry is appended, every existing chain entry (--mode chain), or at least one existing entry (--mode signers), must verify with one of these keys.",
|
|
60
|
+
})
|
|
61
|
+
.option("allow-unverified-history", {
|
|
62
|
+
type: "boolean",
|
|
63
|
+
default: false,
|
|
64
|
+
description:
|
|
65
|
+
"With --mode chain or signers, append even when the existing signature entries cannot be verified with --verify-existing-with.",
|
|
66
|
+
})
|
|
67
|
+
.option("embed-public-key", {
|
|
68
|
+
type: "boolean",
|
|
69
|
+
default: true,
|
|
70
|
+
description:
|
|
71
|
+
"Embed the signer's public key in the root signature, which binds the signature to that key. Not used for HMAC (HS*) algorithms. Pass --no-embed-public-key to leave it out.",
|
|
72
|
+
})
|
|
48
73
|
.option("key-id", {
|
|
49
74
|
description:
|
|
50
75
|
"Optional identifier for the key (keyId) to embed in the signature block.",
|
|
51
76
|
})
|
|
52
77
|
.option("sign-components", {
|
|
53
78
|
type: "boolean",
|
|
54
|
-
default: true,
|
|
55
79
|
description:
|
|
56
|
-
"Sign granular components.
|
|
80
|
+
"Sign granular components. Defaults to true, or false when appending a signers or chain signature to a signed BOM.",
|
|
57
81
|
})
|
|
58
82
|
.option("sign-services", {
|
|
59
83
|
type: "boolean",
|
|
60
|
-
default: true,
|
|
61
84
|
description:
|
|
62
|
-
"Sign granular services.
|
|
85
|
+
"Sign granular services. Defaults to true, or false when appending a signers or chain signature to a signed BOM.",
|
|
63
86
|
})
|
|
64
87
|
.option("sign-annotations", {
|
|
65
88
|
type: "boolean",
|
|
66
|
-
default: true,
|
|
67
89
|
description:
|
|
68
|
-
"Sign granular annotations.
|
|
90
|
+
"Sign granular annotations. Defaults to true, or false when appending a signers or chain signature to a signed BOM.",
|
|
69
91
|
})
|
|
70
92
|
.option("attach", {
|
|
71
93
|
type: "string",
|
|
@@ -97,7 +119,7 @@ const envPrivateKeyBase64 = readEnvironmentVariable(
|
|
|
97
119
|
|
|
98
120
|
if (args.privateKey) {
|
|
99
121
|
if (safeExistsSync(args.privateKey)) {
|
|
100
|
-
privateKeyContent = fs.readFileSync(args.privateKey
|
|
122
|
+
privateKeyContent = fs.readFileSync(args.privateKey);
|
|
101
123
|
hasPrivateKey = true;
|
|
102
124
|
} else {
|
|
103
125
|
console.error(`Private key file '${args.privateKey}' not found.`);
|
|
@@ -105,7 +127,7 @@ if (args.privateKey) {
|
|
|
105
127
|
}
|
|
106
128
|
} else if (envPrivateKeyFile) {
|
|
107
129
|
if (safeExistsSync(envPrivateKeyFile)) {
|
|
108
|
-
privateKeyContent = fs.readFileSync(envPrivateKeyFile
|
|
130
|
+
privateKeyContent = fs.readFileSync(envPrivateKeyFile);
|
|
109
131
|
hasPrivateKey = true;
|
|
110
132
|
} else {
|
|
111
133
|
console.error(
|
|
@@ -115,9 +137,7 @@ if (args.privateKey) {
|
|
|
115
137
|
}
|
|
116
138
|
} else if (envPrivateKeyBase64) {
|
|
117
139
|
try {
|
|
118
|
-
privateKeyContent = Buffer.from(envPrivateKeyBase64, "base64")
|
|
119
|
-
"utf8",
|
|
120
|
-
);
|
|
140
|
+
privateKeyContent = Buffer.from(envPrivateKeyBase64, "base64");
|
|
121
141
|
hasPrivateKey = true;
|
|
122
142
|
} catch {
|
|
123
143
|
console.error(
|
|
@@ -147,8 +167,75 @@ function hasAnySignature(bomJson) {
|
|
|
147
167
|
return false;
|
|
148
168
|
}
|
|
149
169
|
|
|
170
|
+
function describeEntry(result) {
|
|
171
|
+
const parts = [];
|
|
172
|
+
if (result.algorithm !== undefined) {
|
|
173
|
+
parts.push(displayValue(result.algorithm));
|
|
174
|
+
}
|
|
175
|
+
if (result.keyId !== undefined) {
|
|
176
|
+
parts.push(`keyId '${displayValue(result.keyId)}'`);
|
|
177
|
+
}
|
|
178
|
+
return parts.length
|
|
179
|
+
? `entry ${result.index} (${parts.join(", ")})`
|
|
180
|
+
: `entry ${result.index}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// A new chain entry covers every earlier entry, so the signer vouches for that
|
|
184
|
+
// history, and every entry must verify. A new signers entry covers the same
|
|
185
|
+
// content as the existing entries, so at least one must verify to show the
|
|
186
|
+
// content is still what an earlier signer signed.
|
|
187
|
+
function checkExistingSignatures(bomJson) {
|
|
188
|
+
const existing = bomJson.signature;
|
|
189
|
+
const otherForm = args.mode === "chain" ? "signers" : "chain";
|
|
190
|
+
if (
|
|
191
|
+
args.mode === "replace" ||
|
|
192
|
+
existing === undefined ||
|
|
193
|
+
(existing !== null &&
|
|
194
|
+
typeof existing === "object" &&
|
|
195
|
+
Object.hasOwn(existing, otherForm))
|
|
196
|
+
) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const keys = (args.verifyExistingWith || []).map((keyFile) => {
|
|
200
|
+
if (!safeExistsSync(keyFile)) {
|
|
201
|
+
throw new Error(`Public key file '${keyFile}' not found.`);
|
|
202
|
+
}
|
|
203
|
+
return fs.readFileSync(keyFile, "utf8");
|
|
204
|
+
});
|
|
205
|
+
const results = checkSignatureEntries(bomJson, keys);
|
|
206
|
+
const unverified = results.filter((result) => !result.verified);
|
|
207
|
+
const chain = args.mode === "chain";
|
|
208
|
+
if (chain ? !unverified.length : unverified.length < results.length) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const details = unverified.map(
|
|
212
|
+
(result) =>
|
|
213
|
+
` - ${describeEntry(result)}: ${result.reasons[0] || "no --verify-existing-with key was given"}`,
|
|
214
|
+
);
|
|
215
|
+
if (args.allowUnverifiedHistory) {
|
|
216
|
+
console.warn(
|
|
217
|
+
[
|
|
218
|
+
chain
|
|
219
|
+
? `Warning: appending to a chain with ${unverified.length} unverified entry(s):`
|
|
220
|
+
: `Warning: appending a signers entry although none of the ${unverified.length} existing entry(s) verified:`,
|
|
221
|
+
...details,
|
|
222
|
+
].join("\n"),
|
|
223
|
+
);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
throw new Error(
|
|
227
|
+
[
|
|
228
|
+
chain
|
|
229
|
+
? `the new chain entry would vouch for ${unverified.length} existing entry(s) that could not be verified:`
|
|
230
|
+
: `none of the ${unverified.length} existing signature entry(s) could be verified, so the content may have changed since it was signed:`,
|
|
231
|
+
...details,
|
|
232
|
+
"Pass --verify-existing-with <public key> for each earlier signer, or --allow-unverified-history to sign anyway.",
|
|
233
|
+
].join("\n"),
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
150
237
|
try {
|
|
151
|
-
const bomJson =
|
|
238
|
+
const bomJson = parseStrictJson(fs.readFileSync(args.input));
|
|
152
239
|
if (!isCycloneDxBom(bomJson)) {
|
|
153
240
|
console.error(getNonCycloneDxErrorMessage(bomJson, "cdx-sign"));
|
|
154
241
|
process.exit(1);
|
|
@@ -156,9 +243,12 @@ try {
|
|
|
156
243
|
|
|
157
244
|
let signedBom = bomJson;
|
|
158
245
|
if (hasPrivateKey && privateKeyContent) {
|
|
246
|
+
checkExistingSignatures(bomJson);
|
|
159
247
|
signedBom = signBom(bomJson, {
|
|
160
248
|
privateKey: privateKeyContent,
|
|
161
249
|
algorithm: args.algorithm,
|
|
250
|
+
embedPublicKey: args.embedPublicKey,
|
|
251
|
+
embedPublicKeyNested: false,
|
|
162
252
|
keyId: args.keyId,
|
|
163
253
|
mode: args.mode,
|
|
164
254
|
signComponents: args.signComponents,
|
|
@@ -173,6 +263,20 @@ try {
|
|
|
173
263
|
console.log(
|
|
174
264
|
`Mode: ${args.mode} | Algorithm: ${args.algorithm}${args.keyId ? ` | KeyId: ${args.keyId}` : ""}`,
|
|
175
265
|
);
|
|
266
|
+
const rootSignature = signedBom.signature;
|
|
267
|
+
const newEntry =
|
|
268
|
+
rootSignature?.signers?.at(-1) ??
|
|
269
|
+
rootSignature?.chain?.at(-1) ??
|
|
270
|
+
rootSignature;
|
|
271
|
+
if (
|
|
272
|
+
args.embedPublicKey &&
|
|
273
|
+
!String(args.algorithm).startsWith("HS") &&
|
|
274
|
+
newEntry?.publicKey === undefined
|
|
275
|
+
) {
|
|
276
|
+
console.warn(
|
|
277
|
+
"Warning: this key type has no JWK form, so the public key was not embedded in the signature.",
|
|
278
|
+
);
|
|
279
|
+
}
|
|
176
280
|
} else {
|
|
177
281
|
if (!hasAnySignature(bomJson)) {
|
|
178
282
|
console.error(
|
package/bin/tracebom.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
safeWriteSync,
|
|
15
15
|
} from "../lib/ecosystems/utils.js";
|
|
16
16
|
import { DEFAULT_CDX_SPEC_VERSION } from "../lib/inventory/bomUtils.js";
|
|
17
|
+
import { applySpecVersionCompatibility } from "../lib/stages/postgen/specVersionCompat.js";
|
|
17
18
|
|
|
18
19
|
const _yargs = yargs(hideBin(process.argv));
|
|
19
20
|
|
|
@@ -79,7 +80,7 @@ const args = _yargs
|
|
|
79
80
|
})
|
|
80
81
|
.option("disable-network", {
|
|
81
82
|
description:
|
|
82
|
-
"Disable network inside sandbox. Automatically disabled when --trace-http-urls is set.",
|
|
83
|
+
"Disable network inside sandbox. Automatically disabled when --trace-http-urls or --proxy-egress is set.",
|
|
83
84
|
default: true,
|
|
84
85
|
type: "boolean",
|
|
85
86
|
})
|
|
@@ -166,10 +167,60 @@ const args = _yargs
|
|
|
166
167
|
})
|
|
167
168
|
.option("trace-crypto", {
|
|
168
169
|
description:
|
|
169
|
-
"
|
|
170
|
+
"Trace cryptographic libraries and TLS parameters: eBPF on Linux, crypto DLLs on Windows (elevated prompt), and TLS handshakes through --proxy-egress on every platform.",
|
|
170
171
|
default: true,
|
|
171
172
|
type: "boolean",
|
|
172
173
|
})
|
|
174
|
+
.option("proxy-egress", {
|
|
175
|
+
description:
|
|
176
|
+
"Route all outbound traffic through safer-exec's hostname-pinning egress proxy. Only --allow-host (and --allow-url) hosts are reachable; allowed and denied targets are recorded as services. Enables the network.",
|
|
177
|
+
default: false,
|
|
178
|
+
type: "boolean",
|
|
179
|
+
})
|
|
180
|
+
.option("allow-loopback", {
|
|
181
|
+
description: "Allow the traced process to connect to loopback addresses.",
|
|
182
|
+
default: false,
|
|
183
|
+
type: "boolean",
|
|
184
|
+
})
|
|
185
|
+
.option("sandbox-dry-run", {
|
|
186
|
+
description:
|
|
187
|
+
"Deny every filesystem write and network connection and record the attempted operations as counts in the BOM metadata.",
|
|
188
|
+
default: false,
|
|
189
|
+
type: "boolean",
|
|
190
|
+
})
|
|
191
|
+
.option("policy", {
|
|
192
|
+
description:
|
|
193
|
+
"Apply a named safer-exec ecosystem policy (for example npm, pnpm, pypi, uv, maven, cargo, gomod, nuget) before the other sandbox options.",
|
|
194
|
+
type: "string",
|
|
195
|
+
})
|
|
196
|
+
.option("policy-file", {
|
|
197
|
+
description: "Apply a safer-exec JSON policy file.",
|
|
198
|
+
type: "string",
|
|
199
|
+
})
|
|
200
|
+
.option("block-interpreters", {
|
|
201
|
+
description:
|
|
202
|
+
"Block interpreters that carry sandbox or task-port exemptions (macOS only).",
|
|
203
|
+
default: false,
|
|
204
|
+
type: "boolean",
|
|
205
|
+
})
|
|
206
|
+
.option("deny-persistence-writes", {
|
|
207
|
+
description:
|
|
208
|
+
"Deny writes to auto-execution and persistence locations such as LaunchAgents, shell rc files and cron directories. Paths in --write-paths stay writable.",
|
|
209
|
+
default: false,
|
|
210
|
+
type: "boolean",
|
|
211
|
+
})
|
|
212
|
+
.option("private-tmp", {
|
|
213
|
+
description:
|
|
214
|
+
"Give the traced process private /tmp and /var/tmp mounts (Linux only).",
|
|
215
|
+
default: false,
|
|
216
|
+
type: "boolean",
|
|
217
|
+
})
|
|
218
|
+
.option("protect-home", {
|
|
219
|
+
description: "Isolate $HOME inside the sandbox (Linux only).",
|
|
220
|
+
choices: ["off", "read-only", "tmpfs"],
|
|
221
|
+
default: "off",
|
|
222
|
+
type: "string",
|
|
223
|
+
})
|
|
173
224
|
.option("print", {
|
|
174
225
|
description: "Print BOM to stdout.",
|
|
175
226
|
default: false,
|
|
@@ -230,9 +281,10 @@ const options = {
|
|
|
230
281
|
traceMaxMemoryMB: args.maxMemory,
|
|
231
282
|
traceMaxProcesses: args.maxProcesses,
|
|
232
283
|
traceTimeoutMs: args.timeout,
|
|
233
|
-
traceDisableNetwork:
|
|
234
|
-
|
|
235
|
-
|
|
284
|
+
traceDisableNetwork:
|
|
285
|
+
args.traceHttpUrls || args.proxyEgress
|
|
286
|
+
? false
|
|
287
|
+
: (args.disableNetwork ?? true),
|
|
236
288
|
traceHTTPURLs: args.traceHttpUrls ?? false,
|
|
237
289
|
tracePeriod: args.tracePeriod,
|
|
238
290
|
traceMaxCPUCores: args.maxCpu,
|
|
@@ -265,6 +317,15 @@ const options = {
|
|
|
265
317
|
? args.blockExec.split(",").filter(Boolean)
|
|
266
318
|
: [],
|
|
267
319
|
traceCrypto: args.traceCrypto ?? true,
|
|
320
|
+
traceProxyEgress: args.proxyEgress ?? false,
|
|
321
|
+
traceAllowLoopback: args.allowLoopback ?? false,
|
|
322
|
+
traceDryRun: args.sandboxDryRun ?? false,
|
|
323
|
+
tracePolicy: args.policy,
|
|
324
|
+
tracePolicyFile: args.policyFile ? resolve(args.policyFile) : undefined,
|
|
325
|
+
traceBlockInterpreters: args.blockInterpreters ?? false,
|
|
326
|
+
traceDenyPersistenceWrites: args.denyPersistenceWrites ?? false,
|
|
327
|
+
tracePrivateTmp: args.privateTmp ?? false,
|
|
328
|
+
traceProtectHome: args.protectHome,
|
|
268
329
|
cbom: undefined,
|
|
269
330
|
projectType: ["dynamic"],
|
|
270
331
|
output: resolve(args.output),
|
|
@@ -277,6 +338,9 @@ const options = {
|
|
|
277
338
|
console.error("Dynamic SBOM generation failed: no output was produced.");
|
|
278
339
|
process.exit(1);
|
|
279
340
|
}
|
|
341
|
+
// tracebom skips postProcess, so the downgrade to an older --spec-version
|
|
342
|
+
// (for example dropping the 1.7 algorithmFamily of traced crypto) runs here.
|
|
343
|
+
applySpecVersionCompatibility(bomJson, options);
|
|
280
344
|
|
|
281
345
|
const jsonPayload = JSON.stringify(bomJson, null, DEBUG_MODE ? 2 : null);
|
|
282
346
|
|
package/bin/validate.js
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
safeMkdirSync,
|
|
27
27
|
safeWriteSync,
|
|
28
28
|
} from "../lib/ecosystems/utils.js";
|
|
29
|
+
import { displayValue } from "../lib/helpers/bomSigner.js";
|
|
29
30
|
import {
|
|
30
31
|
getNonCycloneDxErrorMessage,
|
|
31
32
|
isCycloneDxBom,
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
isProtoBomPath,
|
|
36
37
|
} from "../lib/inventory/protobomLoader.js";
|
|
37
38
|
import { getBomWithOras } from "../lib/managers/oci.js";
|
|
39
|
+
import { parseStrictJson } from "../lib/parsers/strictJson.js";
|
|
38
40
|
import { shouldFail, validateBomAdvanced } from "../lib/validator/index.js";
|
|
39
41
|
import { render as renderReport } from "../lib/validator/reporters/index.js";
|
|
40
42
|
|
|
@@ -114,11 +116,20 @@ const args = _yargs
|
|
|
114
116
|
description:
|
|
115
117
|
"Path to a PEM public key. When set, cdx-validate also verifies the BOM signature.",
|
|
116
118
|
})
|
|
119
|
+
.option("secret-key", {
|
|
120
|
+
description:
|
|
121
|
+
"Path to the shared secret for HMAC (HS256, HS384, HS512) signatures. HMAC signatures are only accepted with this option.",
|
|
122
|
+
})
|
|
123
|
+
.option("nested-signatures", {
|
|
124
|
+
type: "boolean",
|
|
125
|
+
default: true,
|
|
126
|
+
description:
|
|
127
|
+
"Also verify component, service, and annotation signatures, as cdx-verify does. Pass --no-nested-signatures to verify only the root signature.",
|
|
128
|
+
})
|
|
117
129
|
.option("require-signature", {
|
|
118
130
|
type: "boolean",
|
|
119
|
-
default: false,
|
|
120
131
|
description:
|
|
121
|
-
"Exit non-zero (4) when --public-key is
|
|
132
|
+
"Exit non-zero (4) when signature verification fails. On by default when --public-key or --secret-key is given; pass --no-require-signature to only report the result.",
|
|
122
133
|
})
|
|
123
134
|
.option("strict", {
|
|
124
135
|
type: "boolean",
|
|
@@ -133,7 +144,11 @@ const args = _yargs
|
|
|
133
144
|
.help()
|
|
134
145
|
.wrap(Math.min(120, yargs().terminalWidth())).argv;
|
|
135
146
|
|
|
136
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Loads the BOM. When signatures are verified, the text must also pass
|
|
149
|
+
* parseStrictJson(), and `strictError` explains why it did not.
|
|
150
|
+
*/
|
|
151
|
+
async function loadBom(input, platform, strict, requireSignature) {
|
|
137
152
|
if (safeExistsSync(input)) {
|
|
138
153
|
try {
|
|
139
154
|
if (isProtoBomPath(input)) {
|
|
@@ -141,9 +156,18 @@ async function loadBom(input, platform) {
|
|
|
141
156
|
"cdx-validate",
|
|
142
157
|
"protobuf BOM input",
|
|
143
158
|
);
|
|
144
|
-
return readBinary(input, true);
|
|
159
|
+
return { bomJson: readBinary(input, true) };
|
|
160
|
+
}
|
|
161
|
+
const bytes = fs.readFileSync(input);
|
|
162
|
+
const bomJson = JSON.parse(bytes.toString("utf8"));
|
|
163
|
+
if (strict) {
|
|
164
|
+
try {
|
|
165
|
+
parseStrictJson(bytes);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
return { bomJson, strictError: err.message };
|
|
168
|
+
}
|
|
145
169
|
}
|
|
146
|
-
return
|
|
170
|
+
return { bomJson };
|
|
147
171
|
} catch (err) {
|
|
148
172
|
console.error(`Failed to parse ${input}: ${err.message}`);
|
|
149
173
|
process.exit(1);
|
|
@@ -154,21 +178,49 @@ async function loadBom(input, platform) {
|
|
|
154
178
|
input.includes("docker") ||
|
|
155
179
|
input.includes("ghcr")
|
|
156
180
|
) {
|
|
157
|
-
|
|
158
|
-
|
|
181
|
+
let strictError;
|
|
182
|
+
try {
|
|
183
|
+
const bom = await getBomWithOras(
|
|
184
|
+
input,
|
|
185
|
+
platform,
|
|
186
|
+
strict ? { checkBytes: parseStrictJson } : {},
|
|
187
|
+
);
|
|
188
|
+
if (bom) return { bomJson: bom };
|
|
189
|
+
} catch (err) {
|
|
190
|
+
if (requireSignature) {
|
|
191
|
+
console.error(
|
|
192
|
+
`cdx-validate: signature verification failed — the attached BOM cannot be verified: ${err.message}`,
|
|
193
|
+
);
|
|
194
|
+
process.exit(4);
|
|
195
|
+
}
|
|
196
|
+
strictError = err.message;
|
|
197
|
+
}
|
|
198
|
+
if (strictError) {
|
|
199
|
+
const bom = await getBomWithOras(input, platform);
|
|
200
|
+
if (bom) return { bomJson: bom, strictError };
|
|
201
|
+
}
|
|
159
202
|
}
|
|
160
203
|
console.error(`Input '${input}' is not a readable SBOM.`);
|
|
161
204
|
process.exit(1);
|
|
162
205
|
return undefined;
|
|
163
206
|
}
|
|
164
207
|
|
|
165
|
-
function
|
|
166
|
-
if (
|
|
208
|
+
function loadKeyFile(path, label, encoding) {
|
|
209
|
+
if (path === undefined) return null;
|
|
210
|
+
if (typeof path !== "string" || !path.trim()) {
|
|
211
|
+
console.error(`cdx-validate: ${label} needs exactly one file path.`);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
167
214
|
if (!safeExistsSync(path)) {
|
|
168
|
-
console.error(
|
|
215
|
+
console.error(`${label} '${path}' not found.`);
|
|
169
216
|
process.exit(1);
|
|
170
217
|
}
|
|
171
|
-
|
|
218
|
+
const content = fs.readFileSync(path, encoding);
|
|
219
|
+
if (!content.toString().trim()) {
|
|
220
|
+
console.error(`${label} '${path}' is empty.`);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
return content;
|
|
172
224
|
}
|
|
173
225
|
|
|
174
226
|
function splitCsv(value) {
|
|
@@ -198,19 +250,32 @@ function isLocalProtoBomInput(input) {
|
|
|
198
250
|
return isProtoBomPath(input);
|
|
199
251
|
}
|
|
200
252
|
|
|
201
|
-
const
|
|
202
|
-
const
|
|
253
|
+
const keyGiven = args.publicKey !== undefined || args.secretKey !== undefined;
|
|
254
|
+
const requireSignature = args.requireSignature ?? keyGiven;
|
|
255
|
+
const { bomJson, strictError } = await loadBom(
|
|
256
|
+
args.input,
|
|
257
|
+
args.platform,
|
|
258
|
+
keyGiven,
|
|
259
|
+
requireSignature,
|
|
260
|
+
);
|
|
261
|
+
if (args.publicKey !== undefined && args.secretKey !== undefined) {
|
|
262
|
+
console.error("cdx-validate: use either --public-key or --secret-key.");
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
const publicKeyStr = loadKeyFile(args.publicKey, "Public key", "utf8");
|
|
266
|
+
const secretKey = loadKeyFile(args.secretKey, "Shared secret");
|
|
267
|
+
const verificationRequested = keyGiven;
|
|
203
268
|
const inputIsLocalProtoBom = isLocalProtoBomInput(args.input);
|
|
204
269
|
if (!isCycloneDxBom(bomJson)) {
|
|
205
270
|
console.error(getNonCycloneDxErrorMessage(bomJson, "cdx-validate"));
|
|
206
271
|
process.exit(1);
|
|
207
272
|
}
|
|
208
273
|
|
|
209
|
-
if (inputIsLocalProtoBom &&
|
|
274
|
+
if (inputIsLocalProtoBom && verificationRequested) {
|
|
210
275
|
console.error(
|
|
211
276
|
"cdx-validate: protobuf BOM input does not currently preserve JSF signature blocks. Verify signatures against the source JSON BOM instead.",
|
|
212
277
|
);
|
|
213
|
-
process.exit(
|
|
278
|
+
process.exit(requireSignature ? 4 : 1);
|
|
214
279
|
}
|
|
215
280
|
|
|
216
281
|
const report = await validateBomAdvanced(bomJson, {
|
|
@@ -221,8 +286,17 @@ const report = await validateBomAdvanced(bomJson, {
|
|
|
221
286
|
minSeverity: args.minSeverity,
|
|
222
287
|
includeManual: args.includeManual,
|
|
223
288
|
includePass: args.includePass,
|
|
224
|
-
publicKey: publicKeyStr
|
|
289
|
+
publicKey: publicKeyStr ?? undefined,
|
|
290
|
+
secretKey: secretKey ?? undefined,
|
|
291
|
+
nestedSignatures: args.nestedSignatures,
|
|
225
292
|
});
|
|
293
|
+
if (strictError && verificationRequested) {
|
|
294
|
+
report.signatureVerified = false;
|
|
295
|
+
report.signatureEntries = null;
|
|
296
|
+
report.signatureDetails = {
|
|
297
|
+
error: `the BOM cannot be verified: ${strictError}`,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
226
300
|
|
|
227
301
|
let output;
|
|
228
302
|
try {
|
|
@@ -242,17 +316,31 @@ try {
|
|
|
242
316
|
|
|
243
317
|
writeOrPrint(output, args.reportFile);
|
|
244
318
|
|
|
319
|
+
for (const e of report.validationErrors || []) {
|
|
320
|
+
console.error(`cdx-validate: ${e.stage} validation threw: ${e.message}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
245
323
|
const { shouldFail: fail, reason } = shouldFail(report, {
|
|
246
324
|
failSeverity: args.failSeverity,
|
|
247
325
|
strict: args.strict,
|
|
248
|
-
requireSignature: Boolean(
|
|
326
|
+
requireSignature: Boolean(requireSignature && verificationRequested),
|
|
249
327
|
});
|
|
250
328
|
|
|
251
|
-
if (report.signatureVerified === false &&
|
|
329
|
+
if (report.signatureVerified === false && verificationRequested) {
|
|
252
330
|
console.error(
|
|
253
331
|
`cdx-validate: signature verification failed — ${report.signatureDetails?.error || "no matching key"}.`,
|
|
254
332
|
);
|
|
255
|
-
|
|
333
|
+
if (requireSignature) {
|
|
334
|
+
process.exit(4);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const unverifiedEntries = (report.signatureEntries || []).filter(
|
|
338
|
+
(entry) => !entry.verified,
|
|
339
|
+
);
|
|
340
|
+
if (report.signatureVerified && unverifiedEntries.length) {
|
|
341
|
+
console.error(
|
|
342
|
+
`cdx-validate: warning — ${unverifiedEntries.length} of ${report.signatureEntries.length} signature entries did not verify with the given key: ${unverifiedEntries.map((entry) => `entry ${entry.index}${entry.keyId === undefined ? "" : ` (keyId '${displayValue(entry.keyId)}')`}`).join(", ")}.`,
|
|
343
|
+
);
|
|
256
344
|
}
|
|
257
345
|
|
|
258
346
|
if (args.strict && report.schemaValid === false) {
|