@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
package/lib/inventory/dosai.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import { basename, delimiter, isAbsolute, join, resolve } from "node:path";
|
|
11
11
|
import process from "node:process";
|
|
12
12
|
|
|
13
|
-
import { DEBUG_MODE } from "../core/activity.js";
|
|
13
|
+
import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
|
|
14
14
|
import { recordDegradation } from "../core/buildLedger.js";
|
|
15
15
|
import {
|
|
16
16
|
getTmpDir,
|
|
@@ -20,10 +20,14 @@ import {
|
|
|
20
20
|
safeSpawnSync,
|
|
21
21
|
safeWriteChunksSync,
|
|
22
22
|
safeWriteSync,
|
|
23
|
+
TIMEOUT_MS,
|
|
23
24
|
} from "../core/fs.js";
|
|
25
|
+
import { formatDuration, reapProcessTree } from "../core/processTree.js";
|
|
26
|
+
import { encodeUriReference } from "../core/propertySanitizer.js";
|
|
24
27
|
import {
|
|
25
28
|
indexJsonObject,
|
|
26
29
|
MAX_JSON_TEXT_BYTES,
|
|
30
|
+
readJsonMember,
|
|
27
31
|
readJsonRange,
|
|
28
32
|
} from "../parsers/largeJson.js";
|
|
29
33
|
import {
|
|
@@ -152,11 +156,7 @@ function sanitizeEndpoint(endpoint) {
|
|
|
152
156
|
// CycloneDX types `services[].endpoints[]` as an iri-reference, where square
|
|
153
157
|
// brackets are reserved for IPv6 literals and braces are excluded outright,
|
|
154
158
|
// so the template is percent-encoded before it reaches the BOM.
|
|
155
|
-
|
|
156
|
-
/[[\]{}]/g,
|
|
157
|
-
(character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`,
|
|
158
|
-
);
|
|
159
|
-
return encodedPath;
|
|
159
|
+
return encodeUriReference(routePath);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
function serviceNameFromEndpoint(endpoint) {
|
|
@@ -167,15 +167,67 @@ function serviceNameFromEndpoint(endpoint) {
|
|
|
167
167
|
.replace(/-+/g, "-");
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
170
|
+
// The .NET host prints "You must install .NET" when it finds no .NET at all,
|
|
171
|
+
// and "You must install or update .NET" when no installed runtime is new
|
|
172
|
+
// enough for the app.
|
|
173
|
+
const DOTNET_HOST_LAUNCH_FAILURE =
|
|
174
|
+
/You must install (?:or update )?\.NET to run this application/;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* The .NET runtime a framework-dependent dosai needs, read from the error the
|
|
178
|
+
* .NET host prints when it refuses to start dosai.
|
|
179
|
+
*
|
|
180
|
+
* When an older runtime is installed, the host names the exact framework
|
|
181
|
+
* version it looked for. When it finds no .NET at all, it names only its own
|
|
182
|
+
* version. The app host comes from the pack of the runtime the app targets, so
|
|
183
|
+
* its major version is still the .NET release to install.
|
|
184
|
+
*
|
|
185
|
+
* @param {Object|string} resultOrOutput spawnSync result, or the text it printed
|
|
186
|
+
* @returns {{version: (string|undefined), major: (string|undefined)}|undefined}
|
|
187
|
+
* The framework version, when the host named it, and the .NET major version;
|
|
188
|
+
* undefined when the output is not a .NET host launch failure
|
|
189
|
+
*/
|
|
190
|
+
export function dosaiDotnetRequirement(resultOrOutput) {
|
|
191
|
+
const output =
|
|
192
|
+
typeof resultOrOutput === "string"
|
|
193
|
+
? resultOrOutput
|
|
194
|
+
: `${resultOrOutput?.stdout || ""}\n${resultOrOutput?.stderr || ""}`;
|
|
195
|
+
if (!DOTNET_HOST_LAUNCH_FAILURE.test(output)) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
const version = output.match(
|
|
199
|
+
/Framework: 'Microsoft\.NETCore\.App', version '([^']+)'/,
|
|
200
|
+
)?.[1];
|
|
201
|
+
const major =
|
|
202
|
+
version?.match(/^(\d+)\./)?.[1] ||
|
|
203
|
+
output.match(/App host version: (\d+)\./)?.[1];
|
|
204
|
+
return { version, major };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* What stopped dosai from starting, for warnings and ledger details.
|
|
209
|
+
*
|
|
210
|
+
* @param {{version: (string|undefined), major: (string|undefined)}} [requirement] Result of dosaiDotnetRequirement
|
|
211
|
+
* @returns {string} A clause naming the missing .NET runtime when it is known
|
|
212
|
+
*/
|
|
213
|
+
export function dosaiDotnetRequirementDetail(requirement) {
|
|
214
|
+
if (requirement?.version) {
|
|
215
|
+
return `dosai needs the .NET runtime ${requirement.version} or newer, which is not installed`;
|
|
216
|
+
}
|
|
217
|
+
if (requirement?.major) {
|
|
218
|
+
return `dosai needs the .NET ${requirement.major} runtime, and no .NET installation was found`;
|
|
219
|
+
}
|
|
220
|
+
return "dosai could not start because the .NET runtime it needs is not installed";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* How to get dosai running when the .NET host refused to start it.
|
|
225
|
+
*
|
|
226
|
+
* @param {{version: (string|undefined), major: (string|undefined)}} [requirement] Result of dosaiDotnetRequirement
|
|
227
|
+
* @returns {string} Install instructions, naming the .NET major version when it is known
|
|
228
|
+
*/
|
|
229
|
+
export function dosaiDotnetRequirementAdvice(requirement) {
|
|
230
|
+
return `Install the .NET${requirement?.major ? ` ${requirement.major}` : ""} runtime or SDK from https://dotnet.microsoft.com/download, or download the self-contained dosai (-full suffix) from https://github.com/owasp-dep-scan/dosai/releases and set DOSAI_CMD to its path.`;
|
|
179
231
|
}
|
|
180
232
|
|
|
181
233
|
function safeDosaiPath(value) {
|
|
@@ -196,6 +248,16 @@ function safeDosaiPatternPacks(value) {
|
|
|
196
248
|
.join(delimiter);
|
|
197
249
|
}
|
|
198
250
|
|
|
251
|
+
// A --sections value names top-level report sections, which are plain
|
|
252
|
+
// identifiers in dosai's report contract. Anything else is not passed.
|
|
253
|
+
function safeDosaiSections(value) {
|
|
254
|
+
if (!value || typeof value !== "string") {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
const names = value.split(",");
|
|
258
|
+
return names.every((name) => /^[A-Za-z]+$/.test(name)) ? value : undefined;
|
|
259
|
+
}
|
|
260
|
+
|
|
199
261
|
const MAX_DOSAI_EXCLUDES = 256;
|
|
200
262
|
const MAX_BRACE_RANGE = 64;
|
|
201
263
|
const GLOB_ESCAPABLE = "*?[]{}()!+@,";
|
|
@@ -207,7 +269,10 @@ const SAFE_DOSAI_GLOB = /^[\p{L}\p{N} _.+@#%~=/*?-]+$/u;
|
|
|
207
269
|
// letters and "Program Files (x86)". Shell metacharacters are not allowed.
|
|
208
270
|
const SAFE_DOSAI_EXECUTABLE = /^[\p{L}\p{N} _.,+@#%~=/\\:()-]+$/u;
|
|
209
271
|
const warnedDosaiMessages = new Set();
|
|
210
|
-
|
|
272
|
+
// Optional dosai flags this executable has rejected, so later runs drop them
|
|
273
|
+
// without the doomed first attempt. dosai reports every unknown token in one
|
|
274
|
+
// run, so one rejection teaches the memo about every flag of that run.
|
|
275
|
+
const dosaiRejectedFlagMemo = new Map();
|
|
211
276
|
|
|
212
277
|
function warnDosaiOnce(message) {
|
|
213
278
|
if (!warnedDosaiMessages.has(message)) {
|
|
@@ -494,9 +559,25 @@ function dosaiExcludeArgs(excludes, srcPath) {
|
|
|
494
559
|
return patterns.flatMap((pattern) => ["--exclude", pattern]);
|
|
495
560
|
}
|
|
496
561
|
|
|
497
|
-
|
|
562
|
+
/**
|
|
563
|
+
* Every flag dosai reported as unknown in a run.
|
|
564
|
+
*
|
|
565
|
+
* System.CommandLine prints one `Unrecognized command or argument '--x'` line
|
|
566
|
+
* per unknown token (and a second line for the value that follows it, which
|
|
567
|
+
* carries no leading dashes and so never matches), so one failed run names
|
|
568
|
+
* every rejected flag.
|
|
569
|
+
*
|
|
570
|
+
* @param {Object} result spawnSync result
|
|
571
|
+
* @returns {Set<string>} The rejected flag literals, e.g. `{"--exclude"}`
|
|
572
|
+
*/
|
|
573
|
+
function dosaiRejectedFlags(result) {
|
|
498
574
|
const output = `${result?.stdout || ""}${result?.stderr || ""}`;
|
|
499
|
-
|
|
575
|
+
const rejected = new Set();
|
|
576
|
+
const pattern = /Unrecognized command or argument '(--[^\s']+)'/g;
|
|
577
|
+
for (const match of output.matchAll(pattern)) {
|
|
578
|
+
rejected.add(match[1]);
|
|
579
|
+
}
|
|
580
|
+
return rejected;
|
|
500
581
|
}
|
|
501
582
|
|
|
502
583
|
function safeDosaiExecutable(value) {
|
|
@@ -537,9 +618,10 @@ export function readDosaiJsonFile(jsonFile) {
|
|
|
537
618
|
}
|
|
538
619
|
}
|
|
539
620
|
|
|
540
|
-
// The sections of a dosai methods report that cdxgen reads.
|
|
541
|
-
//
|
|
542
|
-
//
|
|
621
|
+
// The sections of a dosai methods report that cdxgen reads. A run that will
|
|
622
|
+
// not run evinse asks dosai to write only these (--sections;
|
|
623
|
+
// Metadata.OmittedSections names the rest), so the complete report reaches
|
|
624
|
+
// downstream tools through the semantics slices file of an evidence run.
|
|
543
625
|
const DOSAI_METHODS_SECTIONS = [
|
|
544
626
|
"Metadata",
|
|
545
627
|
"Dependencies",
|
|
@@ -552,6 +634,84 @@ const DOSAI_METHODS_SECTIONS = [
|
|
|
552
634
|
"AiComponents",
|
|
553
635
|
];
|
|
554
636
|
|
|
637
|
+
/**
|
|
638
|
+
* The `--sections` value that writes exactly the sections cdxgen reads.
|
|
639
|
+
*
|
|
640
|
+
* Passed as `dosaiMethodsSections` by callers that know their run will not
|
|
641
|
+
* need the whole report (see {@link runsEvidenceAnalysis}); an older dosai
|
|
642
|
+
* that rejects `--sections` falls back to the full report on its own.
|
|
643
|
+
*
|
|
644
|
+
* @returns {string} Comma-separated section list for dosai `--sections`
|
|
645
|
+
*/
|
|
646
|
+
export function dosaiMethodsSectionsArgument() {
|
|
647
|
+
return DOSAI_METHODS_SECTIONS.join(",");
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Whether this scan will run the evidence (evinse) analysis.
|
|
652
|
+
*
|
|
653
|
+
* One place answers for both the trim decision in `createCsharpBom` and the
|
|
654
|
+
* evinse gate in `bin/cdxgen.js`: `--evidence` alone runs it, and so does
|
|
655
|
+
* `--include-crypto` alone, after profiles are applied (`--profile research`
|
|
656
|
+
* turns on both). Testing `options.evidence` alone would trim a report that
|
|
657
|
+
* `--include-crypto` then needs full.
|
|
658
|
+
*
|
|
659
|
+
* @param {Object} options Scan options after profile expansion
|
|
660
|
+
* @returns {boolean} True when the scan will run evinse
|
|
661
|
+
*/
|
|
662
|
+
export function runsEvidenceAnalysis(options) {
|
|
663
|
+
return Boolean(options?.evidence || options?.includeCrypto);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* The top-level sections a dosai methods report left out, empty for a full
|
|
668
|
+
* report. dosai v4 reports have no `OmittedSections`, so they count as full.
|
|
669
|
+
*
|
|
670
|
+
* @param {Object} [report] A dosai methods report as read by readDosaiMethodsReport
|
|
671
|
+
* @returns {string[]} The omitted section names
|
|
672
|
+
*/
|
|
673
|
+
export function dosaiOmittedSections(report) {
|
|
674
|
+
const omitted = report?.Metadata?.OmittedSections;
|
|
675
|
+
return Array.isArray(omitted)
|
|
676
|
+
? omitted.filter((name) => typeof name === "string" && name)
|
|
677
|
+
: [];
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* The top-level sections a dosai methods report file left out, read from its
|
|
682
|
+
* Metadata alone. dosai writes Metadata first, so this reads only the head of
|
|
683
|
+
* the file, however large the report is. A missing or unreadable file counts
|
|
684
|
+
* as full: the read that follows reports it.
|
|
685
|
+
*
|
|
686
|
+
* @param {string} reportFile Path to the dosai methods JSON
|
|
687
|
+
* @returns {string[]} The omitted section names
|
|
688
|
+
*/
|
|
689
|
+
export function dosaiReportFileOmittedSections(reportFile) {
|
|
690
|
+
if (!reportFile || !safeExistsSync(reportFile)) {
|
|
691
|
+
return [];
|
|
692
|
+
}
|
|
693
|
+
try {
|
|
694
|
+
return dosaiOmittedSections({
|
|
695
|
+
Metadata: readJsonMember(reportFile, "Metadata"),
|
|
696
|
+
});
|
|
697
|
+
} catch (_err) {
|
|
698
|
+
return [];
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* The omitted sections of a dosai methods report that cdxgen reads, so the
|
|
704
|
+
* evidence built from the report lacks their input.
|
|
705
|
+
*
|
|
706
|
+
* @param {string[]} omittedSections Sections the report left out
|
|
707
|
+
* @returns {string[]} The ones cdxgen reads
|
|
708
|
+
*/
|
|
709
|
+
export function dosaiSectionsCdxgenReads(omittedSections) {
|
|
710
|
+
return omittedSections.filter((name) =>
|
|
711
|
+
DOSAI_METHODS_SECTIONS.includes(name),
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
|
|
555
715
|
// The file each report returned by readDosaiReport came from, and whether it
|
|
556
716
|
// was trimmed to what cdxgen reads. persistDosaiSemanticsReport copies the
|
|
557
717
|
// file rather than re-serialising a trimmed or oversized report.
|
|
@@ -675,14 +835,123 @@ export function readDosaiDataFlowReport(reportFile, options = {}) {
|
|
|
675
835
|
});
|
|
676
836
|
}
|
|
677
837
|
|
|
838
|
+
/**
|
|
839
|
+
* Whether a dosai run ended because it ran out of time.
|
|
840
|
+
*
|
|
841
|
+
* dosai has no limit of its own: cdxgen's spawn timeout
|
|
842
|
+
* (CDXGEN_TIMEOUT_MS) stops the process, which reaches the caller only as an
|
|
843
|
+
* ETIMEDOUT error looking like any other failure (issue 4438).
|
|
844
|
+
*
|
|
845
|
+
* @param {Object} result spawnSync result
|
|
846
|
+
* @returns {boolean} true when the run was stopped by the spawn timeout
|
|
847
|
+
*/
|
|
848
|
+
export function dosaiRunTimedOut(result) {
|
|
849
|
+
return result?.error?.code === "ETIMEDOUT";
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* How a dosai run was stopped before it could finish, if it was.
|
|
854
|
+
*
|
|
855
|
+
* Besides the spawn timeout, a run is stopped when its console output outgrows
|
|
856
|
+
* CDXGEN_MAX_BUFFER (ENOBUFS) or a signal ends it, such as the kernel's
|
|
857
|
+
* out-of-memory killer. A stopped run can leave its slice half written.
|
|
858
|
+
*
|
|
859
|
+
* @param {Object} result spawnSync result
|
|
860
|
+
* @returns {"timeout"|"buffer"|"signal"|undefined} Why the run was stopped, or undefined when it ended by itself
|
|
861
|
+
*/
|
|
862
|
+
export function dosaiRunStopReason(result) {
|
|
863
|
+
if (dosaiRunTimedOut(result)) {
|
|
864
|
+
return "timeout";
|
|
865
|
+
}
|
|
866
|
+
if (result?.error?.code === "ENOBUFS") {
|
|
867
|
+
return "buffer";
|
|
868
|
+
}
|
|
869
|
+
if (result?.signal) {
|
|
870
|
+
return "signal";
|
|
871
|
+
}
|
|
872
|
+
return undefined;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// The output files of the dosai runs of this process that were stopped, so a
|
|
876
|
+
// caller does not follow the stop warning with advice for another failure.
|
|
877
|
+
const stoppedDosaiOutputs = new Set();
|
|
878
|
+
// The output files of the dosai runs the .NET host refused to start; the
|
|
879
|
+
// warning naming the missing runtime already says why they are absent.
|
|
880
|
+
const unstartedDosaiOutputs = new Set();
|
|
881
|
+
// The executable that wrote each output file of this process, and whether it
|
|
882
|
+
// was the cdxgen-plugins-bin build rather than an override.
|
|
883
|
+
const dosaiOutputProducers = new Map();
|
|
884
|
+
// Executables the .NET host refused to start, once that has been reported to
|
|
885
|
+
// the user, by the pregen check or by a dosai run.
|
|
886
|
+
const dosaiLaunchFailuresReported = new Set();
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Note that the .NET host's refusal to start a dosai executable has been
|
|
890
|
+
* reported, so dosai runs of the same executable do not repeat the warning.
|
|
891
|
+
*
|
|
892
|
+
* @param {string} executable dosai executable
|
|
893
|
+
*/
|
|
894
|
+
export function markDosaiLaunchFailureReported(executable) {
|
|
895
|
+
const safeExecutable = safeDosaiExecutable(executable);
|
|
896
|
+
if (safeExecutable) {
|
|
897
|
+
dosaiLaunchFailuresReported.add(safeExecutable);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Whether the dosai run that should have written a file was stopped (by the
|
|
903
|
+
* time limit, the output buffer limit, or a signal) and already reported so.
|
|
904
|
+
*
|
|
905
|
+
* @param {string} outputFile Output file passed to the dosai run
|
|
906
|
+
* @returns {boolean} true when the run was stopped
|
|
907
|
+
*/
|
|
908
|
+
export function dosaiRunWasStopped(outputFile) {
|
|
909
|
+
const outputPath = safeDosaiPath(outputFile);
|
|
910
|
+
return Boolean(outputPath && stoppedDosaiOutputs.has(outputPath));
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Whether the dosai run that should have written a file failed in a way it has
|
|
915
|
+
* already reported: it was stopped (see dosaiRunWasStopped), or the .NET host
|
|
916
|
+
* refused to start dosai and the warning named the missing runtime.
|
|
917
|
+
*
|
|
918
|
+
* @param {string} outputFile Output file passed to the dosai run
|
|
919
|
+
* @returns {boolean} true when the failure has been reported
|
|
920
|
+
*/
|
|
921
|
+
export function dosaiFailureReported(outputFile) {
|
|
922
|
+
const outputPath = safeDosaiPath(outputFile);
|
|
923
|
+
return Boolean(
|
|
924
|
+
outputPath &&
|
|
925
|
+
(stoppedDosaiOutputs.has(outputPath) ||
|
|
926
|
+
unstartedDosaiOutputs.has(outputPath)),
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function dosaiStopMessage(reason, signal) {
|
|
931
|
+
if (reason === "timeout") {
|
|
932
|
+
return `WARN: dosai did not finish within ${formatDuration(TIMEOUT_MS)} and was stopped, so its slices are missing or incomplete. Allow more time with CDXGEN_TIMEOUT_MS (milliseconds, every external command).`;
|
|
933
|
+
}
|
|
934
|
+
if (reason === "buffer") {
|
|
935
|
+
return "WARN: dosai wrote more console output than CDXGEN_MAX_BUFFER allows and was stopped, so its slices are missing or incomplete. Raise CDXGEN_MAX_BUFFER (bytes).";
|
|
936
|
+
}
|
|
937
|
+
return `WARN: dosai was stopped by ${signal || "a signal"} before it finished, so its slices are missing or incomplete. A SIGKILL usually means the system ran out of memory.`;
|
|
938
|
+
}
|
|
939
|
+
|
|
678
940
|
/**
|
|
679
941
|
* Run a dosai subcommand ("methods", "dataflows", or "crypto") against a source
|
|
680
942
|
* tree and write its JSON output to the given file.
|
|
681
943
|
*
|
|
944
|
+
* Flags cdxgen adds for its own benefit that an older dosai may not know
|
|
945
|
+
* (--exclude, --sections, --crypto-dataflows) are optional: when dosai reports
|
|
946
|
+
* them as unknown, every rejected one is dropped in a single retry, later runs
|
|
947
|
+
* of the same executable skip them up front, and dropping is reported once
|
|
948
|
+
* (a warning for --exclude, whose loss changes results; a debug line for the
|
|
949
|
+
* size-only flags). A run that rejects anything else has simply failed.
|
|
950
|
+
*
|
|
682
951
|
* @param {string} command Dosai subcommand to execute
|
|
683
952
|
* @param {string} src Source directory to analyze
|
|
684
953
|
* @param {string} outputFile Path where the dosai JSON output is written
|
|
685
|
-
* @param {Object} [options] Options carrying dosaiCommand, dataFlowPatterns, patternPacks,
|
|
954
|
+
* @param {Object} [options] Options carrying dosaiCommand, dataFlowPatterns, patternPacks, exclude globs, or dosaiMethodsSections
|
|
686
955
|
* @returns {boolean} True when the command succeeded and produced the output file, false otherwise
|
|
687
956
|
*/
|
|
688
957
|
export function runDosaiCommand(command, src, outputFile, options = {}) {
|
|
@@ -714,47 +983,138 @@ export function runDosaiCommand(command, src, outputFile, options = {}) {
|
|
|
714
983
|
} else if (command === "crypto") {
|
|
715
984
|
args.push("--format", "dosai");
|
|
716
985
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
986
|
+
// Optional flag groups: the flag literal, the tokens to append while it is
|
|
987
|
+
// active, and whether dropping it changes what dosai analyses (true, only
|
|
988
|
+
// --exclude) or only the size of the output (false).
|
|
989
|
+
const flagGroups = [];
|
|
990
|
+
if (command === "crypto") {
|
|
991
|
+
// cdxgen reads only Assets, Operations, and Materials from the crypto
|
|
992
|
+
// report, so the data-flow graph it never reads is omitted.
|
|
993
|
+
flagGroups.push({
|
|
994
|
+
flag: "--crypto-dataflows",
|
|
995
|
+
tokens: ["--crypto-dataflows", "none"],
|
|
996
|
+
changesResults: false,
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
const sections =
|
|
1000
|
+
command === "methods"
|
|
1001
|
+
? safeDosaiSections(options.dosaiMethodsSections)
|
|
1002
|
+
: undefined;
|
|
1003
|
+
if (sections) {
|
|
1004
|
+
flagGroups.push({
|
|
1005
|
+
flag: "--sections",
|
|
1006
|
+
tokens: ["--sections", sections],
|
|
1007
|
+
changesResults: false,
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
const rejectedBefore = dosaiRejectedFlagMemo.get(executable);
|
|
1011
|
+
// An executable that rejected --exclude gets no exclude translation at all:
|
|
1012
|
+
// computing it would print warnings about patterns that are never passed.
|
|
1013
|
+
const excludeTokens =
|
|
1014
|
+
rejectedBefore?.has("--exclude") || !options.exclude
|
|
1015
|
+
? []
|
|
1016
|
+
: dosaiExcludeArgs(options.exclude, srcPath);
|
|
1017
|
+
if (excludeTokens.length) {
|
|
1018
|
+
flagGroups.push({
|
|
1019
|
+
flag: "--exclude",
|
|
1020
|
+
tokens: excludeTokens,
|
|
1021
|
+
changesResults: true,
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
const activeGroups = flagGroups.filter(
|
|
1025
|
+
(group) => !rejectedBefore?.has(group.flag),
|
|
1026
|
+
);
|
|
1027
|
+
const commandArgs = [
|
|
1028
|
+
...args,
|
|
1029
|
+
...activeGroups.flatMap((group) => group.tokens),
|
|
1030
|
+
];
|
|
720
1031
|
if (DEBUG_MODE) {
|
|
721
|
-
console.log("Executing", executable,
|
|
1032
|
+
console.log("Executing", executable, commandArgs.join(" "));
|
|
722
1033
|
}
|
|
723
|
-
|
|
1034
|
+
stoppedDosaiOutputs.delete(outputPath);
|
|
1035
|
+
unstartedDosaiOutputs.delete(outputPath);
|
|
1036
|
+
dosaiOutputProducers.delete(outputPath);
|
|
1037
|
+
let startedAt = Date.now();
|
|
1038
|
+
let result = safeSpawnSync(executable, commandArgs, {
|
|
724
1039
|
cwd: srcPath,
|
|
725
1040
|
shell: false,
|
|
726
1041
|
});
|
|
727
|
-
// Dosai releases that predate
|
|
728
|
-
//
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
)
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1042
|
+
// Dosai releases that predate an optional flag reject it. Rerun once without
|
|
1043
|
+
// every flag this run rejected, so a newer cdxgen flag never turns into a
|
|
1044
|
+
// missing slice on an older dosai.
|
|
1045
|
+
if (result?.status !== 0) {
|
|
1046
|
+
const rejected = dosaiRejectedFlags(result);
|
|
1047
|
+
if (
|
|
1048
|
+
rejected.size &&
|
|
1049
|
+
[...rejected].every((flag) =>
|
|
1050
|
+
activeGroups.some((group) => group.flag === flag),
|
|
1051
|
+
)
|
|
1052
|
+
) {
|
|
1053
|
+
const dropped = flagGroups.filter((group) => rejected.has(group.flag));
|
|
1054
|
+
const memo = dosaiRejectedFlagMemo.get(executable) ?? new Set();
|
|
1055
|
+
for (const group of dropped) {
|
|
1056
|
+
memo.add(group.flag);
|
|
1057
|
+
}
|
|
1058
|
+
dosaiRejectedFlagMemo.set(executable, memo);
|
|
1059
|
+
const droppedExclude = dropped.some((group) => group.changesResults);
|
|
1060
|
+
if (droppedExclude) {
|
|
1061
|
+
warnDosaiOnce(
|
|
1062
|
+
"This dosai build does not support --exclude, so it runs without the exclude patterns. Upgrade dosai or set DOSAI_CMD to a newer build to honour them.",
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
if (DEBUG_MODE) {
|
|
1066
|
+
console.log(
|
|
1067
|
+
`Retrying without the optional dosai flag(s) it rejected: ${dropped.map((group) => group.flag).join(", ")}.`,
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
const retryArgs = [
|
|
1071
|
+
...args,
|
|
1072
|
+
...activeGroups
|
|
1073
|
+
.filter((group) => !rejected.has(group.flag))
|
|
1074
|
+
.flatMap((group) => group.tokens),
|
|
1075
|
+
];
|
|
1076
|
+
startedAt = Date.now();
|
|
1077
|
+
result = safeSpawnSync(executable, retryArgs, {
|
|
1078
|
+
cwd: srcPath,
|
|
1079
|
+
shell: false,
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
742
1082
|
}
|
|
743
|
-
|
|
1083
|
+
const dotnetRequirement = dosaiDotnetRequirement(result);
|
|
1084
|
+
if (dotnetRequirement) {
|
|
1085
|
+
const detail = dosaiDotnetRequirementDetail(dotnetRequirement);
|
|
744
1086
|
recordDegradation("dotnet.sdk.missing", {
|
|
745
1087
|
ecosystem: "csharp",
|
|
746
1088
|
tool: "dotnet",
|
|
747
1089
|
impact: "none",
|
|
748
1090
|
command: `${basename(executable)} ${args.join(" ")}`,
|
|
749
|
-
detail:
|
|
750
|
-
"dosai reported that no .NET SDK is installed, so the requested dosai analysis could not run.",
|
|
1091
|
+
detail: `${detail}, so the requested dosai analysis could not run.`,
|
|
751
1092
|
});
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
)
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1093
|
+
// Visible without debug output, and once per scan: every later run of
|
|
1094
|
+
// this executable fails the same way.
|
|
1095
|
+
if (!dosaiLaunchFailuresReported.has(executable)) {
|
|
1096
|
+
dosaiLaunchFailuresReported.add(executable);
|
|
1097
|
+
console.warn(
|
|
1098
|
+
`WARNING: ${detail}, so dosai could not run and the .NET evidence it provides (call graph, reachability, services, crypto) is missing. ${dosaiDotnetRequirementAdvice(dotnetRequirement)}`,
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
unstartedDosaiOutputs.add(outputPath);
|
|
1102
|
+
}
|
|
1103
|
+
const stopReason = dosaiRunStopReason(result);
|
|
1104
|
+
if (stopReason) {
|
|
1105
|
+
// Visible without debug output: otherwise a stopped dosai reads like any
|
|
1106
|
+
// other failure while the scan silently loses its .NET evidence.
|
|
1107
|
+
console.warn(dosaiStopMessage(stopReason, result.signal));
|
|
1108
|
+
// Windows kills only the dosai process itself, and the build hosts it
|
|
1109
|
+
// started would keep running.
|
|
1110
|
+
reapProcessTree(result.pid, startedAt, Date.now(), "dosai");
|
|
1111
|
+
// A stopped dosai can leave its output half written, and the caller would
|
|
1112
|
+
// read the truncated file back as a slice.
|
|
1113
|
+
if (safeExistsSync(outputPath)) {
|
|
1114
|
+
safeRmSync(outputPath, { force: true });
|
|
1115
|
+
}
|
|
1116
|
+
stoppedDosaiOutputs.add(outputPath);
|
|
1117
|
+
return false;
|
|
758
1118
|
}
|
|
759
1119
|
if (result?.status !== 0 || result?.error || !safeExistsSync(outputPath)) {
|
|
760
1120
|
if (DEBUG_MODE) {
|
|
@@ -766,6 +1126,10 @@ export function runDosaiCommand(command, src, outputFile, options = {}) {
|
|
|
766
1126
|
}
|
|
767
1127
|
return false;
|
|
768
1128
|
}
|
|
1129
|
+
dosaiOutputProducers.set(outputPath, {
|
|
1130
|
+
executable,
|
|
1131
|
+
bundled: !options.dosaiCommand && !readEnvironmentVariable("DOSAI_CMD"),
|
|
1132
|
+
});
|
|
769
1133
|
return true;
|
|
770
1134
|
}
|
|
771
1135
|
|
|
@@ -774,7 +1138,7 @@ export function runDosaiCommand(command, src, outputFile, options = {}) {
|
|
|
774
1138
|
*
|
|
775
1139
|
* @param {string} src Source directory to analyze
|
|
776
1140
|
* @param {string} outputFile Path where the methods slice JSON is written
|
|
777
|
-
* @param {Object} [options] Options forwarded to runDosaiCommand
|
|
1141
|
+
* @param {Object} [options] Options forwarded to runDosaiCommand; `dosaiMethodsSections` names the top-level sections to write (see dosaiMethodsSectionsArgument), omitted for the full report
|
|
778
1142
|
* @returns {boolean} True when the slice was produced successfully
|
|
779
1143
|
*/
|
|
780
1144
|
export function createDosaiMethodsSlice(src, outputFile, options = {}) {
|
|
@@ -827,6 +1191,141 @@ export function analyzeDosaiCrypto(src, options = {}) {
|
|
|
827
1191
|
}
|
|
828
1192
|
}
|
|
829
1193
|
|
|
1194
|
+
const DOSAI_PURL = "pkg:github/owasp-dep-scan/dosai";
|
|
1195
|
+
const DOSAI_VERSION_PATTERN = /^\d+(?:\.\d+){1,3}(?:-[0-9A-Za-z.-]+)?$/;
|
|
1196
|
+
// The version each dosai executable reported for itself, by executable.
|
|
1197
|
+
const dosaiExecutableVersions = new Map();
|
|
1198
|
+
|
|
1199
|
+
/**
|
|
1200
|
+
* The version a dosai executable reports with `--version`, without its build
|
|
1201
|
+
* metadata: `5.0.0-rc.2+e42b205` reads as `5.0.0-rc.2`. Each executable is
|
|
1202
|
+
* asked once per process.
|
|
1203
|
+
*
|
|
1204
|
+
* @param {string} executable dosai executable
|
|
1205
|
+
* @returns {string|undefined} The version, or undefined when dosai reported none
|
|
1206
|
+
*/
|
|
1207
|
+
function dosaiExecutableVersion(executable) {
|
|
1208
|
+
const safeExecutable = safeDosaiExecutable(executable);
|
|
1209
|
+
if (!safeExecutable) {
|
|
1210
|
+
return undefined;
|
|
1211
|
+
}
|
|
1212
|
+
if (!dosaiExecutableVersions.has(safeExecutable)) {
|
|
1213
|
+
const result = safeSpawnSync(safeExecutable, ["--version"], {
|
|
1214
|
+
shell: false,
|
|
1215
|
+
});
|
|
1216
|
+
const reported =
|
|
1217
|
+
result?.status === 0 && !result.error
|
|
1218
|
+
? `${result.stdout || ""}`.trim().split(/\s+/)[0].split("+")[0]
|
|
1219
|
+
: "";
|
|
1220
|
+
dosaiExecutableVersions.set(
|
|
1221
|
+
safeExecutable,
|
|
1222
|
+
DOSAI_VERSION_PATTERN.test(reported) ? reported : undefined,
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
return dosaiExecutableVersions.get(safeExecutable);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* The dosai version a report records about its producer. AnalyzerVersion is
|
|
1230
|
+
* the four-part assembly version, so `4.1.0.0` reads as `4.1.0`, and a
|
|
1231
|
+
* prerelease build reads as the release it precedes.
|
|
1232
|
+
*
|
|
1233
|
+
* @param {Object} [report] A dosai report
|
|
1234
|
+
* @returns {string|undefined} The version, or undefined when the report names no dosai version
|
|
1235
|
+
*/
|
|
1236
|
+
function dosaiReportVersion(report) {
|
|
1237
|
+
const metadata = report?.Metadata;
|
|
1238
|
+
if (`${metadata?.Tool || ""}`.toLowerCase() !== "dosai") {
|
|
1239
|
+
return undefined;
|
|
1240
|
+
}
|
|
1241
|
+
const match = `${metadata.AnalyzerVersion || ""}`.match(
|
|
1242
|
+
/^(\d+\.\d+\.\d+)(?:\.(\d+))?$/,
|
|
1243
|
+
);
|
|
1244
|
+
if (!match) {
|
|
1245
|
+
return undefined;
|
|
1246
|
+
}
|
|
1247
|
+
return match[2] && match[2] !== "0" ? match[0] : match[1];
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function dosaiToolComponent(version, manifestComponent, bundled) {
|
|
1251
|
+
// The manifest component describes the bundled binary, hashes included
|
|
1252
|
+
if (bundled && version && manifestComponent?.version === version) {
|
|
1253
|
+
return structuredClone(manifestComponent);
|
|
1254
|
+
}
|
|
1255
|
+
const purl = version ? `${DOSAI_PURL}@${version}` : DOSAI_PURL;
|
|
1256
|
+
const component = { type: "application", name: "dosai" };
|
|
1257
|
+
if (version) {
|
|
1258
|
+
component.version = version;
|
|
1259
|
+
}
|
|
1260
|
+
if (manifestComponent?.description) {
|
|
1261
|
+
component.description = manifestComponent.description;
|
|
1262
|
+
}
|
|
1263
|
+
component.purl = purl;
|
|
1264
|
+
component["bom-ref"] = purl;
|
|
1265
|
+
for (const field of ["licenses", "externalReferences"]) {
|
|
1266
|
+
if (manifestComponent?.[field]) {
|
|
1267
|
+
component[field] = structuredClone(manifestComponent[field]);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
return component;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
/**
|
|
1274
|
+
* The tool component naming the dosai behind a methods report.
|
|
1275
|
+
*
|
|
1276
|
+
* A report that a dosai run of this process wrote names the executable that
|
|
1277
|
+
* wrote it, with the version that executable reports. A report reused from an
|
|
1278
|
+
* earlier run names the version recorded in its Metadata, or the configured
|
|
1279
|
+
* dosai's own version when that is a build of the same release. No component
|
|
1280
|
+
* is returned for a report that is missing or that no dosai wrote, such as
|
|
1281
|
+
* when the .NET host refused to start dosai.
|
|
1282
|
+
*
|
|
1283
|
+
* The cdxgen-plugins-bin manifest component is returned as-is only for its
|
|
1284
|
+
* own binary at its own version. Any other dosai gets a component built from
|
|
1285
|
+
* the manifest's project fields (description, licenses, references), without
|
|
1286
|
+
* the bundled binary's hashes or path.
|
|
1287
|
+
*
|
|
1288
|
+
* @param {string} reportFile Path of the dosai methods report
|
|
1289
|
+
* @param {Object} [report] The report as read, for its Metadata
|
|
1290
|
+
* @param {Object} [manifestComponent] dosai component of the cdxgen-plugins-bin manifest
|
|
1291
|
+
* @returns {Object[]} The dosai tool component, or an empty array
|
|
1292
|
+
*/
|
|
1293
|
+
export function dosaiToolComponents(reportFile, report, manifestComponent) {
|
|
1294
|
+
const reportPath = safeDosaiPath(reportFile);
|
|
1295
|
+
if (!reportPath) {
|
|
1296
|
+
return [];
|
|
1297
|
+
}
|
|
1298
|
+
const producer = dosaiOutputProducers.get(reportPath);
|
|
1299
|
+
if (producer) {
|
|
1300
|
+
return [
|
|
1301
|
+
dosaiToolComponent(
|
|
1302
|
+
dosaiExecutableVersion(producer.executable) ||
|
|
1303
|
+
dosaiReportVersion(report),
|
|
1304
|
+
manifestComponent,
|
|
1305
|
+
producer.bundled,
|
|
1306
|
+
),
|
|
1307
|
+
];
|
|
1308
|
+
}
|
|
1309
|
+
const recorded = dosaiReportVersion(report);
|
|
1310
|
+
if (!recorded) {
|
|
1311
|
+
return [];
|
|
1312
|
+
}
|
|
1313
|
+
const executable = dosaiBin();
|
|
1314
|
+
const configured = executable
|
|
1315
|
+
? dosaiExecutableVersion(executable)
|
|
1316
|
+
: undefined;
|
|
1317
|
+
if (configured?.split("-")[0] === recorded) {
|
|
1318
|
+
return [
|
|
1319
|
+
dosaiToolComponent(
|
|
1320
|
+
configured,
|
|
1321
|
+
manifestComponent,
|
|
1322
|
+
!readEnvironmentVariable("DOSAI_CMD"),
|
|
1323
|
+
),
|
|
1324
|
+
];
|
|
1325
|
+
}
|
|
1326
|
+
return [dosaiToolComponent(recorded, manifestComponent, false)];
|
|
1327
|
+
}
|
|
1328
|
+
|
|
830
1329
|
/**
|
|
831
1330
|
* Build the combined native dosai report object persisted for downstream tools.
|
|
832
1331
|
*
|
|
@@ -887,10 +1386,12 @@ function* combinedDosaiReportChunks(combined, parts) {
|
|
|
887
1386
|
* analyzeGolemProject on branch feat/rusi-persist-report): when a semantics-
|
|
888
1387
|
* slices path is provided, the FULL native report is written there and kept so
|
|
889
1388
|
* downstream tools (depscan) can consume the complete methods + data-flow
|
|
890
|
-
* facts that cdxgen only projects a subset of into the SBOM evidence.
|
|
891
|
-
*
|
|
892
|
-
*
|
|
893
|
-
*
|
|
1389
|
+
* facts that cdxgen only projects a subset of into the SBOM evidence. A
|
|
1390
|
+
* methods report the user supplied with --usages-slices-file is written as
|
|
1391
|
+
* given, with a recorded degradation when it was trimmed with --sections.
|
|
1392
|
+
* dotnet does not otherwise use the semantics slice (atom is never run for
|
|
1393
|
+
* dotnet), so the path is free to carry the combined dosai report. Returns the
|
|
1394
|
+
* resolved durable path when something was persisted, otherwise undefined.
|
|
894
1395
|
*/
|
|
895
1396
|
export function persistDosaiSemanticsReport(
|
|
896
1397
|
options,
|
|
@@ -912,6 +1413,19 @@ export function persistDosaiSemanticsReport(
|
|
|
912
1413
|
const combined = buildCombinedDosaiReport(methodsSlice, dataFlowSlice);
|
|
913
1414
|
const methodsSource = dosaiReportSources.get(methodsSlice);
|
|
914
1415
|
const dataFlowSource = dosaiReportSources.get(dataFlowSlice);
|
|
1416
|
+
// A methods report written with --sections is partial. Persist it as given
|
|
1417
|
+
// (a user-supplied usages slice is their choice), but say so, because the
|
|
1418
|
+
// semantics file is the source of truth for downstream tools.
|
|
1419
|
+
const omittedSections = dosaiOmittedSections(methodsSlice);
|
|
1420
|
+
if (omittedSections.length) {
|
|
1421
|
+
recordDegradation("dosai.semantics.trimmed-report", {
|
|
1422
|
+
ecosystem: "csharp",
|
|
1423
|
+
tool: "dosai",
|
|
1424
|
+
impact: "none",
|
|
1425
|
+
command: `persistDosaiSemanticsReport ${durablePath}`,
|
|
1426
|
+
detail: `The dosai methods report was written with --sections, so the persisted report omits: ${omittedSections.join(", ")}. Run dosai methods without --sections for the complete report.`,
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
915
1429
|
try {
|
|
916
1430
|
let text;
|
|
917
1431
|
if (!methodsSource?.trimmed && !dataFlowSource?.trimmed) {
|
|
@@ -958,21 +1472,63 @@ export function persistDosaiSemanticsReport(
|
|
|
958
1472
|
* Build a purl alias map for a list of components.
|
|
959
1473
|
*
|
|
960
1474
|
* @param {Object[]} [components] Component objects with purl fields
|
|
961
|
-
* @
|
|
1475
|
+
* @param {Object} [options] Options passed to {@link buildDosaiPurlAliasMap} (`srcPath`)
|
|
1476
|
+
* @returns {Map<string, string>} Map of component purls, carrying the version-free identity index
|
|
962
1477
|
*/
|
|
963
|
-
export function buildPurlAliasMap(components = []) {
|
|
964
|
-
return buildDosaiPurlAliasMap(components);
|
|
1478
|
+
export function buildPurlAliasMap(components = [], options = {}) {
|
|
1479
|
+
return buildDosaiPurlAliasMap(components, options);
|
|
965
1480
|
}
|
|
966
1481
|
|
|
967
1482
|
/**
|
|
968
|
-
* Resolve a
|
|
1483
|
+
* Resolve a dosai purl to the canonical component purl, by version, and for a
|
|
1484
|
+
* package the BOM holds in several versions by the record's location.
|
|
969
1485
|
*
|
|
970
1486
|
* @param {string} purl Purl from a dosai report
|
|
971
1487
|
* @param {Map<string, string>} purlAliasMap Alias map built by buildPurlAliasMap
|
|
972
|
-
* @
|
|
1488
|
+
* @param {string} [location] Source location of the dosai record
|
|
1489
|
+
* @returns {string|undefined} Canonical component purl, the input purl when the BOM has no such package,
|
|
1490
|
+
* or undefined when empty or ambiguous
|
|
973
1491
|
*/
|
|
974
|
-
export function resolveComponentPurl(purl, purlAliasMap) {
|
|
975
|
-
return resolveDosaiComponentPurl(purl, purlAliasMap);
|
|
1492
|
+
export function resolveComponentPurl(purl, purlAliasMap, location) {
|
|
1493
|
+
return resolveDosaiComponentPurl(purl, purlAliasMap, location);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
/**
|
|
1497
|
+
* The component purls one PackageReachability fact belongs to: each of its
|
|
1498
|
+
* locations resolved in its own project (a versionless purl from unrestored
|
|
1499
|
+
* projects spans several), else the purl alone.
|
|
1500
|
+
*
|
|
1501
|
+
* @param {Object} reachability dosai PackageReachability entry
|
|
1502
|
+
* @param {Map<string, string>} purlAliasMap Alias map built by buildPurlAliasMap
|
|
1503
|
+
* @param {Object} [graph] Call graph lookups `{ edgesById, nodesById }`
|
|
1504
|
+
* @returns {string[]} Component purls
|
|
1505
|
+
*/
|
|
1506
|
+
function reachabilityComponentPurls(reachability, purlAliasMap, graph = {}) {
|
|
1507
|
+
const locations = [
|
|
1508
|
+
...(reachability.SourceLocations || []).map((location) =>
|
|
1509
|
+
dosaiSourceLocation(location),
|
|
1510
|
+
),
|
|
1511
|
+
...(reachability.EdgeIds || []).map((edgeId) =>
|
|
1512
|
+
dosaiSourceLocation(graph.edgesById?.get(edgeId)),
|
|
1513
|
+
),
|
|
1514
|
+
...(reachability.NodeIds || []).map((nodeId) =>
|
|
1515
|
+
dosaiSourceLocationFromNode(graph.nodesById?.get(nodeId)),
|
|
1516
|
+
),
|
|
1517
|
+
].filter(Boolean);
|
|
1518
|
+
const purls = new Set(
|
|
1519
|
+
locations
|
|
1520
|
+
.map((location) =>
|
|
1521
|
+
resolveComponentPurl(reachability.Purl, purlAliasMap, location),
|
|
1522
|
+
)
|
|
1523
|
+
.filter(Boolean),
|
|
1524
|
+
);
|
|
1525
|
+
if (!purls.size) {
|
|
1526
|
+
const purl = resolveComponentPurl(reachability.Purl, purlAliasMap);
|
|
1527
|
+
if (purl) {
|
|
1528
|
+
purls.add(purl);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
return [...purls];
|
|
976
1532
|
}
|
|
977
1533
|
|
|
978
1534
|
/**
|
|
@@ -1021,24 +1577,51 @@ export function addDosaiReachabilityProperties(component, reachability) {
|
|
|
1021
1577
|
*
|
|
1022
1578
|
* @param {Object} methodsSlice Parsed dosai methods slice JSON
|
|
1023
1579
|
* @param {Object[]} [components] BOM components used to resolve purl aliases and mutate
|
|
1580
|
+
* @param {Object} [options] Options
|
|
1581
|
+
* @param {string} [options.srcPath] Directory dosai analyzed; a package the BOM holds in several
|
|
1582
|
+
* versions is matched by the project of the record's file
|
|
1024
1583
|
* @returns {number} Number of distinct components enriched
|
|
1025
1584
|
*/
|
|
1026
|
-
export function applyDosaiReachabilityEvidence(
|
|
1027
|
-
|
|
1585
|
+
export function applyDosaiReachabilityEvidence(
|
|
1586
|
+
methodsSlice,
|
|
1587
|
+
components = [],
|
|
1588
|
+
options = {},
|
|
1589
|
+
) {
|
|
1590
|
+
const purlAliasMap = buildPurlAliasMap(components, options);
|
|
1028
1591
|
const componentsByPurl = new Map(
|
|
1029
1592
|
components
|
|
1030
1593
|
.filter((component) => component?.purl)
|
|
1031
1594
|
.map((component) => [component.purl, component]),
|
|
1032
1595
|
);
|
|
1596
|
+
const graph = {
|
|
1597
|
+
edgesById: new Map(
|
|
1598
|
+
(methodsSlice?.CallGraph?.Edges || []).map((edge) => [edge.Id, edge]),
|
|
1599
|
+
),
|
|
1600
|
+
nodesById: new Map(
|
|
1601
|
+
(methodsSlice?.CallGraph?.Nodes || []).map((node) => [node.Id, node]),
|
|
1602
|
+
),
|
|
1603
|
+
};
|
|
1033
1604
|
const enriched = new Set();
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1605
|
+
// The first fact applied to a component wins, so a fact that names the
|
|
1606
|
+
// component's exact purl goes before one that reached it by name.
|
|
1607
|
+
const facts = [...(methodsSlice?.PackageReachability || [])].sort(
|
|
1608
|
+
(a, b) =>
|
|
1609
|
+
Number(!componentsByPurl.has(a.Purl)) -
|
|
1610
|
+
Number(!componentsByPurl.has(b.Purl)),
|
|
1611
|
+
);
|
|
1612
|
+
for (const reachability of facts) {
|
|
1613
|
+
for (const purl of reachabilityComponentPurls(
|
|
1614
|
+
reachability,
|
|
1615
|
+
purlAliasMap,
|
|
1616
|
+
graph,
|
|
1617
|
+
)) {
|
|
1618
|
+
const component = componentsByPurl.get(purl);
|
|
1619
|
+
if (!component) {
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
addDosaiReachabilityProperties(component, reachability);
|
|
1623
|
+
enriched.add(component);
|
|
1039
1624
|
}
|
|
1040
|
-
addDosaiReachabilityProperties(component, reachability);
|
|
1041
|
-
enriched.add(component);
|
|
1042
1625
|
}
|
|
1043
1626
|
return enriched.size;
|
|
1044
1627
|
}
|
|
@@ -1051,10 +1634,17 @@ export function applyDosaiReachabilityEvidence(methodsSlice, components = []) {
|
|
|
1051
1634
|
*
|
|
1052
1635
|
* @param {Object} methodsSlice Parsed dosai methods slice JSON
|
|
1053
1636
|
* @param {Object[]} [components] BOM components used to resolve purl aliases
|
|
1637
|
+
* @param {Object} [options] Options
|
|
1638
|
+
* @param {string} [options.srcPath] Directory dosai analyzed; a package the BOM holds in several
|
|
1639
|
+
* versions is matched by the project of the record's file
|
|
1054
1640
|
* @returns {Object} Object with purlLocationMap, purlModulesMap, and purlMethodsMap keyed by purl
|
|
1055
1641
|
*/
|
|
1056
|
-
export function collectDosaiPurlEvidence(
|
|
1057
|
-
|
|
1642
|
+
export function collectDosaiPurlEvidence(
|
|
1643
|
+
methodsSlice,
|
|
1644
|
+
components = [],
|
|
1645
|
+
options = {},
|
|
1646
|
+
) {
|
|
1647
|
+
const purlAliasMap = buildPurlAliasMap(components, options);
|
|
1058
1648
|
const purlLocationMap = {};
|
|
1059
1649
|
const purlModulesMap = {};
|
|
1060
1650
|
const purlMethodsMap = {};
|
|
@@ -1066,7 +1656,11 @@ export function collectDosaiPurlEvidence(methodsSlice, components = []) {
|
|
|
1066
1656
|
);
|
|
1067
1657
|
|
|
1068
1658
|
for (const dependency of methodsSlice?.Dependencies || []) {
|
|
1069
|
-
const purl = resolveComponentPurl(
|
|
1659
|
+
const purl = resolveComponentPurl(
|
|
1660
|
+
dependency.Purl,
|
|
1661
|
+
purlAliasMap,
|
|
1662
|
+
dependency.Path,
|
|
1663
|
+
);
|
|
1070
1664
|
if (!purl) {
|
|
1071
1665
|
continue;
|
|
1072
1666
|
}
|
|
@@ -1079,42 +1673,61 @@ export function collectDosaiPurlEvidence(methodsSlice, components = []) {
|
|
|
1079
1673
|
}
|
|
1080
1674
|
|
|
1081
1675
|
for (const reachability of methodsSlice?.PackageReachability || []) {
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1676
|
+
// Every location resolves in its own project: a versionless purl from
|
|
1677
|
+
// unrestored projects spans projects restored at different versions.
|
|
1678
|
+
const resolveAt = (location) =>
|
|
1679
|
+
resolveComponentPurl(reachability.Purl, purlAliasMap, location);
|
|
1680
|
+
const factPurls = new Set();
|
|
1681
|
+
// Modules and methods of call-graph items that name no source file
|
|
1682
|
+
// (frames inside the package), held for the fact's own version.
|
|
1683
|
+
const unplaced = [];
|
|
1086
1684
|
let hasExplicitSourceLocations = false;
|
|
1087
1685
|
for (const sourceLocation of reachability.SourceLocations || []) {
|
|
1088
1686
|
const location = dosaiSourceLocation(sourceLocation);
|
|
1687
|
+
const purl = resolveAt(location);
|
|
1089
1688
|
addDosaiSetValue(purlLocationMap, purl, location);
|
|
1689
|
+
if (purl) {
|
|
1690
|
+
factPurls.add(purl);
|
|
1691
|
+
}
|
|
1090
1692
|
hasExplicitSourceLocations ||= Boolean(location);
|
|
1091
1693
|
}
|
|
1092
1694
|
for (const edgeId of reachability.EdgeIds || []) {
|
|
1093
1695
|
const edge = edgesById.get(edgeId);
|
|
1696
|
+
const location = dosaiSourceLocation(edge);
|
|
1697
|
+
const purl = resolveAt(location ?? edge?.Path);
|
|
1698
|
+
const method = edge?.CalledMethodName || edge?.TargetName;
|
|
1699
|
+
if (!purl) {
|
|
1700
|
+
unplaced.push([purlMethodsMap, method]);
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
factPurls.add(purl);
|
|
1094
1704
|
if (!hasExplicitSourceLocations) {
|
|
1095
|
-
addDosaiSetValue(purlLocationMap, purl,
|
|
1705
|
+
addDosaiSetValue(purlLocationMap, purl, location);
|
|
1096
1706
|
}
|
|
1097
|
-
addDosaiSetValue(
|
|
1098
|
-
purlMethodsMap,
|
|
1099
|
-
purl,
|
|
1100
|
-
edge?.CalledMethodName || edge?.TargetName,
|
|
1101
|
-
);
|
|
1707
|
+
addDosaiSetValue(purlMethodsMap, purl, method);
|
|
1102
1708
|
}
|
|
1103
1709
|
for (const nodeId of reachability.NodeIds || []) {
|
|
1104
1710
|
const node = nodesById.get(nodeId);
|
|
1711
|
+
const location = dosaiSourceLocationFromNode(node);
|
|
1712
|
+
const purl = resolveAt(location);
|
|
1713
|
+
const module = node?.ClassName || node?.Module;
|
|
1714
|
+
const method = node?.Name || node?.Identity?.MethodName;
|
|
1715
|
+
if (!purl) {
|
|
1716
|
+
unplaced.push([purlModulesMap, module], [purlMethodsMap, method]);
|
|
1717
|
+
continue;
|
|
1718
|
+
}
|
|
1719
|
+
factPurls.add(purl);
|
|
1105
1720
|
if (!hasExplicitSourceLocations) {
|
|
1106
|
-
addDosaiSetValue(
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1721
|
+
addDosaiSetValue(purlLocationMap, purl, location);
|
|
1722
|
+
}
|
|
1723
|
+
addDosaiSetValue(purlModulesMap, purl, module);
|
|
1724
|
+
addDosaiSetValue(purlMethodsMap, purl, method);
|
|
1725
|
+
}
|
|
1726
|
+
if (factPurls.size === 1) {
|
|
1727
|
+
const [purl] = factPurls;
|
|
1728
|
+
for (const [map, value] of unplaced) {
|
|
1729
|
+
addDosaiSetValue(map, purl, value);
|
|
1111
1730
|
}
|
|
1112
|
-
addDosaiSetValue(purlModulesMap, purl, node?.ClassName || node?.Module);
|
|
1113
|
-
addDosaiSetValue(
|
|
1114
|
-
purlMethodsMap,
|
|
1115
|
-
purl,
|
|
1116
|
-
node?.Name || node?.Identity?.MethodName,
|
|
1117
|
-
);
|
|
1118
1731
|
}
|
|
1119
1732
|
}
|
|
1120
1733
|
return { purlLocationMap, purlModulesMap, purlMethodsMap };
|
|
@@ -1128,17 +1741,38 @@ export function collectDosaiPurlEvidence(methodsSlice, components = []) {
|
|
|
1128
1741
|
*
|
|
1129
1742
|
* @param {Object} dataFlowResult Parsed dosai data-flow slice JSON
|
|
1130
1743
|
* @param {Object[]} [components] BOM components used to resolve purl aliases
|
|
1744
|
+
* @param {Object} [options] Options
|
|
1745
|
+
* @param {string} [options.srcPath] Directory dosai analyzed; a package the BOM holds in several
|
|
1746
|
+
* versions is matched by the project of the record's file
|
|
1131
1747
|
* @returns {Object} Map of canonical purl to arrays of call-stack frame objects
|
|
1132
1748
|
*/
|
|
1133
|
-
export function collectDosaiDataFlowFrames(
|
|
1134
|
-
|
|
1749
|
+
export function collectDosaiDataFlowFrames(
|
|
1750
|
+
dataFlowResult,
|
|
1751
|
+
components = [],
|
|
1752
|
+
options = {},
|
|
1753
|
+
) {
|
|
1754
|
+
const purlAliasMap = buildPurlAliasMap(components, options);
|
|
1135
1755
|
const nodesById = new Map(
|
|
1136
1756
|
(dataFlowResult?.Nodes || []).map((node) => [node.Id, node]),
|
|
1137
1757
|
);
|
|
1138
1758
|
const dataFlowFrames = {};
|
|
1139
1759
|
const addFramesForPurl = (purl, frames) => {
|
|
1140
|
-
|
|
1141
|
-
|
|
1760
|
+
if (!frames.length) {
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
// The flow runs in the project of its first frame that settles one.
|
|
1764
|
+
let componentPurl;
|
|
1765
|
+
for (const frame of frames) {
|
|
1766
|
+
componentPurl = resolveComponentPurl(
|
|
1767
|
+
purl,
|
|
1768
|
+
purlAliasMap,
|
|
1769
|
+
frame.fullFilename,
|
|
1770
|
+
);
|
|
1771
|
+
if (componentPurl) {
|
|
1772
|
+
break;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
if (!componentPurl) {
|
|
1142
1776
|
return;
|
|
1143
1777
|
}
|
|
1144
1778
|
dataFlowFrames[componentPurl] ??= [];
|
|
@@ -1575,6 +2209,12 @@ export function collectDosaiServicesFromMethods(
|
|
|
1575
2209
|
/**
|
|
1576
2210
|
* Normalize a services map into a sorted array of CycloneDX service objects.
|
|
1577
2211
|
*
|
|
2212
|
+
* This is where evinse's service maps — dosai, golem, kosi, scalasem, an
|
|
2213
|
+
* OpenAPI document, and the atom usage and type detectors — become
|
|
2214
|
+
* `services[]`, so it is also where every endpoint is percent-encoded into the
|
|
2215
|
+
* iri-reference CycloneDX requires. The collectors keep the readable template
|
|
2216
|
+
* (`/users/{id}`); the BOM gets `/users/%7Bid%7D`.
|
|
2217
|
+
*
|
|
1578
2218
|
* @param {Object} [servicesMap] Map of service name to service definition with Set-backed endpoints
|
|
1579
2219
|
* @returns {Object[]} Array of service objects with sorted endpoint arrays and properties
|
|
1580
2220
|
*/
|
|
@@ -1582,12 +2222,18 @@ export function normalizeDosaiServiceMap(servicesMap = {}) {
|
|
|
1582
2222
|
return Object.keys(servicesMap)
|
|
1583
2223
|
.map((serviceName) => {
|
|
1584
2224
|
const definition = servicesMap[serviceName];
|
|
2225
|
+
const endpoints = new Set();
|
|
2226
|
+
for (const endpoint of definition.endpoints || []) {
|
|
2227
|
+
if (typeof endpoint === "string" && endpoint) {
|
|
2228
|
+
endpoints.add(encodeUriReference(endpoint));
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
1585
2231
|
const service = {
|
|
1586
2232
|
name:
|
|
1587
2233
|
definition.name ||
|
|
1588
2234
|
serviceName ||
|
|
1589
2235
|
`dosai-${basename(serviceName)}-service`,
|
|
1590
|
-
endpoints: Array.from(
|
|
2236
|
+
endpoints: Array.from(endpoints).sort(),
|
|
1591
2237
|
authenticated: definition.authenticated,
|
|
1592
2238
|
// Accept both spellings. Every pre-4.0.0 producer — collectDosaiServicesFromMethods here, and
|
|
1593
2239
|
// the OpenAPI/Java/Python paths in evinser.js — sets the camelCase `xTrustBoundary`; only the
|