@cdxgen/cdxgen 13.2.0 → 13.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -14
- package/bin/cdxgen.js +310 -138
- package/bin/evinse.js +22 -0
- package/bin/repl.js +86 -0
- package/bin/sign.js +119 -15
- package/bin/tracebom.js +69 -5
- package/bin/validate.js +107 -19
- package/bin/verify.js +172 -63
- package/data/helpers/init.gradle +46 -21
- package/data/remediations.json +85 -0
- package/data/rules/dependency-sources.yaml +67 -0
- package/data/rules/scala-evidence.yaml +115 -0
- package/lib/audit/index.js +38 -6
- package/lib/cli/bomAssembly.js +105 -15
- package/lib/cli/cliOptions.js +93 -1
- package/lib/cli/index.js +156 -58
- package/lib/cli/jsBom.js +99 -53
- package/lib/cli/jvmBom.js +708 -147
- package/lib/cli/managedBom.js +415 -224
- package/lib/cli/nativeBom.js +264 -44
- package/lib/core/activity.js +12 -0
- package/lib/core/env.js +22 -15
- package/lib/core/fs.js +176 -7
- package/lib/core/httpClient.js +159 -24
- package/lib/core/metadataProperties.js +97 -0
- package/lib/core/pluginRun.js +184 -0
- package/lib/core/processTree.js +100 -0
- package/lib/core/projectConfig.js +31 -4
- package/lib/core/runState.js +39 -0
- package/lib/core/toml.js +72 -0
- package/lib/ecosystems/asarutils.js +4 -2
- package/lib/ecosystems/bunutils.js +274 -108
- package/lib/ecosystems/cargoCache.js +231 -8
- package/lib/ecosystems/caxa.js +434 -2
- package/lib/ecosystems/cmakeResolver.js +76 -21
- package/lib/ecosystems/cppBuildContext.js +631 -0
- package/lib/ecosystems/cppEvidence.js +399 -51
- package/lib/ecosystems/cppIncludeResolver.js +294 -0
- package/lib/ecosystems/denoutils.js +93 -66
- package/lib/ecosystems/ecosystems.js +1983 -793
- package/lib/ecosystems/golem.js +28 -12
- package/lib/ecosystems/gradleutils.js +356 -37
- package/lib/ecosystems/jsEvidence.js +104 -44
- package/lib/ecosystems/kosi.js +31 -7
- package/lib/ecosystems/npmutils.js +174 -9
- package/lib/ecosystems/parsers-dotnet.js +616 -159
- package/lib/ecosystems/parsers-elm.js +46 -28
- package/lib/ecosystems/parsers-gleam.js +3 -4
- package/lib/ecosystems/parsers-go.js +371 -70
- package/lib/ecosystems/parsers-js.js +263 -76
- package/lib/ecosystems/parsers-julia.js +2 -3
- package/lib/ecosystems/parsers-jvm.js +204 -12
- package/lib/ecosystems/parsers-misc.js +929 -216
- package/lib/ecosystems/parsers-python.js +167 -24
- package/lib/ecosystems/parsers-rust.js +28 -22
- package/lib/ecosystems/parsers-terraform.js +1573 -19
- package/lib/ecosystems/pythonutils.js +102 -6
- package/lib/ecosystems/remote/huggingface.js +3 -0
- package/lib/ecosystems/rubyutils.js +1492 -182
- package/lib/ecosystems/rusi.js +54 -10
- package/lib/ecosystems/sbtutils.js +243 -187
- package/lib/ecosystems/scalacliutils.js +342 -0
- package/lib/ecosystems/scalasem.js +2618 -0
- package/lib/ecosystems/terraformRegistry.js +288 -0
- package/lib/ecosystems/utils.js +2 -0
- package/lib/ecosystems/vendoredCode.js +156 -0
- package/lib/evinser/evinser.js +1008 -191
- package/lib/evinser/swiftsem.js +1914 -302
- package/lib/helpers/agenticToolutils.js +2 -2
- package/lib/helpers/bomSigner.js +1101 -222
- package/lib/inventory/aiCollector.js +4 -1
- package/lib/inventory/analyzer.js +110 -127
- package/lib/inventory/atomUtils.js +703 -43
- package/lib/inventory/auditCategories.js +7 -0
- package/lib/inventory/buildToolRateLimit.js +144 -0
- package/lib/inventory/cacheCommands.js +2 -1
- package/lib/inventory/cdxrs.js +103 -12
- package/lib/inventory/cmakeBuildDirs.js +254 -0
- package/lib/inventory/deps.js +501 -124
- package/lib/inventory/depsUtils.js +183 -11
- package/lib/inventory/display.js +4 -5
- package/lib/inventory/dosai.js +798 -57
- package/lib/inventory/dosaiParsers.js +310 -15
- package/lib/inventory/dynamic.js +90 -6
- package/lib/inventory/evidenceUtils.js +166 -113
- package/lib/inventory/fetchBatch.js +690 -55
- package/lib/inventory/fetchDiskCache.js +597 -0
- package/lib/inventory/forgeEnricher.js +14 -5
- package/lib/inventory/jvmLocalRepos.js +917 -0
- package/lib/inventory/mcpConfigParser.js +2 -3
- package/lib/inventory/parentSelection.js +146 -0
- package/lib/inventory/protobom.js +8 -1
- package/lib/inventory/purl.js +152 -3
- package/lib/inventory/rustFormulationParser.js +3 -4
- package/lib/inventory/sbomDocument.js +2 -70
- package/lib/inventory/scalaCoords.js +153 -0
- package/lib/inventory/source.js +45 -27
- package/lib/inventory/spdx.js +101 -0
- package/lib/inventory/traceRunner.js +295 -25
- package/lib/inventory/tracedCrypto.js +294 -0
- package/lib/managers/binary.js +115 -24
- package/lib/managers/docker.js +467 -46
- package/lib/managers/oci.js +63 -3
- package/lib/parsers/cmakeCache.js +69 -0
- package/lib/parsers/cmakeLists.js +466 -0
- package/lib/parsers/cmakePresets.js +400 -0
- package/lib/parsers/compileCommands.js +557 -0
- package/lib/parsers/largeJson.js +454 -0
- package/lib/parsers/npmrc.js +19 -1
- package/lib/parsers/strictJson.js +322 -0
- package/lib/parsers/terraformConfig.js +1073 -0
- package/lib/parsers/terraformSource.js +652 -0
- package/lib/server/server.js +23 -0
- package/lib/stages/postgen/introspection/markers.js +1 -1
- package/lib/stages/postgen/postgen.js +83 -16
- package/lib/stages/postgen/specVersionCompat.js +32 -0
- package/lib/validator/bomValidator.js +6 -16
- package/lib/validator/index.js +64 -11
- package/lib/validator/reporters/console.js +3 -0
- package/lib/validator/reporters/json.js +2 -0
- package/lib/validator/reporters/sarif.js +7 -0
- package/package.json +23 -23
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +2 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +33 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +24 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +7 -4
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +3 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +9 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +55 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts +13 -8
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/metadataProperties.d.ts +39 -0
- package/types/lib/core/metadataProperties.d.ts.map +1 -0
- package/types/lib/core/pluginRun.d.ts +97 -0
- package/types/lib/core/pluginRun.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +36 -0
- package/types/lib/core/processTree.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +6 -2
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/runState.d.ts +29 -0
- package/types/lib/core/runState.d.ts.map +1 -0
- package/types/lib/core/toml.d.ts +20 -0
- package/types/lib/core/toml.d.ts.map +1 -0
- package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
- package/types/lib/ecosystems/bunutils.d.ts +12 -5
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/cargoCache.d.ts +12 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
- package/types/lib/ecosystems/caxa.d.ts +14 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +28 -3
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
- package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +17 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
- package/types/lib/ecosystems/denoutils.d.ts +9 -3
- package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +62 -13
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +70 -4
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +13 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmutils.d.ts +57 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +54 -2
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +16 -5
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +70 -4
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +53 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +27 -2
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +164 -3
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts +13 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/sbtutils.d.ts +88 -2
- package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
- package/types/lib/ecosystems/scalasem.d.ts +311 -0
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/utils.d.ts +2 -2
- package/types/lib/ecosystems/utils.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +132 -15
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +280 -15
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +182 -13
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +219 -5
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +3 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
- package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
- package/types/lib/inventory/cacheCommands.d.ts +2 -1
- package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
- package/types/lib/inventory/cdxrs.d.ts +8 -3
- package/types/lib/inventory/cdxrs.d.ts.map +1 -1
- package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
- package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +59 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts +15 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +121 -9
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts +53 -7
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts +10 -0
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +19 -4
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +83 -14
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
- package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/parentSelection.d.ts +34 -0
- package/types/lib/inventory/parentSelection.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/purl.d.ts +71 -2
- package/types/lib/inventory/purl.d.ts.map +1 -1
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts +80 -0
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +9 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +59 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +42 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts +30 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +29 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +17 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeCache.d.ts +16 -0
- package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +77 -0
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
- package/types/lib/parsers/cmakePresets.d.ts +78 -0
- package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
- package/types/lib/parsers/compileCommands.d.ts +115 -0
- package/types/lib/parsers/compileCommands.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +52 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +5 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/parsers/strictJson.d.ts +46 -0
- package/types/lib/parsers/strictJson.d.ts.map +1 -0
- package/types/lib/parsers/terraformConfig.d.ts +70 -0
- package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
- package/types/lib/parsers/terraformSource.d.ts +66 -0
- package/types/lib/parsers/terraformSource.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +18 -1
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
|
@@ -1,5 +1,18 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
basename,
|
|
4
|
+
dirname,
|
|
5
|
+
isAbsolute,
|
|
6
|
+
relative,
|
|
7
|
+
resolve,
|
|
8
|
+
sep,
|
|
9
|
+
win32,
|
|
10
|
+
} from "node:path";
|
|
11
|
+
|
|
1
12
|
import { Purl } from "@cdxgen/cdx-purl";
|
|
2
13
|
|
|
14
|
+
import { safeExistsSync } from "../core/fs.js";
|
|
15
|
+
|
|
3
16
|
/**
|
|
4
17
|
* Build a lowercase type/namespace/name lookup key for a purl.
|
|
5
18
|
*
|
|
@@ -73,6 +86,62 @@ function dosaiSourceLineNumber(item) {
|
|
|
73
86
|
return location?.LineNumber || item?.LineNumber;
|
|
74
87
|
}
|
|
75
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Format the `file#line` location of a dosai MethodCalls row, relative to the
|
|
91
|
+
* scanned directory like dosai's source rows.
|
|
92
|
+
*
|
|
93
|
+
* A call site found in assembly IL names its file through the PDB. dosai 5
|
|
94
|
+
* writes it relative to the scan root and leaves Path out when the file has
|
|
95
|
+
* no place in the tree; dosai 4 writes the PDB path as it is: absolute for a
|
|
96
|
+
* local build, `/_/` for a deterministic one (owasp-dep-scan/dosai#79). An
|
|
97
|
+
* absolute path under the scanned directory is made relative to it, and any
|
|
98
|
+
* other build path, or a missing one, gives way to the file name, as for call
|
|
99
|
+
* graph edges, so both versions list a call site found in source and in IL
|
|
100
|
+
* once. Where no sequence point covers a call, the row names the assembly and
|
|
101
|
+
* its line is an IL offset, which is no source location.
|
|
102
|
+
*
|
|
103
|
+
* @param {Object} methodCall Dosai MethodCalls row
|
|
104
|
+
* @param {string} [srcPath] Directory dosai analyzed
|
|
105
|
+
* @returns {string|undefined} Location string, or undefined when the row names no source file
|
|
106
|
+
*/
|
|
107
|
+
export function dosaiCallSiteLocation(methodCall, srcPath) {
|
|
108
|
+
const file = dosaiCallSiteFile(methodCall, srcPath);
|
|
109
|
+
const lowerFile = file?.toLowerCase();
|
|
110
|
+
if (!file || lowerFile.endsWith(".dll") || lowerFile.endsWith(".exe")) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
const lineNumber = methodCall?.LineNumber;
|
|
114
|
+
return lineNumber > 0 ? `${file}#${lineNumber}` : file;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function dosaiCallSiteFile(methodCall, srcPath) {
|
|
118
|
+
const filePath = methodCall?.Path;
|
|
119
|
+
if (filePath && filePath !== "<unknown>") {
|
|
120
|
+
if (!isAbsolute(filePath) && !win32.isAbsolute(filePath)) {
|
|
121
|
+
return filePath;
|
|
122
|
+
}
|
|
123
|
+
if (srcPath && isAbsolute(filePath)) {
|
|
124
|
+
const relativePath = relative(resolve(srcPath), filePath);
|
|
125
|
+
if (
|
|
126
|
+
relativePath &&
|
|
127
|
+
relativePath !== ".." &&
|
|
128
|
+
!relativePath.startsWith(`..${sep}`) &&
|
|
129
|
+
!isAbsolute(relativePath)
|
|
130
|
+
) {
|
|
131
|
+
return relativePath;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const fileName = methodCall?.FileName || filePath;
|
|
136
|
+
if (!fileName || fileName === "<unknown>") {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
// dosai 4 kept a Windows build's whole path as the file name on Linux.
|
|
140
|
+
return fileName.slice(
|
|
141
|
+
Math.max(fileName.lastIndexOf("/"), fileName.lastIndexOf("\\")) + 1,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
76
145
|
/**
|
|
77
146
|
* Return a validated source location for .NET source extensions, from a call graph node.
|
|
78
147
|
*
|
|
@@ -111,44 +180,270 @@ export function dosaiSourceLocation(location) {
|
|
|
111
180
|
return sourceLocation;
|
|
112
181
|
}
|
|
113
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Split a purl into its version-free identity key and its version.
|
|
185
|
+
*
|
|
186
|
+
* @param {string} purl Package URL string
|
|
187
|
+
* @returns {{key: string, version: (string|undefined)}|undefined} Parts, or undefined for an empty input
|
|
188
|
+
*/
|
|
189
|
+
function dosaiPurlParts(purl) {
|
|
190
|
+
const key = normalizeDosaiPurlKey(purl);
|
|
191
|
+
if (!key) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
let version;
|
|
195
|
+
try {
|
|
196
|
+
version = Purl.parse(purl).version || undefined;
|
|
197
|
+
} catch (_err) {
|
|
198
|
+
const at = purl.split("?")[0].split("#")[0].lastIndexOf("@");
|
|
199
|
+
version = at > 0 ? purl.slice(at + 1).split("?")[0] : undefined;
|
|
200
|
+
}
|
|
201
|
+
return { key, version };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The manifests a component was identified from, as recorded in its
|
|
206
|
+
* `internal:SrcFile` values.
|
|
207
|
+
*
|
|
208
|
+
* @param {Object} component BOM component
|
|
209
|
+
* @returns {string[]} Manifest paths as recorded
|
|
210
|
+
*/
|
|
211
|
+
function componentSrcFiles(component) {
|
|
212
|
+
const files = new Set();
|
|
213
|
+
for (const property of component?.properties || []) {
|
|
214
|
+
if (property?.name === "internal:SrcFile" && property.value) {
|
|
215
|
+
files.add(`${property.value}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return [...files];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Resolve a recorded manifest path. A BOM records it relative to the scanned
|
|
223
|
+
* directory, a scan in progress as found; a relative path that does not exist
|
|
224
|
+
* under the scanned directory is tried against the working directory.
|
|
225
|
+
*
|
|
226
|
+
* @param {string} file Manifest path as recorded
|
|
227
|
+
* @param {string} [srcPath] Scanned directory
|
|
228
|
+
* @returns {string} Absolute manifest path
|
|
229
|
+
*/
|
|
230
|
+
function resolveManifestPath(file, srcPath) {
|
|
231
|
+
if (isAbsolute(file)) {
|
|
232
|
+
return file;
|
|
233
|
+
}
|
|
234
|
+
const fromSrcPath = resolve(srcPath || ".", file);
|
|
235
|
+
if (!srcPath || safeExistsSync(fromSrcPath)) {
|
|
236
|
+
return fromSrcPath;
|
|
237
|
+
}
|
|
238
|
+
const fromCwd = resolve(file);
|
|
239
|
+
return safeExistsSync(fromCwd) ? fromCwd : fromSrcPath;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The project directory a .NET manifest belongs to. A project.assets.json
|
|
244
|
+
* names its project, which an artifacts layout keeps outside the `obj/`
|
|
245
|
+
* holding the file; otherwise it sits in the project's `obj/`, and the other
|
|
246
|
+
* manifests in the project directory.
|
|
247
|
+
*
|
|
248
|
+
* @param {string} file Absolute manifest path
|
|
249
|
+
* @returns {string} Absolute project directory
|
|
250
|
+
*/
|
|
251
|
+
function manifestProjectDir(file) {
|
|
252
|
+
const dir = dirname(file);
|
|
253
|
+
if (basename(file).toLowerCase() === "project.assets.json") {
|
|
254
|
+
try {
|
|
255
|
+
const projectPath = JSON.parse(readFileSync(file, "utf-8"))?.project
|
|
256
|
+
?.restore?.projectPath;
|
|
257
|
+
const projectDir = projectPath && dirname(resolve(projectPath));
|
|
258
|
+
if (projectDir && safeExistsSync(projectDir)) {
|
|
259
|
+
return projectDir;
|
|
260
|
+
}
|
|
261
|
+
} catch (_err) {
|
|
262
|
+
// An unreadable file falls back to its location
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return basename(dir) === "obj" ? dirname(dir) : dir;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* The project directories a candidate's manifests belong to, worked out on
|
|
270
|
+
* first use: only a record that names a version the BOM lacks, of a package
|
|
271
|
+
* the BOM holds in several versions, needs them.
|
|
272
|
+
*
|
|
273
|
+
* @param {Object} candidate Candidate built by buildDosaiPurlAliasMap
|
|
274
|
+
* @param {Map<string, string>} purlAliasMap Alias map carrying srcPath and the directory cache
|
|
275
|
+
* @returns {string[]} Absolute project directories
|
|
276
|
+
*/
|
|
277
|
+
function candidateProjectDirs(candidate, purlAliasMap) {
|
|
278
|
+
if (!candidate.projectDirs) {
|
|
279
|
+
const dirs = new Set();
|
|
280
|
+
for (const file of candidate.srcFiles) {
|
|
281
|
+
const path = resolveManifestPath(file, purlAliasMap?.srcPath);
|
|
282
|
+
let dir = purlAliasMap?.projectDirCache?.get(path);
|
|
283
|
+
if (!dir) {
|
|
284
|
+
dir = manifestProjectDir(path);
|
|
285
|
+
purlAliasMap?.projectDirCache?.set(path, dir);
|
|
286
|
+
}
|
|
287
|
+
dirs.add(dir);
|
|
288
|
+
}
|
|
289
|
+
candidate.projectDirs = [...dirs];
|
|
290
|
+
}
|
|
291
|
+
return candidate.projectDirs;
|
|
292
|
+
}
|
|
293
|
+
|
|
114
294
|
/**
|
|
115
295
|
* Build a purl alias map from BOM components.
|
|
116
296
|
*
|
|
117
|
-
* Maps
|
|
118
|
-
*
|
|
297
|
+
* Maps each component purl to itself, and indexes the components by their
|
|
298
|
+
* version-free identity, so a dosai-reported purl can be reconciled by version
|
|
299
|
+
* (see {@link resolveDosaiComponentPurl}). Two versions of one package are two
|
|
300
|
+
* components, and nothing here picks one of them on a name alone.
|
|
119
301
|
*
|
|
120
302
|
* @param {Object[]} [components] Component objects with purl fields
|
|
121
|
-
* @
|
|
303
|
+
* @param {Object} [options] Options
|
|
304
|
+
* @param {string} [options.srcPath] Directory dosai analyzed, which its relative locations
|
|
305
|
+
* and the BOM's relative manifest paths are relative to
|
|
306
|
+
* @returns {Map<string, string>} Map of purl to canonical component purl, carrying the identity index
|
|
122
307
|
*/
|
|
123
|
-
export function buildDosaiPurlAliasMap(components = []) {
|
|
308
|
+
export function buildDosaiPurlAliasMap(components = [], options = {}) {
|
|
124
309
|
const purlAliasMap = new Map();
|
|
310
|
+
const candidatesByKey = new Map();
|
|
125
311
|
for (const component of components) {
|
|
126
312
|
if (!component?.purl) {
|
|
127
313
|
continue;
|
|
128
314
|
}
|
|
129
315
|
purlAliasMap.set(component.purl, component.purl);
|
|
130
|
-
const
|
|
131
|
-
if (
|
|
132
|
-
|
|
316
|
+
const parts = dosaiPurlParts(component.purl);
|
|
317
|
+
if (!parts) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const candidates = candidatesByKey.get(parts.key) || [];
|
|
321
|
+
const existing = candidates.find(
|
|
322
|
+
(candidate) => candidate.purl === component.purl,
|
|
323
|
+
);
|
|
324
|
+
if (existing) {
|
|
325
|
+
for (const file of componentSrcFiles(component)) {
|
|
326
|
+
if (!existing.srcFiles.includes(file)) {
|
|
327
|
+
existing.srcFiles.push(file);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
candidates.push({
|
|
332
|
+
purl: component.purl,
|
|
333
|
+
version: parts.version,
|
|
334
|
+
srcFiles: componentSrcFiles(component),
|
|
335
|
+
});
|
|
133
336
|
}
|
|
337
|
+
candidatesByKey.set(parts.key, candidates);
|
|
134
338
|
}
|
|
339
|
+
purlAliasMap.candidatesByKey = candidatesByKey;
|
|
340
|
+
purlAliasMap.srcPath = options.srcPath;
|
|
341
|
+
purlAliasMap.projectDirCache = new Map();
|
|
135
342
|
return purlAliasMap;
|
|
136
343
|
}
|
|
137
344
|
|
|
138
345
|
/**
|
|
139
|
-
*
|
|
346
|
+
* The candidate whose project directory is the nearest one holding a file.
|
|
347
|
+
*
|
|
348
|
+
* @param {Object[]} candidates Candidates of one package identity
|
|
349
|
+
* @param {string} location Location string (`path` or `path#line`), relative to srcPath or absolute
|
|
350
|
+
* @param {Map<string, string>} purlAliasMap Alias map built by buildDosaiPurlAliasMap
|
|
351
|
+
* @returns {string|undefined} Candidate purl, or undefined when no single candidate is nearest
|
|
352
|
+
*/
|
|
353
|
+
function candidateForLocation(candidates, location, purlAliasMap) {
|
|
354
|
+
if (!location) {
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
const file = resolve(
|
|
358
|
+
purlAliasMap?.srcPath || ".",
|
|
359
|
+
`${location}`.split("#")[0],
|
|
360
|
+
);
|
|
361
|
+
let best;
|
|
362
|
+
let bestLength = -1;
|
|
363
|
+
let tied = false;
|
|
364
|
+
for (const candidate of candidates) {
|
|
365
|
+
for (const dir of candidateProjectDirs(candidate, purlAliasMap)) {
|
|
366
|
+
if (file !== dir && !file.startsWith(`${dir}${sep}`)) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (dir.length > bestLength) {
|
|
370
|
+
best = candidate.purl;
|
|
371
|
+
bestLength = dir.length;
|
|
372
|
+
tied = false;
|
|
373
|
+
} else if (dir.length === bestLength && best !== candidate.purl) {
|
|
374
|
+
tied = true;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return tied ? undefined : best;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Choose among the components that share a file name (two versions of a
|
|
383
|
+
* package ship the same DLLs): the only one, else the one whose project
|
|
384
|
+
* directory is nearest the record's location.
|
|
385
|
+
*
|
|
386
|
+
* @param {Iterable<string>} purls Candidate component purls
|
|
387
|
+
* @param {Map<string, string>} purlAliasMap Alias map built by buildDosaiPurlAliasMap
|
|
388
|
+
* @param {string} [location] Source location of the dosai record (`path` or `path#line`)
|
|
389
|
+
* @returns {string|undefined} The component purl, or undefined when it cannot be told
|
|
390
|
+
*/
|
|
391
|
+
export function pickDosaiComponentByLocation(purls, purlAliasMap, location) {
|
|
392
|
+
const wanted = new Set(purls);
|
|
393
|
+
if (wanted.size <= 1) {
|
|
394
|
+
return [...wanted][0];
|
|
395
|
+
}
|
|
396
|
+
const candidates = [];
|
|
397
|
+
for (const list of purlAliasMap?.candidatesByKey?.values() || []) {
|
|
398
|
+
for (const candidate of list) {
|
|
399
|
+
if (wanted.has(candidate.purl)) {
|
|
400
|
+
candidates.push(candidate);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return candidateForLocation(candidates, location, purlAliasMap);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Resolve a dosai-reported purl to a BOM component purl.
|
|
409
|
+
*
|
|
410
|
+
* The exact purl wins, then the same package at the same version (ids compare
|
|
411
|
+
* case-insensitively). A versionless purl, or a version the BOM does not hold,
|
|
412
|
+
* maps to the package's only component when it has one. When the BOM holds
|
|
413
|
+
* several versions, the record's location picks the version of the project
|
|
414
|
+
* that file belongs to; without a location that settles it the purl maps to
|
|
415
|
+
* nothing rather than to an arbitrary version (issue dosai#72).
|
|
140
416
|
*
|
|
141
417
|
* @param {string} purl Purl reported by dosai
|
|
142
418
|
* @param {Map<string, string>} purlAliasMap Alias map built by buildDosaiPurlAliasMap
|
|
143
|
-
* @
|
|
419
|
+
* @param {string} [location] Source location of the dosai record (`path` or `path#line`)
|
|
420
|
+
* @returns {string|undefined} Canonical component purl, the input purl when the BOM has no
|
|
421
|
+
* component of that package, or undefined when empty or ambiguous
|
|
144
422
|
*/
|
|
145
|
-
export function resolveDosaiComponentPurl(purl, purlAliasMap) {
|
|
423
|
+
export function resolveDosaiComponentPurl(purl, purlAliasMap, location) {
|
|
146
424
|
if (!purl) {
|
|
147
425
|
return undefined;
|
|
148
426
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
);
|
|
427
|
+
const exact = purlAliasMap?.get(purl);
|
|
428
|
+
if (exact) {
|
|
429
|
+
return exact;
|
|
430
|
+
}
|
|
431
|
+
const parts = dosaiPurlParts(purl);
|
|
432
|
+
const candidates = parts && purlAliasMap?.candidatesByKey?.get(parts.key);
|
|
433
|
+
if (!candidates?.length) {
|
|
434
|
+
return purl;
|
|
435
|
+
}
|
|
436
|
+
if (parts.version) {
|
|
437
|
+
const sameVersion = candidates.filter(
|
|
438
|
+
(candidate) =>
|
|
439
|
+
candidate.version?.toLowerCase() === parts.version.toLowerCase(),
|
|
440
|
+
);
|
|
441
|
+
if (sameVersion.length === 1) {
|
|
442
|
+
return sameVersion[0].purl;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (candidates.length === 1) {
|
|
446
|
+
return candidates[0].purl;
|
|
447
|
+
}
|
|
448
|
+
return candidateForLocation(candidates, location, purlAliasMap);
|
|
154
449
|
}
|
package/lib/inventory/dynamic.js
CHANGED
|
@@ -4,8 +4,36 @@ import { checksumFile } from "../core/fs.js";
|
|
|
4
4
|
import { mapWithConcurrency } from "../core/parallel.js";
|
|
5
5
|
import { resolvePackageForFile } from "./osPackageResolver.js";
|
|
6
6
|
import { applyPurl, tryBuildPurl } from "./purl.js";
|
|
7
|
+
import {
|
|
8
|
+
cryptoLibrariesByPath,
|
|
9
|
+
normalizeTracedCryptoComponents,
|
|
10
|
+
} from "./tracedCrypto.js";
|
|
7
11
|
import { executeAndTrace, groupHttpEntriesToServices } from "./traceRunner.js";
|
|
8
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Converts a traced library path into a purl subpath. Windows paths
|
|
15
|
+
* ("C:\Windows\System32\bcrypt.dll", "\\server\share\x.dll") use forward
|
|
16
|
+
* slashes and drop the drive or UNC prefix, which a purl subpath cannot carry;
|
|
17
|
+
* the full path stays in the cdx:dynamic:filePath property.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} libPath - Absolute path of the loaded library
|
|
20
|
+
* @returns {string|null} Subpath without leading separators, or null
|
|
21
|
+
*/
|
|
22
|
+
export function librarySubpath(libPath) {
|
|
23
|
+
if (!libPath) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
let subpath = libPath;
|
|
27
|
+
const isDrivePath = /^[A-Za-z]:[\\/]/.test(subpath);
|
|
28
|
+
if (isDrivePath || subpath.startsWith("\\\\")) {
|
|
29
|
+
subpath = subpath.replaceAll("\\", "/");
|
|
30
|
+
if (isDrivePath) {
|
|
31
|
+
subpath = subpath.substring(3);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return subpath.replace(/^\/+/, "") || null;
|
|
35
|
+
}
|
|
36
|
+
|
|
9
37
|
/**
|
|
10
38
|
* Builds a flat list of CycloneDX component objects and services by executing
|
|
11
39
|
* the given command and inspecting which shared libraries it loads at runtime
|
|
@@ -32,8 +60,14 @@ export async function buildDynamicComponents(
|
|
|
32
60
|
traceOptions = {},
|
|
33
61
|
) {
|
|
34
62
|
const result = await executeAndTrace(commandStr, workingDir, traceOptions);
|
|
35
|
-
|
|
63
|
+
// A crypto library is a loaded library too, even when library tracing
|
|
64
|
+
// missed it.
|
|
65
|
+
const cryptoLibraries = cryptoLibrariesByPath(result.cryptoEntries);
|
|
66
|
+
const libPaths = Array.from(
|
|
67
|
+
new Set([...(result.libPaths || []), ...cryptoLibraries.keys()]),
|
|
68
|
+
);
|
|
36
69
|
const httpAccessEntries = result.httpAccessEntries || [];
|
|
70
|
+
const egressEntries = result.egressEntries || [];
|
|
37
71
|
const cryptoComponents = result.cryptoComponents || [];
|
|
38
72
|
const components = [];
|
|
39
73
|
|
|
@@ -49,7 +83,10 @@ export async function buildDynamicComponents(
|
|
|
49
83
|
}
|
|
50
84
|
|
|
51
85
|
const pkgInfo = resolvePackageForFile(libPath);
|
|
52
|
-
|
|
86
|
+
const cryptoLibrary = cryptoLibraries.get(libPath);
|
|
87
|
+
// The subpath uses forward slashes, so this also names a Windows DLL
|
|
88
|
+
// correctly when the BOM is built on another host.
|
|
89
|
+
let name = basename(librarySubpath(libPath) || libPath);
|
|
53
90
|
let version = "";
|
|
54
91
|
let purlStr = "";
|
|
55
92
|
let confidence = 0.5;
|
|
@@ -70,12 +107,16 @@ export async function buildDynamicComponents(
|
|
|
70
107
|
qualifiers: pkgInfo.arch ? { arch: pkgInfo.arch } : null,
|
|
71
108
|
});
|
|
72
109
|
} else {
|
|
110
|
+
// A crypto library's version is its file version (Windows reports one
|
|
111
|
+
// for each DLL it classifies).
|
|
112
|
+
version = cryptoLibrary?.version || "";
|
|
73
113
|
// Fall back to a generic purl carrying the file path as a subpath. It was
|
|
74
114
|
// previously a `path` qualifier, which generic does not permit.
|
|
75
115
|
purlStr = tryBuildPurl({
|
|
76
116
|
type: "generic",
|
|
77
117
|
name: name,
|
|
78
|
-
|
|
118
|
+
version: version || null,
|
|
119
|
+
subpath: librarySubpath(libPath),
|
|
79
120
|
});
|
|
80
121
|
}
|
|
81
122
|
|
|
@@ -116,6 +157,12 @@ export async function buildDynamicComponents(
|
|
|
116
157
|
if (version) {
|
|
117
158
|
component.version = version;
|
|
118
159
|
}
|
|
160
|
+
if (cryptoLibrary) {
|
|
161
|
+
component.properties.push({
|
|
162
|
+
name: "cdx:dynamic:cryptoLibrary",
|
|
163
|
+
value: cryptoLibrary.name,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
119
166
|
// applyPurl sets a unique fallback bom-ref when no valid purl exists, so a
|
|
120
167
|
// library that cannot be expressed as a purl still participates correctly in
|
|
121
168
|
// the dependency graph.
|
|
@@ -125,7 +172,7 @@ export async function buildDynamicComponents(
|
|
|
125
172
|
}
|
|
126
173
|
|
|
127
174
|
// Append cryptographic assets
|
|
128
|
-
for (const comp of cryptoComponents) {
|
|
175
|
+
for (const comp of normalizeTracedCryptoComponents(cryptoComponents)) {
|
|
129
176
|
components.push(comp);
|
|
130
177
|
}
|
|
131
178
|
|
|
@@ -135,11 +182,48 @@ export async function buildDynamicComponents(
|
|
|
135
182
|
const entry = servicesMap[serviceName];
|
|
136
183
|
return {
|
|
137
184
|
name: serviceName,
|
|
138
|
-
|
|
185
|
+
"bom-ref": `urn:service:dynamic:${serviceName}`,
|
|
139
186
|
endpoints: Array.from(entry.endpoints).sort(),
|
|
140
187
|
properties: entry.properties,
|
|
141
188
|
};
|
|
142
189
|
});
|
|
143
190
|
|
|
144
|
-
|
|
191
|
+
// Egress seen by the safer-exec proxy: hostnames the command connected to
|
|
192
|
+
// (allowed) or tried to reach (denied). This works on every platform,
|
|
193
|
+
// including macOS where eBPF URL tracing is unavailable.
|
|
194
|
+
const servicesByName = new Map(services.map((svc) => [svc.name, svc]));
|
|
195
|
+
for (const egress of egressEntries) {
|
|
196
|
+
const name = `dynamic-${egress.host}-${egress.port}`;
|
|
197
|
+
const defaultPort = egress.protocol === "http" ? 80 : 443;
|
|
198
|
+
const endpoint = `${egress.protocol}://${egress.host}${egress.port !== defaultPort ? `:${egress.port}` : ""}`;
|
|
199
|
+
let svc = servicesByName.get(name);
|
|
200
|
+
if (!svc) {
|
|
201
|
+
svc = {
|
|
202
|
+
name,
|
|
203
|
+
"bom-ref": `urn:service:dynamic:${name}`,
|
|
204
|
+
endpoints: [],
|
|
205
|
+
properties: [],
|
|
206
|
+
};
|
|
207
|
+
servicesByName.set(name, svc);
|
|
208
|
+
services.push(svc);
|
|
209
|
+
}
|
|
210
|
+
if (!svc.endpoints.some((e) => e.startsWith(endpoint))) {
|
|
211
|
+
svc.endpoints.push(endpoint);
|
|
212
|
+
svc.endpoints.sort();
|
|
213
|
+
}
|
|
214
|
+
if (
|
|
215
|
+
!svc.properties.some(
|
|
216
|
+
(p) =>
|
|
217
|
+
p.name === "cdx:dynamic:egressDecision" &&
|
|
218
|
+
p.value === egress.decision,
|
|
219
|
+
)
|
|
220
|
+
) {
|
|
221
|
+
svc.properties.push({
|
|
222
|
+
name: "cdx:dynamic:egressDecision",
|
|
223
|
+
value: egress.decision,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return { components, services, dryRunSummary: result.dryRunSummary };
|
|
145
229
|
}
|