@cdxgen/cdxgen 13.3.0 → 13.5.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 +12 -9
- package/bin/cdxgen-secure.js +15 -0
- package/bin/cdxgen.js +115 -31
- package/bin/evinse.js +23 -1
- package/bin/hbom.js +15 -7
- package/bin/repl.js +115 -0
- package/bin/sign.js +45 -15
- package/bin/tracebom.js +5 -1
- package/bin/validate.js +84 -18
- package/bin/verify.js +155 -86
- package/data/cbomosdb-queries.json +13 -1
- package/data/component-tags.json +8 -0
- package/data/helpers/init.gradle +46 -21
- package/data/queries-darwin.json +25 -1
- package/data/queries-win.json +19 -1
- package/data/queries.json +41 -0
- package/data/remediations.json +344 -1
- package/data/rules/cbom-security.yaml +235 -4
- package/data/rules/dependency-sources.yaml +67 -0
- package/data/rules/go-binaries.yaml +46 -0
- package/data/rules/golem-go.yaml +80 -0
- package/data/rules/hbom-compliance.yaml +40 -22
- package/data/rules/hbom-performance.yaml +7 -1
- package/data/rules/host-topology.yaml +1 -1
- package/data/rules/obom-runtime.yaml +31 -2
- package/data/rules/php-runtime.yaml +143 -0
- package/data/rules/scala-evidence.yaml +115 -0
- package/lib/audit/index.js +52 -6
- package/lib/cli/bomAssembly.js +28 -3
- package/lib/cli/cliOptions.js +110 -3
- package/lib/cli/index.js +186 -58
- package/lib/cli/jsBom.js +50 -29
- package/lib/cli/jvmBom.js +779 -146
- package/lib/cli/managedBom.js +1021 -261
- package/lib/cli/nativeBom.js +364 -58
- package/lib/core/activity.js +12 -0
- package/lib/core/env.js +47 -17
- package/lib/core/fs.js +31 -21
- package/lib/core/httpClient.js +233 -69
- package/lib/core/pluginRun.js +184 -0
- package/lib/core/processTree.js +100 -0
- package/lib/core/projectConfig.js +1 -0
- package/lib/core/projectTooling.js +97 -0
- package/lib/core/propertySanitizer.js +72 -0
- 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 +5 -7
- package/lib/ecosystems/cargoCache.js +231 -8
- package/lib/ecosystems/cmakeResolver.js +10 -21
- package/lib/ecosystems/cppBuildContext.js +631 -0
- package/lib/ecosystems/cppEvidence.js +337 -22
- package/lib/ecosystems/cppIncludeResolver.js +294 -0
- package/lib/ecosystems/denoutils.js +93 -66
- package/lib/ecosystems/ecosystems.js +1967 -788
- package/lib/ecosystems/golem.js +578 -18
- package/lib/ecosystems/gradleutils.js +340 -46
- package/lib/ecosystems/jsEvidence.js +85 -24
- package/lib/ecosystems/kosi.js +21 -4
- package/lib/ecosystems/npmutils.js +64 -0
- package/lib/ecosystems/parsers-dotnet.js +668 -153
- package/lib/ecosystems/parsers-elm.js +46 -28
- package/lib/ecosystems/parsers-gleam.js +3 -4
- package/lib/ecosystems/parsers-go.js +1283 -297
- package/lib/ecosystems/parsers-js.js +92 -26
- package/lib/ecosystems/parsers-julia.js +2 -3
- package/lib/ecosystems/parsers-jvm.js +463 -29
- package/lib/ecosystems/parsers-misc.js +405 -93
- package/lib/ecosystems/parsers-python.js +152 -20
- package/lib/ecosystems/parsers-rust.js +4 -4
- 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 +73 -20
- package/lib/ecosystems/rusi.js +32 -9
- package/lib/ecosystems/sbtutils.js +243 -187
- package/lib/ecosystems/scalacliutils.js +343 -0
- package/lib/ecosystems/scalasem.js +2620 -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 +695 -78
- package/lib/evinser/swiftsem.js +68 -5
- package/lib/helpers/agenticToolutils.js +2 -2
- package/lib/helpers/bomSigner.js +576 -128
- package/lib/inventory/agentSkillSpec.js +17 -0
- package/lib/inventory/aiCollector.js +4 -1
- package/lib/inventory/analyzer.js +110 -127
- package/lib/inventory/apkInstalledDb.js +243 -0
- package/lib/inventory/atomUtils.js +204 -48
- package/lib/inventory/auditCategories.js +10 -3
- package/lib/inventory/buildToolRateLimit.js +144 -0
- package/lib/inventory/cacheCommands.js +2 -1
- package/lib/inventory/cacheDir.js +52 -9
- package/lib/inventory/certificates.js +2371 -0
- package/lib/inventory/cmakeBuildDirs.js +254 -0
- package/lib/inventory/communityAiConfigParser.js +4 -9
- package/lib/inventory/deps.js +507 -128
- package/lib/inventory/display.js +200 -3
- package/lib/inventory/dosai.js +747 -101
- package/lib/inventory/dosaiParsers.js +310 -15
- package/lib/inventory/dynamic.js +96 -8
- package/lib/inventory/envcontext.js +168 -5
- package/lib/inventory/evidenceUtils.js +590 -132
- package/lib/inventory/fetchBatch.js +574 -38
- package/lib/inventory/fetchDiskCache.js +597 -0
- package/lib/inventory/forgeEnricher.js +14 -5
- package/lib/inventory/formulationParsers.js +3 -2
- package/lib/inventory/goBuildInfo.js +140 -0
- package/lib/inventory/hbom.js +67 -7
- package/lib/inventory/hbomAnalysis.js +16 -0
- package/lib/inventory/hostTopology.js +21 -0
- package/lib/inventory/jvmLocalRepos.js +917 -0
- package/lib/inventory/mcpConfigParser.js +2 -3
- package/lib/inventory/osPackagePurl.js +244 -0
- package/lib/inventory/osPackageResolver.js +89 -87
- package/lib/inventory/osinfo.js +79 -31
- package/lib/inventory/osqueryTransform.js +114 -5
- package/lib/inventory/parentSelection.js +146 -0
- package/lib/inventory/phpBuildInfo.js +487 -0
- package/lib/inventory/phpHostProbe.js +641 -0
- package/lib/inventory/rootfsCerts.js +426 -0
- package/lib/inventory/rustFormulationParser.js +3 -4
- package/lib/inventory/scalaCoords.js +153 -0
- package/lib/inventory/source.js +215 -11
- package/lib/inventory/spdx.js +245 -0
- package/lib/inventory/traceRunner.js +93 -8
- package/lib/inventory/tracedCrypto.js +423 -0
- package/lib/managers/binary.js +529 -183
- package/lib/managers/docker.js +199 -44
- package/lib/managers/oci.js +24 -2
- package/lib/parsers/cmakeCache.js +69 -0
- package/lib/parsers/cmakeLists.js +16 -2
- package/lib/parsers/cmakePresets.js +400 -0
- package/lib/parsers/compileCommands.js +557 -0
- package/lib/parsers/email.js +277 -0
- package/lib/parsers/largeJson.js +34 -2
- 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 +209 -1
- package/lib/stages/postgen/annotator.js +17 -1
- package/lib/stages/postgen/introspection/markers.js +1 -1
- package/lib/stages/postgen/introspection/reflect.js +4 -1
- package/lib/stages/postgen/postgen.js +20 -6
- package/lib/stages/postgen/ruleEngine.js +58 -1
- package/lib/stages/postgen/specVersionCompat.js +62 -0
- package/lib/stages/pregen/pregen.js +117 -5
- package/lib/validator/bomValidator.js +26 -5
- package/lib/validator/generated/runtime.mjs +13 -0
- package/lib/validator/generated/validate-1.6.mjs +26 -7
- package/lib/validator/generated/validate-1.7.mjs +31 -12
- package/lib/validator/generated/validate-2.0.mjs +30 -11
- package/lib/validator/index.js +25 -10
- package/lib/validator/reporters/json.js +1 -0
- package/package.json +23 -23
- package/types/bin/cdxgen-secure.d.ts +3 -0
- package/types/bin/cdxgen-secure.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +9 -2
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +43 -0
- 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 +44 -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 +27 -2
- package/types/lib/core/env.d.ts.map +1 -1
- 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/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.map +1 -1
- package/types/lib/core/projectTooling.d.ts +41 -0
- package/types/lib/core/projectTooling.d.ts.map +1 -0
- package/types/lib/core/propertySanitizer.d.ts +26 -0
- package/types/lib/core/propertySanitizer.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.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/cmakeResolver.d.ts +5 -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 +19 -3
- 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 +81 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +59 -7
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmutils.d.ts +15 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +155 -5
- 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 +119 -5
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +6 -1
- 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 +74 -3
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +21 -59
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +57 -6
- package/types/lib/ecosystems/parsers-python.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 +26 -1
- 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 +21 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- 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 +100 -8
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +10 -0
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +80 -4
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
- package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
- package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
- package/types/lib/inventory/atomUtils.d.ts +96 -10
- 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/cacheDir.d.ts +12 -4
- package/types/lib/inventory/cacheDir.d.ts.map +1 -1
- package/types/lib/inventory/certificates.d.ts +205 -0
- package/types/lib/inventory/certificates.d.ts.map +1 -0
- package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
- package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/deps.d.ts +61 -2
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts +18 -0
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +202 -14
- 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 +16 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts +40 -2
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +48 -14
- 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/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/goBuildInfo.d.ts +10 -0
- package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
- package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
- package/types/lib/inventory/hostTopology.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/osPackagePurl.d.ts +99 -0
- package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +6 -2
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/osinfo.d.ts +31 -6
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/osqueryTransform.d.ts +51 -0
- package/types/lib/inventory/osqueryTransform.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/phpBuildInfo.d.ts +142 -0
- package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/phpHostProbe.d.ts +35 -0
- package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/rootfsCerts.d.ts +27 -0
- package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
- package/types/lib/inventory/rustFormulationParser.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 +28 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +24 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +16 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +64 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts +10 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +12 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +3 -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 +2 -1
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -1
- 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/email.d.ts +19 -0
- package/types/lib/parsers/email.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +13 -0
- package/types/lib/parsers/largeJson.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 +8 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +14 -1
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +8 -0
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
|
@@ -77,6 +77,7 @@ export function parseCommand(cmdStr) {
|
|
|
77
77
|
export function resolveSaferExecBinary() {
|
|
78
78
|
const currentPlatform = platform();
|
|
79
79
|
const currentArch = arch();
|
|
80
|
+
const isWindows = currentPlatform === "win32";
|
|
80
81
|
let pkgName = "";
|
|
81
82
|
|
|
82
83
|
if (currentPlatform === "darwin") {
|
|
@@ -91,6 +92,14 @@ export function resolveSaferExecBinary() {
|
|
|
91
92
|
} else if (currentArch === "arm64") {
|
|
92
93
|
pkgName = "@cdxgen/safer-exec-linux-arm64";
|
|
93
94
|
}
|
|
95
|
+
} else if (isWindows) {
|
|
96
|
+
// The Windows packages ship the MXC executor (wxc-exec.exe) next to the
|
|
97
|
+
// engine, which is where the engine looks for it.
|
|
98
|
+
if (currentArch === "x64") {
|
|
99
|
+
pkgName = "@cdxgen/safer-exec-windows-amd64";
|
|
100
|
+
} else if (currentArch === "arm64") {
|
|
101
|
+
pkgName = "@cdxgen/safer-exec-windows-arm64";
|
|
102
|
+
}
|
|
94
103
|
}
|
|
95
104
|
|
|
96
105
|
if (!pkgName) {
|
|
@@ -117,9 +126,14 @@ export function resolveSaferExecBinary() {
|
|
|
117
126
|
|
|
118
127
|
for (const modulesDir of searchDirs) {
|
|
119
128
|
// Direct structure under node_modules. The Go runtime inside the
|
|
120
|
-
// platform package is bin/safer-exec-rt; "safer-exec" is the JS
|
|
129
|
+
// platform package is bin/safer-exec-rt(.exe); "safer-exec" is the JS
|
|
121
130
|
// dispatcher's npm bin name, not a file on disk.
|
|
122
|
-
const directPath = join(
|
|
131
|
+
const directPath = join(
|
|
132
|
+
modulesDir,
|
|
133
|
+
pkgName,
|
|
134
|
+
"bin",
|
|
135
|
+
isWindows ? "safer-exec-rt.exe" : "safer-exec-rt",
|
|
136
|
+
);
|
|
123
137
|
let realDirectPath;
|
|
124
138
|
try {
|
|
125
139
|
realDirectPath = realpathSync(directPath);
|
|
@@ -127,10 +141,12 @@ export function resolveSaferExecBinary() {
|
|
|
127
141
|
realDirectPath = directPath;
|
|
128
142
|
}
|
|
129
143
|
if (existsSync(realDirectPath)) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
144
|
+
if (!isWindows) {
|
|
145
|
+
try {
|
|
146
|
+
chmodSync(realDirectPath, 0o755);
|
|
147
|
+
} catch (_err) {
|
|
148
|
+
// ignore
|
|
149
|
+
}
|
|
134
150
|
}
|
|
135
151
|
return realDirectPath;
|
|
136
152
|
}
|
|
@@ -179,7 +195,7 @@ export function resolveSaferExecBinary() {
|
|
|
179
195
|
* @param {boolean} [options.denyPersistenceWrites] - Deny writes to auto-execution and persistence locations
|
|
180
196
|
* @param {boolean} [options.privateTmp] - Private tmpfs /tmp and /var/tmp (Linux)
|
|
181
197
|
* @param {string} [options.protectHome] - Home isolation: read-only, tmpfs, or off (Linux)
|
|
182
|
-
* @returns {Promise<{libPaths: string[], httpAccessEntries: Object[], egressEntries?: Object[], dryRunSummary?: Object}>} Collected libraries, HTTP URLs, proxy egress decisions, and the dry-run summary
|
|
198
|
+
* @returns {Promise<{libPaths: string[], httpAccessEntries: Object[], cryptoEntries?: Object[], cryptoComponents?: Object[], cryptoResult?: Object, egressEntries?: Object[], dryRunSummary?: Object}>} Collected libraries, HTTP URLs, crypto observations, proxy egress decisions, and the dry-run summary
|
|
183
199
|
* @throws {Error} When the @cdxgen/safer-exec tracing runtime is not installed
|
|
184
200
|
* (for example after pruning it from a standalone payload), since the traced
|
|
185
201
|
* command could never run and the BOM would silently be empty
|
|
@@ -252,6 +268,14 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
|
252
268
|
if (options.dryRun) {
|
|
253
269
|
exec.enableDryRun();
|
|
254
270
|
}
|
|
271
|
+
if (platform() === "win32" && options.blockInterpreters) {
|
|
272
|
+
// safer-exec reports --private-tmp and --protect-home on Windows when a
|
|
273
|
+
// grant defeats them, but --block-interpreters is macOS-only and stays
|
|
274
|
+
// silent (cdxgen/safer-exec#4).
|
|
275
|
+
console.warn(
|
|
276
|
+
"[cdxgen trace] --block-interpreters has no effect on Windows.",
|
|
277
|
+
);
|
|
278
|
+
}
|
|
255
279
|
if (options.blockInterpreters) {
|
|
256
280
|
exec.blockInterpreters();
|
|
257
281
|
}
|
|
@@ -376,8 +400,41 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
|
376
400
|
|
|
377
401
|
const result = await exec.run(cmd, args);
|
|
378
402
|
const stderr = result?.stderr || "";
|
|
403
|
+
const { warnings, error: engineError } = engineMessages(stderr);
|
|
404
|
+
// Windows records DLL loads through an ETW session, which only an
|
|
405
|
+
// elevated process (or a Performance Log Users member) may start.
|
|
406
|
+
const etwDenied =
|
|
407
|
+
platform() === "win32" &&
|
|
408
|
+
warnings.some(
|
|
409
|
+
(line) =>
|
|
410
|
+
line.includes("DLL tracing:") &&
|
|
411
|
+
line.includes("administrator rights"),
|
|
412
|
+
);
|
|
413
|
+
// run() captures the engine's stderr, so a control the sandbox could not
|
|
414
|
+
// apply would otherwise read like one it did.
|
|
415
|
+
for (const line of warnings) {
|
|
416
|
+
if (etwDenied && line.includes("DLL tracing:")) {
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
console.warn(line);
|
|
420
|
+
}
|
|
421
|
+
if (etwDenied) {
|
|
422
|
+
console.warn(
|
|
423
|
+
"[cdxgen trace] library tracing on Windows needs an elevated prompt (or membership in the Performance Log Users group); the BOM has no library components. Egress services from --proxy-egress are still recorded.",
|
|
424
|
+
);
|
|
425
|
+
}
|
|
379
426
|
if (result && result.exitCode !== 0) {
|
|
380
|
-
if (
|
|
427
|
+
if (engineError) {
|
|
428
|
+
console.error(engineError);
|
|
429
|
+
if (options.strict) {
|
|
430
|
+
// Under --strict the sandbox refused the run; an empty BOM would
|
|
431
|
+
// read as a command that loaded nothing.
|
|
432
|
+
throw Object.assign(
|
|
433
|
+
new Error(`Tracing refused under --strict: ${engineError}`),
|
|
434
|
+
{ strictRefusal: true },
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
} else if (stderr.includes("[safer-exec] Error:")) {
|
|
381
438
|
console.error("Tracing launcher execution failed:", stderr.trim());
|
|
382
439
|
} else {
|
|
383
440
|
// A failing command usually means a partial trace (for example a
|
|
@@ -474,6 +531,9 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
|
474
531
|
httpAccessEntries: [...collectedUrls, ...urls],
|
|
475
532
|
cryptoEntries: [...collectedCrypto, ...cryptoLogs],
|
|
476
533
|
cryptoComponents,
|
|
534
|
+
// The CRYPTO result from the report channel: the cipher entries carry
|
|
535
|
+
// the certificate chains servers presented (safer-exec ≥ 1.2.0).
|
|
536
|
+
cryptoResult: result?.crypto,
|
|
477
537
|
egressEntries: parseEgressEntries(result.auditLog),
|
|
478
538
|
dryRunSummary,
|
|
479
539
|
};
|
|
@@ -484,15 +544,40 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
|
484
544
|
httpAccessEntries: collectedUrls,
|
|
485
545
|
cryptoEntries: collectedCrypto,
|
|
486
546
|
cryptoComponents,
|
|
547
|
+
cryptoResult: result?.crypto,
|
|
487
548
|
egressEntries: [],
|
|
488
549
|
dryRunSummary,
|
|
489
550
|
};
|
|
490
551
|
} catch (err) {
|
|
552
|
+
if (err?.strictRefusal) {
|
|
553
|
+
throw err;
|
|
554
|
+
}
|
|
491
555
|
console.error("Tracing command execution failed:", err);
|
|
492
556
|
}
|
|
493
557
|
return emptyResult;
|
|
494
558
|
}
|
|
495
559
|
|
|
560
|
+
/**
|
|
561
|
+
* Picks the safer-exec engine's own lines out of the stderr it shares with the
|
|
562
|
+
* traced command. Warnings start with "safer-exec: warning:"; when the engine
|
|
563
|
+
* stops a run, its error is the last other "safer-exec: " line it prints.
|
|
564
|
+
*
|
|
565
|
+
* @param {string} stderr Captured stderr of a safer-exec run
|
|
566
|
+
* @returns {{warnings: string[], error: string|undefined}} Unique engine warnings, and the engine's error if any
|
|
567
|
+
*/
|
|
568
|
+
export function engineMessages(stderr) {
|
|
569
|
+
const lines = String(stderr || "")
|
|
570
|
+
.split(/\r?\n/)
|
|
571
|
+
.filter((line) => line.startsWith("safer-exec: "));
|
|
572
|
+
const warnings = [
|
|
573
|
+
...new Set(lines.filter((line) => line.startsWith("safer-exec: warning:"))),
|
|
574
|
+
];
|
|
575
|
+
const others = lines.filter(
|
|
576
|
+
(line) => !line.startsWith("safer-exec: warning:"),
|
|
577
|
+
);
|
|
578
|
+
return { warnings, error: others.at(-1) };
|
|
579
|
+
}
|
|
580
|
+
|
|
496
581
|
/**
|
|
497
582
|
* Returns the sorted, de-duplicated parameter names of a query string.
|
|
498
583
|
*
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { thoughtLog } from "../core/logger.js";
|
|
5
|
+
import { dirNameStr } from "../core/paths.js";
|
|
6
|
+
import {
|
|
7
|
+
CertificateCollector,
|
|
8
|
+
certRecordFromObservedChainEntry,
|
|
9
|
+
toWellFormedText,
|
|
10
|
+
} from "./certificates.js";
|
|
11
|
+
import { applyAlgorithmProperties } from "./cryptoAlgorithmFamily.js";
|
|
12
|
+
|
|
13
|
+
const cryptoOids = JSON.parse(
|
|
14
|
+
readFileSync(join(dirNameStr, "data", "crypto-oid.json"), "utf-8"),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
// TLS key-exchange groups by their IANA registry name. A hybrid group pairs a
|
|
18
|
+
// classical curve with ML-KEM: each half has an OID, the pair has none, so the
|
|
19
|
+
// group is recorded as both algorithms.
|
|
20
|
+
const GROUP_ALGORITHMS = {
|
|
21
|
+
X25519MLKEM768: ["x25519", "ml-kem-768"],
|
|
22
|
+
SecP256r1MLKEM768: ["secp256r1", "ml-kem-768"],
|
|
23
|
+
SecP384r1MLKEM1024: ["secp384r1", "ml-kem-1024"],
|
|
24
|
+
X25519Kyber768Draft00: ["x25519"],
|
|
25
|
+
MLKEM512: ["ml-kem-512"],
|
|
26
|
+
MLKEM768: ["ml-kem-768"],
|
|
27
|
+
MLKEM1024: ["ml-kem-1024"],
|
|
28
|
+
x448: ["curveX448"],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const MLKEM_SECURITY_LEVELS = {
|
|
32
|
+
"ml-kem-512": 1,
|
|
33
|
+
"ml-kem-768": 3,
|
|
34
|
+
"ml-kem-1024": 5,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// The curve each classical key-exchange algorithm is defined over.
|
|
38
|
+
const KEY_AGREEMENT_CURVES = {
|
|
39
|
+
x25519: "x25519",
|
|
40
|
+
curveX448: "x448",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Closed enums of cryptoProperties.algorithmProperties in bom-1.7.schema.json.
|
|
44
|
+
const MODES = new Set(["cbc", "ecb", "ccm", "gcm", "cfb", "ofb", "ctr"]);
|
|
45
|
+
const CRYPTO_FUNCTIONS = new Set([
|
|
46
|
+
"generate",
|
|
47
|
+
"keygen",
|
|
48
|
+
"encrypt",
|
|
49
|
+
"decrypt",
|
|
50
|
+
"digest",
|
|
51
|
+
"tag",
|
|
52
|
+
"keyderive",
|
|
53
|
+
"sign",
|
|
54
|
+
"verify",
|
|
55
|
+
"encapsulate",
|
|
56
|
+
"decapsulate",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Names the bulk cipher of a TLS cipher suite as a crypto-oid.json key, from
|
|
61
|
+
* either its IANA name (TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) or its OpenSSL
|
|
62
|
+
* name (ECDHE-RSA-AES128-GCM-SHA256).
|
|
63
|
+
*
|
|
64
|
+
* @param {string} suiteName Cipher suite name
|
|
65
|
+
* @returns {{name: string, bits: number, mode: string|undefined}|undefined} The cipher, when it has an OID
|
|
66
|
+
*/
|
|
67
|
+
export function suiteCipher(suiteName) {
|
|
68
|
+
const tokens = String(suiteName || "")
|
|
69
|
+
.toUpperCase()
|
|
70
|
+
.split(/[-_]/);
|
|
71
|
+
let cipher;
|
|
72
|
+
if (tokens.includes("CHACHA20")) {
|
|
73
|
+
cipher = { name: "chaCha20Poly1305", bits: 256, mode: undefined };
|
|
74
|
+
} else if (tokens.includes("3DES") || tokens.includes("CBC3")) {
|
|
75
|
+
cipher = { name: "des-EDE3-CBC", bits: 112, mode: "cbc" };
|
|
76
|
+
} else {
|
|
77
|
+
const aesIdx = tokens.findIndex((t) => t.startsWith("AES"));
|
|
78
|
+
if (aesIdx < 0) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
const bits =
|
|
82
|
+
Number.parseInt(tokens[aesIdx].substring(3), 10) ||
|
|
83
|
+
Number.parseInt(tokens[aesIdx + 1], 10);
|
|
84
|
+
const mode = ["GCM", "CCM"].find((m) => tokens.includes(m)) || "CBC";
|
|
85
|
+
cipher = { name: `aes${bits}-${mode}`, bits, mode: mode.toLowerCase() };
|
|
86
|
+
}
|
|
87
|
+
return cryptoOids[cipher.name]?.oid ? cipher : undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function oidKey(name) {
|
|
91
|
+
if (!name) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
if (cryptoOids[name]?.oid) {
|
|
95
|
+
return name;
|
|
96
|
+
}
|
|
97
|
+
const lower = String(name).toLowerCase();
|
|
98
|
+
return cryptoOids[lower]?.oid ? lower : undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function cipherProperties(cipher) {
|
|
102
|
+
const props = {
|
|
103
|
+
primitive: cipher.mode === "cbc" ? "block-cipher" : "ae",
|
|
104
|
+
parameterSetIdentifier: String(cipher.bits),
|
|
105
|
+
cryptoFunctions: ["encrypt", "decrypt"],
|
|
106
|
+
classicalSecurityLevel: cipher.bits,
|
|
107
|
+
};
|
|
108
|
+
if (cipher.mode) {
|
|
109
|
+
props.mode = cipher.mode;
|
|
110
|
+
}
|
|
111
|
+
return props;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function keyExchangeProperties(name) {
|
|
115
|
+
const level = MLKEM_SECURITY_LEVELS[name];
|
|
116
|
+
if (level) {
|
|
117
|
+
return {
|
|
118
|
+
primitive: "kem",
|
|
119
|
+
cryptoFunctions: ["keygen", "encapsulate", "decapsulate"],
|
|
120
|
+
nistQuantumSecurityLevel: level,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return { primitive: "key-agree", cryptoFunctions: ["keygen"] };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Keeps the values of an algorithm the traced CBOM described that fit the
|
|
127
|
+
// schema; the primitive itself is checked by applyAlgorithmProperties.
|
|
128
|
+
function knownAlgorithmProperties(props) {
|
|
129
|
+
const known = {};
|
|
130
|
+
if (typeof props?.primitive === "string") {
|
|
131
|
+
known.primitive = props.primitive === "aead" ? "ae" : props.primitive;
|
|
132
|
+
}
|
|
133
|
+
const mode = String(props?.mode || "").toLowerCase();
|
|
134
|
+
if (MODES.has(mode)) {
|
|
135
|
+
known.mode = mode;
|
|
136
|
+
}
|
|
137
|
+
const functions = (props?.cryptoFunctions || []).filter((f) =>
|
|
138
|
+
CRYPTO_FUNCTIONS.has(f),
|
|
139
|
+
);
|
|
140
|
+
if (functions.length) {
|
|
141
|
+
known.cryptoFunctions = functions;
|
|
142
|
+
}
|
|
143
|
+
for (const level of ["classicalSecurityLevel", "nistQuantumSecurityLevel"]) {
|
|
144
|
+
if (Number.isInteger(props?.[level])) {
|
|
145
|
+
known[level] = props[level];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return known;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Turns the CBOM safer-exec writes for a traced command into components that
|
|
153
|
+
* cdxgen can merge and validate:
|
|
154
|
+
*
|
|
155
|
+
* - every asset gets a bom-ref, without which the assets merge into one
|
|
156
|
+
* - algorithms carry their OID, and those without one are left out
|
|
157
|
+
* - hybrid key-exchange groups become their classical and ML-KEM halves
|
|
158
|
+
* - cipher suites become their bulk cipher, which the protocol's suites refer
|
|
159
|
+
* to by bom-ref
|
|
160
|
+
* - algorithm properties keep only values the schema allows
|
|
161
|
+
* - crypto libraries are left out: they are the traced library components,
|
|
162
|
+
* marked with cdx:dynamic:cryptoLibrary
|
|
163
|
+
*
|
|
164
|
+
* @param {Object[]} cbomComponents Components of the safer-exec CBOM
|
|
165
|
+
* @returns {Object[]} Normalized cryptographic-asset components
|
|
166
|
+
*/
|
|
167
|
+
export function normalizeTracedCryptoComponents(cbomComponents) {
|
|
168
|
+
const byRef = new Map();
|
|
169
|
+
const addAlgorithm = (name, algorithmProperties, curve) => {
|
|
170
|
+
const oid = cryptoOids[name].oid;
|
|
171
|
+
const ref = `crypto/algorithm/${encodeURIComponent(name)}@${oid}`;
|
|
172
|
+
if (!byRef.has(ref)) {
|
|
173
|
+
const component = {
|
|
174
|
+
type: "cryptographic-asset",
|
|
175
|
+
name,
|
|
176
|
+
"bom-ref": ref,
|
|
177
|
+
description: cryptoOids[name].description,
|
|
178
|
+
cryptoProperties: {
|
|
179
|
+
assetType: "algorithm",
|
|
180
|
+
algorithmProperties,
|
|
181
|
+
oid,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
applyAlgorithmProperties(component, { name, curve });
|
|
185
|
+
byRef.set(ref, component);
|
|
186
|
+
}
|
|
187
|
+
return ref;
|
|
188
|
+
};
|
|
189
|
+
const protocols = [];
|
|
190
|
+
for (const comp of cbomComponents || []) {
|
|
191
|
+
const props = comp?.cryptoProperties;
|
|
192
|
+
if (comp?.type !== "cryptographic-asset" || !props) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (props.assetType === "protocol") {
|
|
196
|
+
protocols.push(comp);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (props.assetType !== "algorithm") {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const groupHalves = GROUP_ALGORITHMS[comp.name];
|
|
203
|
+
if (groupHalves) {
|
|
204
|
+
for (const half of groupHalves) {
|
|
205
|
+
addAlgorithm(
|
|
206
|
+
half,
|
|
207
|
+
keyExchangeProperties(half),
|
|
208
|
+
KEY_AGREEMENT_CURVES[half] ||
|
|
209
|
+
(MLKEM_SECURITY_LEVELS[half] ? "" : half),
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const name = oidKey(comp.name);
|
|
215
|
+
if (name) {
|
|
216
|
+
addAlgorithm(
|
|
217
|
+
name,
|
|
218
|
+
MLKEM_SECURITY_LEVELS[name] || KEY_AGREEMENT_CURVES[name]
|
|
219
|
+
? keyExchangeProperties(name)
|
|
220
|
+
: knownAlgorithmProperties(props.algorithmProperties),
|
|
221
|
+
KEY_AGREEMENT_CURVES[name] ||
|
|
222
|
+
(/^(secp|brainpool)/.test(name) ? name : ""),
|
|
223
|
+
);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const cipher = suiteCipher(comp.name);
|
|
227
|
+
if (cipher) {
|
|
228
|
+
addAlgorithm(cipher.name, cipherProperties(cipher));
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
thoughtLog(`Skipping traced algorithm '${comp.name}' without an OID.`);
|
|
232
|
+
}
|
|
233
|
+
for (const comp of protocols) {
|
|
234
|
+
const ref = `crypto/protocol/${encodeURIComponent(comp.name)}@${encodeURIComponent(comp.version || "unknown")}`;
|
|
235
|
+
const protocolProperties = {
|
|
236
|
+
...comp.cryptoProperties.protocolProperties,
|
|
237
|
+
};
|
|
238
|
+
// The traced suites list algorithm names; the schema wants bom-refs.
|
|
239
|
+
const cipherSuites = (protocolProperties.cipherSuites || []).map(
|
|
240
|
+
({ algorithms: _names, ...suite }) => {
|
|
241
|
+
const cipher = suiteCipher(suite.name);
|
|
242
|
+
return cipher
|
|
243
|
+
? {
|
|
244
|
+
...suite,
|
|
245
|
+
algorithms: [addAlgorithm(cipher.name, cipherProperties(cipher))],
|
|
246
|
+
}
|
|
247
|
+
: suite;
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
const existing = byRef.get(ref);
|
|
251
|
+
if (existing) {
|
|
252
|
+
const suites = existing.cryptoProperties.protocolProperties.cipherSuites;
|
|
253
|
+
for (const suite of cipherSuites) {
|
|
254
|
+
if (!suites.some((s) => s.name === suite.name)) {
|
|
255
|
+
suites.push(suite);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
protocolProperties.cipherSuites = cipherSuites;
|
|
261
|
+
const component = {
|
|
262
|
+
type: "cryptographic-asset",
|
|
263
|
+
name: comp.name,
|
|
264
|
+
"bom-ref": ref,
|
|
265
|
+
cryptoProperties: {
|
|
266
|
+
assetType: "protocol",
|
|
267
|
+
protocolProperties,
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
if (comp.version) {
|
|
271
|
+
component.version = comp.version;
|
|
272
|
+
}
|
|
273
|
+
byRef.set(ref, component);
|
|
274
|
+
}
|
|
275
|
+
return Array.from(byRef.values());
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// safer-exec walks at most this many certificates of one server's chain
|
|
279
|
+
// (tlsobserve maxChainCerts); a longer array did not come from it, so only the
|
|
280
|
+
// first entries are read.
|
|
281
|
+
const MAX_CHAIN_CERTS = 10;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* The bom-ref normalizeTracedCryptoComponents gives the protocol component of
|
|
285
|
+
* a negotiated TLS version: safer-exec's CBOM names it "TLSv1.2" with the
|
|
286
|
+
* version the prefix leaves ("1.2").
|
|
287
|
+
*
|
|
288
|
+
* @param {string} protocol negotiated protocol name from the crypto result
|
|
289
|
+
* @returns {string} protocol component bom-ref
|
|
290
|
+
*/
|
|
291
|
+
function tracedProtocolBomRef(protocol) {
|
|
292
|
+
const name = String(protocol || "");
|
|
293
|
+
const version = name.startsWith("TLSv") ? name.slice("TLSv".length) : name;
|
|
294
|
+
return `crypto/protocol/${encodeURIComponent(name)}@${encodeURIComponent(version || "unknown")}`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Builds the shared certificate model for the chains servers presented in TLS
|
|
299
|
+
* ≤ 1.2 handshakes, from the `chain` arrays of safer-exec's crypto result
|
|
300
|
+
* (report channel, safer-exec ≥ 1.2.0; earlier releases and TLS 1.3 entries
|
|
301
|
+
* carry no chain and yield nothing). The records go through the shared
|
|
302
|
+
* certificate collector, so bom-refs, properties and dedup match certificates
|
|
303
|
+
* found on disk. A presented chain is untrusted server input: no issuer edges
|
|
304
|
+
* are inferred from it, and the certificate and public-key components link
|
|
305
|
+
* through the collector as every producer's do. Each TLS protocol component
|
|
306
|
+
* provides the certificates and keys presented under it — the edge safer-
|
|
307
|
+
* exec's own CBOM records — which answers which servers presented a key.
|
|
308
|
+
*
|
|
309
|
+
* @param {Object} cryptoResult safer-exec CRYPTO result (`result.crypto`)
|
|
310
|
+
* @param {Set<string>} protocolRefs bom-refs of the protocol components in the BOM;
|
|
311
|
+
* a provides edge is only emitted for a protocol that is actually present
|
|
312
|
+
* @returns {{components: Object[], dependencies: Object[]}} certificate,
|
|
313
|
+
* public-key and algorithm components, and the protocol provides edges
|
|
314
|
+
*/
|
|
315
|
+
export function observedChainComponents(
|
|
316
|
+
cryptoResult,
|
|
317
|
+
protocolRefs = new Set(),
|
|
318
|
+
) {
|
|
319
|
+
const collector = new CertificateCollector();
|
|
320
|
+
// Protocol name -> {certSha256, spkiSha256} pairs in presentation order,
|
|
321
|
+
// leaf first; sorted to refs at the end so the output never depends on the
|
|
322
|
+
// connection order the engine happened to report.
|
|
323
|
+
const chainEntriesByProtocol = new Map();
|
|
324
|
+
// The collector keeps the first record's fields for a certSha256, so two
|
|
325
|
+
// servers presenting one hash with conflicting text would otherwise make
|
|
326
|
+
// the name (and bom-ref) depend on connection order; sort first.
|
|
327
|
+
const ciphers = (
|
|
328
|
+
Array.isArray(cryptoResult?.ciphers) ? cryptoResult.ciphers : []
|
|
329
|
+
)
|
|
330
|
+
.map((cipher) => [JSON.stringify(cipher) ?? "", cipher])
|
|
331
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
332
|
+
.map(([, cipher]) => cipher);
|
|
333
|
+
for (const cipher of ciphers) {
|
|
334
|
+
const chain = cipher?.chain;
|
|
335
|
+
if (!Array.isArray(chain) || chain.length === 0) {
|
|
336
|
+
// safer-exec ≤ 1.1.2, or a TLS 1.3 handshake (encrypted Certificate
|
|
337
|
+
// message): no chain was observed.
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const host = typeof cipher.host === "string" ? cipher.host : "";
|
|
341
|
+
for (const entry of chain.slice(0, MAX_CHAIN_CERTS)) {
|
|
342
|
+
const record = certRecordFromObservedChainEntry(entry, { host });
|
|
343
|
+
if (record) {
|
|
344
|
+
collector.addRecord(record);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// A protocol name is free text from the report channel; it only reaches a
|
|
348
|
+
// bom-ref through encodeURIComponent, so it must be well-formed.
|
|
349
|
+
const protocol = toWellFormedText(cipher.protocol);
|
|
350
|
+
if (protocol) {
|
|
351
|
+
let entries = chainEntriesByProtocol.get(protocol);
|
|
352
|
+
if (!entries) {
|
|
353
|
+
entries = [];
|
|
354
|
+
chainEntriesByProtocol.set(protocol, entries);
|
|
355
|
+
}
|
|
356
|
+
for (const entry of chain.slice(0, MAX_CHAIN_CERTS)) {
|
|
357
|
+
const certSha256 = String(entry?.certSha256 || "").toLowerCase();
|
|
358
|
+
const spkiSha256 = String(entry?.spkiSha256 || "").toLowerCase();
|
|
359
|
+
if (certSha256.length === 64) {
|
|
360
|
+
entries.push({ certSha256, spkiSha256 });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const { components, dependencies } = collector.build();
|
|
366
|
+
// The collector's components identify their asset by the hash in `version`
|
|
367
|
+
// (certSha256 for certificates, spkiSha256 for public keys).
|
|
368
|
+
const certRefBySha = new Map();
|
|
369
|
+
const keyRefBySpki = new Map();
|
|
370
|
+
for (const component of components) {
|
|
371
|
+
const assetType = component.cryptoProperties?.assetType;
|
|
372
|
+
if (assetType === "certificate") {
|
|
373
|
+
certRefBySha.set(component.version, component["bom-ref"]);
|
|
374
|
+
} else if (assetType === "related-crypto-material") {
|
|
375
|
+
keyRefBySpki.set(component.version, component["bom-ref"]);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const outputDependencies = [...dependencies];
|
|
379
|
+
for (const [protocol, entries] of chainEntriesByProtocol) {
|
|
380
|
+
const protocolRef = tracedProtocolBomRef(protocol);
|
|
381
|
+
if (!protocolRefs.has(protocolRef)) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const provides = [
|
|
385
|
+
...new Set(
|
|
386
|
+
entries.flatMap(({ certSha256, spkiSha256 }) => [
|
|
387
|
+
certRefBySha.get(certSha256),
|
|
388
|
+
keyRefBySpki.get(spkiSha256),
|
|
389
|
+
]),
|
|
390
|
+
),
|
|
391
|
+
]
|
|
392
|
+
.filter((ref) => ref)
|
|
393
|
+
.sort();
|
|
394
|
+
if (provides.length) {
|
|
395
|
+
outputDependencies.push({ ref: protocolRef, provides });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
outputDependencies.sort((left, right) => left.ref.localeCompare(right.ref));
|
|
399
|
+
return { components, dependencies: outputDependencies };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Maps the crypto libraries safer-exec reported to the paths they were loaded
|
|
404
|
+
* from.
|
|
405
|
+
*
|
|
406
|
+
* @param {Object[]} cryptoEntries safer-exec crypto-library and crypto-cipher audit entries
|
|
407
|
+
* @returns {Map<string, {name: string, version: string}>} Crypto libraries by path
|
|
408
|
+
*/
|
|
409
|
+
export function cryptoLibrariesByPath(cryptoEntries) {
|
|
410
|
+
const libraries = new Map();
|
|
411
|
+
for (const entry of cryptoEntries || []) {
|
|
412
|
+
if (entry?.type !== "crypto-library" || !entry.path || !entry.name) {
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (!libraries.has(entry.path)) {
|
|
416
|
+
libraries.set(entry.path, {
|
|
417
|
+
name: String(entry.name),
|
|
418
|
+
version: entry.version ? String(entry.version) : "",
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return libraries;
|
|
423
|
+
}
|