@cdxgen/cdxgen 13.4.0 → 13.6.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 +9 -9
- package/bin/cdxgen-secure.js +15 -0
- package/bin/cdxgen.js +29 -5
- package/bin/evinse.js +8 -0
- package/bin/hbom.js +15 -7
- package/bin/repl.js +29 -0
- package/bin/validate.js +6 -0
- package/data/cbomosdb-queries.json +13 -1
- package/data/component-tags.json +8 -0
- package/data/license-aliases.json +24 -2
- package/data/queries-darwin.json +25 -1
- package/data/queries-win.json +19 -1
- package/data/queries.json +41 -0
- package/data/remediations.json +407 -1
- package/data/rules/ai-bom.yaml +1 -1
- package/data/rules/brotli-decoder.yaml +107 -0
- package/data/rules/cbom-security.yaml +235 -4
- 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/vscode-extensions.yaml +75 -0
- package/data/spdx-license-list.json +121 -7
- package/data/spdx-licenses.json +69 -0
- package/data/spdx.schema.json +845 -2
- package/lib/audit/index.js +14 -0
- package/lib/cli/bomAssembly.js +120 -6
- package/lib/cli/cliOptions.js +110 -3
- package/lib/cli/index.js +182 -6
- package/lib/cli/jsBom.js +153 -64
- package/lib/cli/jvmBom.js +363 -66
- package/lib/cli/managedBom.js +853 -87
- package/lib/cli/nativeBom.js +267 -33
- package/lib/core/boundedFile.js +344 -0
- package/lib/core/brotliProbe.js +182 -0
- package/lib/core/env.js +38 -8
- package/lib/core/fs.js +40 -19
- package/lib/core/httpClient.js +172 -46
- package/lib/core/lockPaths.js +38 -0
- package/lib/core/paths.js +24 -1
- package/lib/core/processTree.js +5 -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/toolRegistry.js +170 -0
- package/lib/ecosystems/cppEvidence.js +263 -106
- package/lib/ecosystems/cppIncludeResolver.js +16 -5
- package/lib/ecosystems/ecosystems.js +25 -10
- package/lib/ecosystems/gemHostProbe.js +301 -0
- package/lib/ecosystems/golem.js +550 -6
- package/lib/ecosystems/gradleutils.js +858 -244
- package/lib/ecosystems/parsers-bazel.js +107 -6
- package/lib/ecosystems/parsers-dotnet.js +117 -18
- package/lib/ecosystems/parsers-go.js +1090 -367
- package/lib/ecosystems/parsers-js.js +505 -22
- package/lib/ecosystems/parsers-jvm.js +259 -17
- package/lib/ecosystems/parsers-misc.js +438 -64
- package/lib/ecosystems/parsers-python.js +100 -12
- package/lib/ecosystems/pythonutils.js +128 -2
- package/lib/ecosystems/rubyutils.js +544 -34
- package/lib/ecosystems/rusi.js +10 -0
- package/lib/ecosystems/scalacliutils.js +4 -1
- package/lib/ecosystems/scalasem.js +798 -151
- package/lib/ecosystems/vendoredCode.js +237 -3
- package/lib/ecosystems/venvProbe.js +360 -0
- package/lib/evinser/evinser.js +472 -61
- package/lib/evinser/swiftsem.js +68 -5
- package/lib/helpers/agenticToolutils.js +413 -93
- package/lib/helpers/vsixutils.js +1574 -315
- package/lib/inventory/agentSkillSpec.js +17 -0
- package/lib/inventory/apkInstalledDb.js +243 -0
- package/lib/inventory/atomUtils.js +110 -0
- package/lib/inventory/auditCategories.js +3 -3
- package/lib/inventory/brotliDecoder.js +142 -0
- package/lib/inventory/buildToolRateLimit.js +1 -1
- package/lib/inventory/cacheDir.js +52 -9
- package/lib/inventory/cbomutils.js +56 -7
- package/lib/inventory/certificates.js +2371 -0
- package/lib/inventory/communityAiConfigParser.js +4 -9
- package/lib/inventory/deps.js +41 -4
- package/lib/inventory/display.js +200 -3
- package/lib/inventory/dosai.js +639 -63
- package/lib/inventory/dotnetRuntime.js +217 -0
- package/lib/inventory/dynamic.js +45 -3
- package/lib/inventory/envcontext.js +475 -18
- package/lib/inventory/evidenceUtils.js +447 -23
- package/lib/inventory/fetchBatch.js +1 -1
- package/lib/inventory/formulationParsers.js +32 -3
- package/lib/inventory/goBuildInfo.js +140 -0
- package/lib/inventory/gtfobins.js +13 -0
- package/lib/inventory/hbom.js +67 -7
- package/lib/inventory/hbomAnalysis.js +16 -0
- package/lib/inventory/hostRuntimeProbe.js +686 -0
- package/lib/inventory/hostTopology.js +21 -0
- package/lib/inventory/jvmLocalRepos.js +9 -2
- package/lib/inventory/jvmToolEnv.js +29 -0
- package/lib/inventory/licenseEnhancer.js +12 -0
- package/lib/inventory/mavenCentralMirror.js +534 -0
- package/lib/inventory/nodeBuildInfo.js +218 -0
- package/lib/inventory/nodeHostProbe.js +202 -0
- package/lib/inventory/osPackagePurl.js +244 -0
- package/lib/inventory/osPackageResolver.js +271 -87
- package/lib/inventory/osinfo.js +79 -31
- package/lib/inventory/osqueryTransform.js +114 -5
- package/lib/inventory/phpBuildInfo.js +438 -0
- package/lib/inventory/phpHostProbe.js +185 -0
- package/lib/inventory/rootfsCerts.js +426 -0
- package/lib/inventory/rubyBuildInfo.js +972 -0
- package/lib/inventory/rubyHostProbe.js +218 -0
- package/lib/inventory/scalaCoords.js +4 -3
- package/lib/inventory/scalaDedupe.js +440 -0
- package/lib/inventory/source.js +256 -5
- package/lib/inventory/spdx.js +144 -0
- package/lib/inventory/traceRunner.js +5 -1
- package/lib/inventory/tracedCrypto.js +129 -0
- package/lib/managers/binary.js +866 -186
- package/lib/managers/docker.js +245 -12
- package/lib/parsers/email.js +277 -0
- package/lib/parsers/largeJson.js +62 -13
- package/lib/parsers/vscodeExtensionId.js +183 -0
- package/lib/server/server.js +198 -2
- package/lib/stages/postgen/annotator.js +17 -1
- package/lib/stages/postgen/introspection/reflect.js +4 -1
- package/lib/stages/postgen/postgen.js +5 -5
- package/lib/stages/postgen/ruleEngine.js +71 -6
- package/lib/stages/postgen/specVersionCompat.js +30 -0
- package/lib/stages/pregen/envAudit.js +1 -0
- package/lib/stages/pregen/pregen.js +231 -62
- package/lib/validator/bomValidator.js +32 -6
- package/lib/validator/generated/runtime.mjs +13 -0
- package/lib/validator/generated/validate-1.6.mjs +27 -8
- package/lib/validator/generated/validate-1.7.mjs +32 -13
- package/lib/validator/generated/validate-2.0.mjs +31 -12
- package/lib/validator/index.js +1 -1
- package/package.json +19 -18
- 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 +22 -3
- 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 +1 -17
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +20 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/boundedFile.d.ts +70 -0
- package/types/lib/core/boundedFile.d.ts.map +1 -0
- package/types/lib/core/brotliProbe.d.ts +37 -0
- package/types/lib/core/brotliProbe.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +22 -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 +24 -0
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/lockPaths.d.ts +19 -0
- package/types/lib/core/lockPaths.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +2 -0
- package/types/lib/core/processTree.d.ts.map +1 -1
- 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/toolRegistry.d.ts +36 -0
- package/types/lib/core/toolRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
- package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
- 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 +57 -21
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +40 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +85 -13
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +17 -20
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +28 -2
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +37 -58
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +14 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +19 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +144 -12
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
- package/types/lib/ecosystems/venvProbe.d.ts +40 -0
- package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +88 -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/inventory/agentSkillSpec.d.ts +13 -0
- package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
- 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 +33 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/brotliDecoder.d.ts +58 -0
- package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
- package/types/lib/inventory/cacheDir.d.ts +12 -4
- package/types/lib/inventory/cacheDir.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts +17 -0
- package/types/lib/inventory/cbomutils.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/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/deps.d.ts +2 -1
- 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 +162 -7
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dotnetRuntime.d.ts +44 -0
- package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
- package/types/lib/inventory/dynamic.d.ts +6 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts +92 -8
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +29 -10
- package/types/lib/inventory/evidenceUtils.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/gtfobins.d.ts.map +1 -1
- 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/hostRuntimeProbe.d.ts +44 -0
- package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
- package/types/lib/inventory/hostTopology.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
- package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
- package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
- package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
- 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 +44 -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/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/rubyBuildInfo.d.ts +271 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
- package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +38 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +15 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +4 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +22 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -1
- 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 +24 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- 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 +17 -3
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- 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/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/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +26 -9
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
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,
|
|
@@ -23,9 +23,11 @@ import {
|
|
|
23
23
|
TIMEOUT_MS,
|
|
24
24
|
} from "../core/fs.js";
|
|
25
25
|
import { formatDuration, reapProcessTree } from "../core/processTree.js";
|
|
26
|
+
import { encodeUriReference } from "../core/propertySanitizer.js";
|
|
26
27
|
import {
|
|
27
28
|
indexJsonObject,
|
|
28
29
|
MAX_JSON_TEXT_BYTES,
|
|
30
|
+
readJsonMember,
|
|
29
31
|
readJsonRange,
|
|
30
32
|
} from "../parsers/largeJson.js";
|
|
31
33
|
import {
|
|
@@ -154,11 +156,7 @@ function sanitizeEndpoint(endpoint) {
|
|
|
154
156
|
// CycloneDX types `services[].endpoints[]` as an iri-reference, where square
|
|
155
157
|
// brackets are reserved for IPv6 literals and braces are excluded outright,
|
|
156
158
|
// so the template is percent-encoded before it reaches the BOM.
|
|
157
|
-
|
|
158
|
-
/[[\]{}]/g,
|
|
159
|
-
(character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`,
|
|
160
|
-
);
|
|
161
|
-
return encodedPath;
|
|
159
|
+
return encodeUriReference(routePath);
|
|
162
160
|
}
|
|
163
161
|
|
|
164
162
|
function serviceNameFromEndpoint(endpoint) {
|
|
@@ -169,15 +167,67 @@ function serviceNameFromEndpoint(endpoint) {
|
|
|
169
167
|
.replace(/-+/g, "-");
|
|
170
168
|
}
|
|
171
169
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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.`;
|
|
181
231
|
}
|
|
182
232
|
|
|
183
233
|
function safeDosaiPath(value) {
|
|
@@ -198,6 +248,16 @@ function safeDosaiPatternPacks(value) {
|
|
|
198
248
|
.join(delimiter);
|
|
199
249
|
}
|
|
200
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
|
+
|
|
201
261
|
const MAX_DOSAI_EXCLUDES = 256;
|
|
202
262
|
const MAX_BRACE_RANGE = 64;
|
|
203
263
|
const GLOB_ESCAPABLE = "*?[]{}()!+@,";
|
|
@@ -209,7 +269,10 @@ const SAFE_DOSAI_GLOB = /^[\p{L}\p{N} _.+@#%~=/*?-]+$/u;
|
|
|
209
269
|
// letters and "Program Files (x86)". Shell metacharacters are not allowed.
|
|
210
270
|
const SAFE_DOSAI_EXECUTABLE = /^[\p{L}\p{N} _.,+@#%~=/\\:()-]+$/u;
|
|
211
271
|
const warnedDosaiMessages = new Set();
|
|
212
|
-
|
|
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();
|
|
213
276
|
|
|
214
277
|
function warnDosaiOnce(message) {
|
|
215
278
|
if (!warnedDosaiMessages.has(message)) {
|
|
@@ -496,9 +559,25 @@ function dosaiExcludeArgs(excludes, srcPath) {
|
|
|
496
559
|
return patterns.flatMap((pattern) => ["--exclude", pattern]);
|
|
497
560
|
}
|
|
498
561
|
|
|
499
|
-
|
|
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) {
|
|
500
574
|
const output = `${result?.stdout || ""}${result?.stderr || ""}`;
|
|
501
|
-
|
|
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;
|
|
502
581
|
}
|
|
503
582
|
|
|
504
583
|
function safeDosaiExecutable(value) {
|
|
@@ -539,9 +618,10 @@ export function readDosaiJsonFile(jsonFile) {
|
|
|
539
618
|
}
|
|
540
619
|
}
|
|
541
620
|
|
|
542
|
-
// The sections of a dosai methods report that cdxgen reads.
|
|
543
|
-
//
|
|
544
|
-
//
|
|
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.
|
|
545
625
|
const DOSAI_METHODS_SECTIONS = [
|
|
546
626
|
"Metadata",
|
|
547
627
|
"Dependencies",
|
|
@@ -554,6 +634,84 @@ const DOSAI_METHODS_SECTIONS = [
|
|
|
554
634
|
"AiComponents",
|
|
555
635
|
];
|
|
556
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
|
+
|
|
557
715
|
// The file each report returned by readDosaiReport came from, and whether it
|
|
558
716
|
// was trimmed to what cdxgen reads. persistDosaiSemanticsReport copies the
|
|
559
717
|
// file rather than re-serialising a trimmed or oversized report.
|
|
@@ -569,6 +727,105 @@ function referencedIds(entries, field) {
|
|
|
569
727
|
return ids;
|
|
570
728
|
}
|
|
571
729
|
|
|
730
|
+
// The fields of a MethodCalls row cdxgen reads: the component it names (Purl
|
|
731
|
+
// or Module), where the call site is (Path, FileName, LineNumber) and what it
|
|
732
|
+
// calls (ClassName, CalledMethod). Everything else a report row carries -
|
|
733
|
+
// arguments, evidence kinds, caller identities - no cdxgen consumer reads,
|
|
734
|
+
// and a report with millions of calls cannot afford to hold them.
|
|
735
|
+
const DOSAI_METHOD_CALL_FIELDS = [
|
|
736
|
+
"Purl",
|
|
737
|
+
"Module",
|
|
738
|
+
"Path",
|
|
739
|
+
"FileName",
|
|
740
|
+
"LineNumber",
|
|
741
|
+
"ClassName",
|
|
742
|
+
"CalledMethod",
|
|
743
|
+
];
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* The largest number of distinct method calls cdxgen keeps from one report.
|
|
747
|
+
*
|
|
748
|
+
* Reports in the millions of calls repeat the same call sites heavily, so the
|
|
749
|
+
* aggregated rows stay far below this; the cap exists so a report whose calls
|
|
750
|
+
* are all distinct degrades the occurrence evidence instead of the process.
|
|
751
|
+
*
|
|
752
|
+
* @returns {number} Cap on distinct kept rows
|
|
753
|
+
*/
|
|
754
|
+
export function maxDosaiMethodCalls() {
|
|
755
|
+
const configured = Number.parseInt(
|
|
756
|
+
readEnvironmentVariable("CDXGEN_DOSAI_MAX_METHOD_CALLS") || "",
|
|
757
|
+
10,
|
|
758
|
+
);
|
|
759
|
+
if (Number.isInteger(configured) && configured > 0) {
|
|
760
|
+
return configured;
|
|
761
|
+
}
|
|
762
|
+
return 1_000_000;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Build the `map` that turns report MethodCalls rows into what cdxgen keeps:
|
|
767
|
+
* rows the caller rejects are dropped, the rest are reduced to
|
|
768
|
+
* {@link DOSAI_METHOD_CALL_FIELDS} and aggregated by those fields, so a call
|
|
769
|
+
* site reached a million times is held once with a `CallCount`. Past
|
|
770
|
+
* {@link maxDosaiMethodCalls} distinct rows, further rows are dropped and one
|
|
771
|
+
* warning names the cap.
|
|
772
|
+
*
|
|
773
|
+
* @param {string} reportFile Report the rows come from, named in the warning
|
|
774
|
+
* @param {Object} [options] Options
|
|
775
|
+
* @param {Function} [options.keepMethodCall] Predicate choosing the rows to keep
|
|
776
|
+
* @param {number} [options.maxMethodCalls] Cap override, for tests
|
|
777
|
+
* @returns {{map: Function, finish: Function}} The row mapper, and the
|
|
778
|
+
* `finish` that reports a cap reached
|
|
779
|
+
*/
|
|
780
|
+
function dosaiMethodCallCollector(reportFile, options = {}) {
|
|
781
|
+
const keep = options.keepMethodCall;
|
|
782
|
+
const max = options.maxMethodCalls ?? maxDosaiMethodCalls();
|
|
783
|
+
const keptByKey = new Map();
|
|
784
|
+
let dropped = 0;
|
|
785
|
+
return {
|
|
786
|
+
map(methodCall) {
|
|
787
|
+
if (keep && !keep(methodCall)) {
|
|
788
|
+
return undefined;
|
|
789
|
+
}
|
|
790
|
+
const compact = {};
|
|
791
|
+
for (const field of DOSAI_METHOD_CALL_FIELDS) {
|
|
792
|
+
if (methodCall?.[field] !== undefined) {
|
|
793
|
+
compact[field] = methodCall[field];
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
const key = DOSAI_METHOD_CALL_FIELDS.map((field) => compact[field]).join(
|
|
797
|
+
"\u0000",
|
|
798
|
+
);
|
|
799
|
+
const kept = keptByKey.get(key);
|
|
800
|
+
if (kept) {
|
|
801
|
+
kept.CallCount = (kept.CallCount ?? 1) + 1;
|
|
802
|
+
return undefined;
|
|
803
|
+
}
|
|
804
|
+
if (keptByKey.size >= max) {
|
|
805
|
+
dropped++;
|
|
806
|
+
return undefined;
|
|
807
|
+
}
|
|
808
|
+
keptByKey.set(key, compact);
|
|
809
|
+
return compact;
|
|
810
|
+
},
|
|
811
|
+
finish() {
|
|
812
|
+
if (!dropped) {
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
console.warn(
|
|
816
|
+
`The dosai report "${reportFile}" holds more distinct method calls than cdxgen keeps (${max}); ${dropped} call sites were dropped, so occurrence evidence may be incomplete. Raise the cap with CDXGEN_DOSAI_MAX_METHOD_CALLS.`,
|
|
817
|
+
);
|
|
818
|
+
recordDegradation("dosai.method-calls-capped", {
|
|
819
|
+
ecosystem: "csharp",
|
|
820
|
+
tool: "dosai",
|
|
821
|
+
impact: "components",
|
|
822
|
+
command: reportFile,
|
|
823
|
+
detail: `The dosai report holds more distinct method calls than the cap of ${max}, so ${dropped} call sites were dropped from the occurrence evidence.`,
|
|
824
|
+
});
|
|
825
|
+
},
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
|
|
572
829
|
function readDosaiReport(reportFile, options, readTrimmed) {
|
|
573
830
|
if (!reportFile || !safeExistsSync(reportFile)) {
|
|
574
831
|
return undefined;
|
|
@@ -603,7 +860,13 @@ function readDosaiReport(reportFile, options, readTrimmed) {
|
|
|
603
860
|
}
|
|
604
861
|
return report;
|
|
605
862
|
} catch (err) {
|
|
606
|
-
if (
|
|
863
|
+
if (err instanceof RangeError) {
|
|
864
|
+
// A heap or string limit is a report-size problem the user can act on,
|
|
865
|
+
// unlike a malformed report, so it is named even without debug output.
|
|
866
|
+
console.warn(
|
|
867
|
+
`The dosai report "${reportFile}" is too large to read: ${err.message}. Raise the Node.js heap, for example NODE_OPTIONS="--max-old-space-size=8192", or write the report with dosai's --sections so it holds less.`,
|
|
868
|
+
);
|
|
869
|
+
} else if (DEBUG_MODE) {
|
|
607
870
|
console.log(
|
|
608
871
|
`Unable to read the dosai report "${reportFile}": ${err.message}`,
|
|
609
872
|
);
|
|
@@ -618,25 +881,34 @@ function readDosaiReport(reportFile, options, readTrimmed) {
|
|
|
618
881
|
* A report that fits in one JavaScript string is parsed whole. A larger one is
|
|
619
882
|
* read in bounded runs, keeping the requested sections, the CallGraph nodes and
|
|
620
883
|
* edges that PackageReachability references, and the MethodCalls accepted by
|
|
621
|
-
* `keepMethodCall` (none when it is omitted).
|
|
884
|
+
* `keepMethodCall` (none when it is omitted). A kept MethodCalls row is
|
|
885
|
+
* reduced to the fields cdxgen reads and aggregated by them, so a call site
|
|
886
|
+
* reached many times is held once with a `CallCount`; distinct rows past
|
|
887
|
+
* {@link maxDosaiMethodCalls} are dropped with one warning. Nothing cdxgen
|
|
888
|
+
* reads is lost.
|
|
622
889
|
*
|
|
623
890
|
* @param {string} reportFile Path to the dosai methods JSON
|
|
624
891
|
* @param {Object} [options] Options
|
|
625
892
|
* @param {string[]} [options.sections] Sections to keep from a large report (default: every section cdxgen reads)
|
|
626
893
|
* @param {Function} [options.keepMethodCall] Predicate choosing the MethodCalls to keep from a large report
|
|
894
|
+
* @param {number} [options.maxMethodCalls] Cap on distinct kept MethodCalls, for tests
|
|
627
895
|
* @param {number} [options.maxTextBytes] Largest report parsed whole, for tests
|
|
628
896
|
* @returns {Object|undefined} Parsed report, or undefined when missing or invalid
|
|
629
897
|
*/
|
|
630
898
|
export function readDosaiMethodsReport(reportFile, options = {}) {
|
|
631
899
|
const sections = options.sections || DOSAI_METHODS_SECTIONS;
|
|
632
|
-
|
|
900
|
+
const collector =
|
|
901
|
+
sections.includes("MethodCalls") && options.keepMethodCall
|
|
902
|
+
? dosaiMethodCallCollector(reportFile, options)
|
|
903
|
+
: undefined;
|
|
904
|
+
const report = readDosaiReport(reportFile, options, (report, read) => {
|
|
633
905
|
for (const section of sections) {
|
|
634
906
|
if (section !== "CallGraph" && section !== "MethodCalls") {
|
|
635
907
|
read(section);
|
|
636
908
|
}
|
|
637
909
|
}
|
|
638
|
-
if (
|
|
639
|
-
read("MethodCalls", {
|
|
910
|
+
if (collector) {
|
|
911
|
+
read("MethodCalls", { map: collector.map });
|
|
640
912
|
}
|
|
641
913
|
if (sections.includes("CallGraph")) {
|
|
642
914
|
// CallGraph precedes PackageReachability in the file, which is why the
|
|
@@ -651,6 +923,26 @@ export function readDosaiMethodsReport(reportFile, options = {}) {
|
|
|
651
923
|
});
|
|
652
924
|
}
|
|
653
925
|
});
|
|
926
|
+
// A report parsed whole kept every row, as it always has: reduce them to
|
|
927
|
+
// the fields cdxgen reads and aggregate repeats, without the caller's
|
|
928
|
+
// predicate, which the whole parse has never applied. A report that took
|
|
929
|
+
// the trimmed path already went through the collector above.
|
|
930
|
+
if (report && !dosaiReportSources.get(report)?.trimmed) {
|
|
931
|
+
const wholeCollector = dosaiMethodCallCollector(reportFile, {
|
|
932
|
+
maxMethodCalls: options.maxMethodCalls,
|
|
933
|
+
});
|
|
934
|
+
const reduced = [];
|
|
935
|
+
for (const methodCall of report.MethodCalls || []) {
|
|
936
|
+
const kept = wholeCollector.map(methodCall);
|
|
937
|
+
if (kept !== undefined) {
|
|
938
|
+
reduced.push(kept);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
report.MethodCalls = reduced;
|
|
942
|
+
wholeCollector.finish();
|
|
943
|
+
}
|
|
944
|
+
collector?.finish();
|
|
945
|
+
return report;
|
|
654
946
|
}
|
|
655
947
|
|
|
656
948
|
/**
|
|
@@ -717,6 +1009,28 @@ export function dosaiRunStopReason(result) {
|
|
|
717
1009
|
// The output files of the dosai runs of this process that were stopped, so a
|
|
718
1010
|
// caller does not follow the stop warning with advice for another failure.
|
|
719
1011
|
const stoppedDosaiOutputs = new Set();
|
|
1012
|
+
// The output files of the dosai runs the .NET host refused to start; the
|
|
1013
|
+
// warning naming the missing runtime already says why they are absent.
|
|
1014
|
+
const unstartedDosaiOutputs = new Set();
|
|
1015
|
+
// The executable that wrote each output file of this process, and whether it
|
|
1016
|
+
// was the cdxgen-plugins-bin build rather than an override.
|
|
1017
|
+
const dosaiOutputProducers = new Map();
|
|
1018
|
+
// Executables the .NET host refused to start, once that has been reported to
|
|
1019
|
+
// the user, by the pregen check or by a dosai run.
|
|
1020
|
+
const dosaiLaunchFailuresReported = new Set();
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Note that the .NET host's refusal to start a dosai executable has been
|
|
1024
|
+
* reported, so dosai runs of the same executable do not repeat the warning.
|
|
1025
|
+
*
|
|
1026
|
+
* @param {string} executable dosai executable
|
|
1027
|
+
*/
|
|
1028
|
+
export function markDosaiLaunchFailureReported(executable) {
|
|
1029
|
+
const safeExecutable = safeDosaiExecutable(executable);
|
|
1030
|
+
if (safeExecutable) {
|
|
1031
|
+
dosaiLaunchFailuresReported.add(safeExecutable);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
720
1034
|
|
|
721
1035
|
/**
|
|
722
1036
|
* Whether the dosai run that should have written a file was stopped (by the
|
|
@@ -730,6 +1044,23 @@ export function dosaiRunWasStopped(outputFile) {
|
|
|
730
1044
|
return Boolean(outputPath && stoppedDosaiOutputs.has(outputPath));
|
|
731
1045
|
}
|
|
732
1046
|
|
|
1047
|
+
/**
|
|
1048
|
+
* Whether the dosai run that should have written a file failed in a way it has
|
|
1049
|
+
* already reported: it was stopped (see dosaiRunWasStopped), or the .NET host
|
|
1050
|
+
* refused to start dosai and the warning named the missing runtime.
|
|
1051
|
+
*
|
|
1052
|
+
* @param {string} outputFile Output file passed to the dosai run
|
|
1053
|
+
* @returns {boolean} true when the failure has been reported
|
|
1054
|
+
*/
|
|
1055
|
+
export function dosaiFailureReported(outputFile) {
|
|
1056
|
+
const outputPath = safeDosaiPath(outputFile);
|
|
1057
|
+
return Boolean(
|
|
1058
|
+
outputPath &&
|
|
1059
|
+
(stoppedDosaiOutputs.has(outputPath) ||
|
|
1060
|
+
unstartedDosaiOutputs.has(outputPath)),
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
733
1064
|
function dosaiStopMessage(reason, signal) {
|
|
734
1065
|
if (reason === "timeout") {
|
|
735
1066
|
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).`;
|
|
@@ -744,10 +1075,17 @@ function dosaiStopMessage(reason, signal) {
|
|
|
744
1075
|
* Run a dosai subcommand ("methods", "dataflows", or "crypto") against a source
|
|
745
1076
|
* tree and write its JSON output to the given file.
|
|
746
1077
|
*
|
|
1078
|
+
* Flags cdxgen adds for its own benefit that an older dosai may not know
|
|
1079
|
+
* (--exclude, --sections, --crypto-dataflows) are optional: when dosai reports
|
|
1080
|
+
* them as unknown, every rejected one is dropped in a single retry, later runs
|
|
1081
|
+
* of the same executable skip them up front, and dropping is reported once
|
|
1082
|
+
* (a warning for --exclude, whose loss changes results; a debug line for the
|
|
1083
|
+
* size-only flags). A run that rejects anything else has simply failed.
|
|
1084
|
+
*
|
|
747
1085
|
* @param {string} command Dosai subcommand to execute
|
|
748
1086
|
* @param {string} src Source directory to analyze
|
|
749
1087
|
* @param {string} outputFile Path where the dosai JSON output is written
|
|
750
|
-
* @param {Object} [options] Options carrying dosaiCommand, dataFlowPatterns, patternPacks,
|
|
1088
|
+
* @param {Object} [options] Options carrying dosaiCommand, dataFlowPatterns, patternPacks, exclude globs, or dosaiMethodsSections
|
|
751
1089
|
* @returns {boolean} True when the command succeeded and produced the output file, false otherwise
|
|
752
1090
|
*/
|
|
753
1091
|
export function runDosaiCommand(command, src, outputFile, options = {}) {
|
|
@@ -779,50 +1117,122 @@ export function runDosaiCommand(command, src, outputFile, options = {}) {
|
|
|
779
1117
|
} else if (command === "crypto") {
|
|
780
1118
|
args.push("--format", "dosai");
|
|
781
1119
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
1120
|
+
// Optional flag groups: the flag literal, the tokens to append while it is
|
|
1121
|
+
// active, and whether dropping it changes what dosai analyses (true, only
|
|
1122
|
+
// --exclude) or only the size of the output (false).
|
|
1123
|
+
const flagGroups = [];
|
|
1124
|
+
if (command === "crypto") {
|
|
1125
|
+
// cdxgen reads only Assets, Operations, and Materials from the crypto
|
|
1126
|
+
// report, so the data-flow graph it never reads is omitted.
|
|
1127
|
+
flagGroups.push({
|
|
1128
|
+
flag: "--crypto-dataflows",
|
|
1129
|
+
tokens: ["--crypto-dataflows", "none"],
|
|
1130
|
+
changesResults: false,
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
const sections =
|
|
1134
|
+
command === "methods"
|
|
1135
|
+
? safeDosaiSections(options.dosaiMethodsSections)
|
|
1136
|
+
: undefined;
|
|
1137
|
+
if (sections) {
|
|
1138
|
+
flagGroups.push({
|
|
1139
|
+
flag: "--sections",
|
|
1140
|
+
tokens: ["--sections", sections],
|
|
1141
|
+
changesResults: false,
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
const rejectedBefore = dosaiRejectedFlagMemo.get(executable);
|
|
1145
|
+
// An executable that rejected --exclude gets no exclude translation at all:
|
|
1146
|
+
// computing it would print warnings about patterns that are never passed.
|
|
1147
|
+
const excludeTokens =
|
|
1148
|
+
rejectedBefore?.has("--exclude") || !options.exclude
|
|
1149
|
+
? []
|
|
1150
|
+
: dosaiExcludeArgs(options.exclude, srcPath);
|
|
1151
|
+
if (excludeTokens.length) {
|
|
1152
|
+
flagGroups.push({
|
|
1153
|
+
flag: "--exclude",
|
|
1154
|
+
tokens: excludeTokens,
|
|
1155
|
+
changesResults: true,
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
const activeGroups = flagGroups.filter(
|
|
1159
|
+
(group) => !rejectedBefore?.has(group.flag),
|
|
1160
|
+
);
|
|
1161
|
+
const commandArgs = [
|
|
1162
|
+
...args,
|
|
1163
|
+
...activeGroups.flatMap((group) => group.tokens),
|
|
1164
|
+
];
|
|
785
1165
|
if (DEBUG_MODE) {
|
|
786
|
-
console.log("Executing", executable,
|
|
1166
|
+
console.log("Executing", executable, commandArgs.join(" "));
|
|
787
1167
|
}
|
|
788
1168
|
stoppedDosaiOutputs.delete(outputPath);
|
|
1169
|
+
unstartedDosaiOutputs.delete(outputPath);
|
|
1170
|
+
dosaiOutputProducers.delete(outputPath);
|
|
789
1171
|
let startedAt = Date.now();
|
|
790
|
-
let result = safeSpawnSync(executable,
|
|
1172
|
+
let result = safeSpawnSync(executable, commandArgs, {
|
|
791
1173
|
cwd: srcPath,
|
|
792
1174
|
shell: false,
|
|
793
1175
|
});
|
|
794
|
-
// Dosai releases that predate
|
|
795
|
-
//
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
)
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1176
|
+
// Dosai releases that predate an optional flag reject it. Rerun once without
|
|
1177
|
+
// every flag this run rejected, so a newer cdxgen flag never turns into a
|
|
1178
|
+
// missing slice on an older dosai.
|
|
1179
|
+
if (result?.status !== 0) {
|
|
1180
|
+
const rejected = dosaiRejectedFlags(result);
|
|
1181
|
+
if (
|
|
1182
|
+
rejected.size &&
|
|
1183
|
+
[...rejected].every((flag) =>
|
|
1184
|
+
activeGroups.some((group) => group.flag === flag),
|
|
1185
|
+
)
|
|
1186
|
+
) {
|
|
1187
|
+
const dropped = flagGroups.filter((group) => rejected.has(group.flag));
|
|
1188
|
+
const memo = dosaiRejectedFlagMemo.get(executable) ?? new Set();
|
|
1189
|
+
for (const group of dropped) {
|
|
1190
|
+
memo.add(group.flag);
|
|
1191
|
+
}
|
|
1192
|
+
dosaiRejectedFlagMemo.set(executable, memo);
|
|
1193
|
+
const droppedExclude = dropped.some((group) => group.changesResults);
|
|
1194
|
+
if (droppedExclude) {
|
|
1195
|
+
warnDosaiOnce(
|
|
1196
|
+
"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.",
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
if (DEBUG_MODE) {
|
|
1200
|
+
console.log(
|
|
1201
|
+
`Retrying without the optional dosai flag(s) it rejected: ${dropped.map((group) => group.flag).join(", ")}.`,
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
const retryArgs = [
|
|
1205
|
+
...args,
|
|
1206
|
+
...activeGroups
|
|
1207
|
+
.filter((group) => !rejected.has(group.flag))
|
|
1208
|
+
.flatMap((group) => group.tokens),
|
|
1209
|
+
];
|
|
1210
|
+
startedAt = Date.now();
|
|
1211
|
+
result = safeSpawnSync(executable, retryArgs, {
|
|
1212
|
+
cwd: srcPath,
|
|
1213
|
+
shell: false,
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
810
1216
|
}
|
|
811
|
-
|
|
1217
|
+
const dotnetRequirement = dosaiDotnetRequirement(result);
|
|
1218
|
+
if (dotnetRequirement) {
|
|
1219
|
+
const detail = dosaiDotnetRequirementDetail(dotnetRequirement);
|
|
812
1220
|
recordDegradation("dotnet.sdk.missing", {
|
|
813
1221
|
ecosystem: "csharp",
|
|
814
1222
|
tool: "dotnet",
|
|
815
1223
|
impact: "none",
|
|
816
1224
|
command: `${basename(executable)} ${args.join(" ")}`,
|
|
817
|
-
detail:
|
|
818
|
-
"dosai reported that no .NET SDK is installed, so the requested dosai analysis could not run.",
|
|
1225
|
+
detail: `${detail}, so the requested dosai analysis could not run.`,
|
|
819
1226
|
});
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
)
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1227
|
+
// Visible without debug output, and once per scan: every later run of
|
|
1228
|
+
// this executable fails the same way.
|
|
1229
|
+
if (!dosaiLaunchFailuresReported.has(executable)) {
|
|
1230
|
+
dosaiLaunchFailuresReported.add(executable);
|
|
1231
|
+
console.warn(
|
|
1232
|
+
`WARNING: ${detail}, so dosai could not run and the .NET evidence it provides (call graph, reachability, services, crypto) is missing. ${dosaiDotnetRequirementAdvice(dotnetRequirement)}`,
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
unstartedDosaiOutputs.add(outputPath);
|
|
826
1236
|
}
|
|
827
1237
|
const stopReason = dosaiRunStopReason(result);
|
|
828
1238
|
if (stopReason) {
|
|
@@ -850,6 +1260,10 @@ export function runDosaiCommand(command, src, outputFile, options = {}) {
|
|
|
850
1260
|
}
|
|
851
1261
|
return false;
|
|
852
1262
|
}
|
|
1263
|
+
dosaiOutputProducers.set(outputPath, {
|
|
1264
|
+
executable,
|
|
1265
|
+
bundled: !options.dosaiCommand && !readEnvironmentVariable("DOSAI_CMD"),
|
|
1266
|
+
});
|
|
853
1267
|
return true;
|
|
854
1268
|
}
|
|
855
1269
|
|
|
@@ -858,7 +1272,7 @@ export function runDosaiCommand(command, src, outputFile, options = {}) {
|
|
|
858
1272
|
*
|
|
859
1273
|
* @param {string} src Source directory to analyze
|
|
860
1274
|
* @param {string} outputFile Path where the methods slice JSON is written
|
|
861
|
-
* @param {Object} [options] Options forwarded to runDosaiCommand
|
|
1275
|
+
* @param {Object} [options] Options forwarded to runDosaiCommand; `dosaiMethodsSections` names the top-level sections to write (see dosaiMethodsSectionsArgument), omitted for the full report
|
|
862
1276
|
* @returns {boolean} True when the slice was produced successfully
|
|
863
1277
|
*/
|
|
864
1278
|
export function createDosaiMethodsSlice(src, outputFile, options = {}) {
|
|
@@ -911,6 +1325,141 @@ export function analyzeDosaiCrypto(src, options = {}) {
|
|
|
911
1325
|
}
|
|
912
1326
|
}
|
|
913
1327
|
|
|
1328
|
+
const DOSAI_PURL = "pkg:github/owasp-dep-scan/dosai";
|
|
1329
|
+
const DOSAI_VERSION_PATTERN = /^\d+(?:\.\d+){1,3}(?:-[0-9A-Za-z.-]+)?$/;
|
|
1330
|
+
// The version each dosai executable reported for itself, by executable.
|
|
1331
|
+
const dosaiExecutableVersions = new Map();
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* The version a dosai executable reports with `--version`, without its build
|
|
1335
|
+
* metadata: `5.0.0-rc.2+e42b205` reads as `5.0.0-rc.2`. Each executable is
|
|
1336
|
+
* asked once per process.
|
|
1337
|
+
*
|
|
1338
|
+
* @param {string} executable dosai executable
|
|
1339
|
+
* @returns {string|undefined} The version, or undefined when dosai reported none
|
|
1340
|
+
*/
|
|
1341
|
+
function dosaiExecutableVersion(executable) {
|
|
1342
|
+
const safeExecutable = safeDosaiExecutable(executable);
|
|
1343
|
+
if (!safeExecutable) {
|
|
1344
|
+
return undefined;
|
|
1345
|
+
}
|
|
1346
|
+
if (!dosaiExecutableVersions.has(safeExecutable)) {
|
|
1347
|
+
const result = safeSpawnSync(safeExecutable, ["--version"], {
|
|
1348
|
+
shell: false,
|
|
1349
|
+
});
|
|
1350
|
+
const reported =
|
|
1351
|
+
result?.status === 0 && !result.error
|
|
1352
|
+
? `${result.stdout || ""}`.trim().split(/\s+/)[0].split("+")[0]
|
|
1353
|
+
: "";
|
|
1354
|
+
dosaiExecutableVersions.set(
|
|
1355
|
+
safeExecutable,
|
|
1356
|
+
DOSAI_VERSION_PATTERN.test(reported) ? reported : undefined,
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1359
|
+
return dosaiExecutableVersions.get(safeExecutable);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
/**
|
|
1363
|
+
* The dosai version a report records about its producer. AnalyzerVersion is
|
|
1364
|
+
* the four-part assembly version, so `4.1.0.0` reads as `4.1.0`, and a
|
|
1365
|
+
* prerelease build reads as the release it precedes.
|
|
1366
|
+
*
|
|
1367
|
+
* @param {Object} [report] A dosai report
|
|
1368
|
+
* @returns {string|undefined} The version, or undefined when the report names no dosai version
|
|
1369
|
+
*/
|
|
1370
|
+
function dosaiReportVersion(report) {
|
|
1371
|
+
const metadata = report?.Metadata;
|
|
1372
|
+
if (`${metadata?.Tool || ""}`.toLowerCase() !== "dosai") {
|
|
1373
|
+
return undefined;
|
|
1374
|
+
}
|
|
1375
|
+
const match = `${metadata.AnalyzerVersion || ""}`.match(
|
|
1376
|
+
/^(\d+\.\d+\.\d+)(?:\.(\d+))?$/,
|
|
1377
|
+
);
|
|
1378
|
+
if (!match) {
|
|
1379
|
+
return undefined;
|
|
1380
|
+
}
|
|
1381
|
+
return match[2] && match[2] !== "0" ? match[0] : match[1];
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
function dosaiToolComponent(version, manifestComponent, bundled) {
|
|
1385
|
+
// The manifest component describes the bundled binary, hashes included
|
|
1386
|
+
if (bundled && version && manifestComponent?.version === version) {
|
|
1387
|
+
return structuredClone(manifestComponent);
|
|
1388
|
+
}
|
|
1389
|
+
const purl = version ? `${DOSAI_PURL}@${version}` : DOSAI_PURL;
|
|
1390
|
+
const component = { type: "application", name: "dosai" };
|
|
1391
|
+
if (version) {
|
|
1392
|
+
component.version = version;
|
|
1393
|
+
}
|
|
1394
|
+
if (manifestComponent?.description) {
|
|
1395
|
+
component.description = manifestComponent.description;
|
|
1396
|
+
}
|
|
1397
|
+
component.purl = purl;
|
|
1398
|
+
component["bom-ref"] = purl;
|
|
1399
|
+
for (const field of ["licenses", "externalReferences"]) {
|
|
1400
|
+
if (manifestComponent?.[field]) {
|
|
1401
|
+
component[field] = structuredClone(manifestComponent[field]);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
return component;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
/**
|
|
1408
|
+
* The tool component naming the dosai behind a methods report.
|
|
1409
|
+
*
|
|
1410
|
+
* A report that a dosai run of this process wrote names the executable that
|
|
1411
|
+
* wrote it, with the version that executable reports. A report reused from an
|
|
1412
|
+
* earlier run names the version recorded in its Metadata, or the configured
|
|
1413
|
+
* dosai's own version when that is a build of the same release. No component
|
|
1414
|
+
* is returned for a report that is missing or that no dosai wrote, such as
|
|
1415
|
+
* when the .NET host refused to start dosai.
|
|
1416
|
+
*
|
|
1417
|
+
* The cdxgen-plugins-bin manifest component is returned as-is only for its
|
|
1418
|
+
* own binary at its own version. Any other dosai gets a component built from
|
|
1419
|
+
* the manifest's project fields (description, licenses, references), without
|
|
1420
|
+
* the bundled binary's hashes or path.
|
|
1421
|
+
*
|
|
1422
|
+
* @param {string} reportFile Path of the dosai methods report
|
|
1423
|
+
* @param {Object} [report] The report as read, for its Metadata
|
|
1424
|
+
* @param {Object} [manifestComponent] dosai component of the cdxgen-plugins-bin manifest
|
|
1425
|
+
* @returns {Object[]} The dosai tool component, or an empty array
|
|
1426
|
+
*/
|
|
1427
|
+
export function dosaiToolComponents(reportFile, report, manifestComponent) {
|
|
1428
|
+
const reportPath = safeDosaiPath(reportFile);
|
|
1429
|
+
if (!reportPath) {
|
|
1430
|
+
return [];
|
|
1431
|
+
}
|
|
1432
|
+
const producer = dosaiOutputProducers.get(reportPath);
|
|
1433
|
+
if (producer) {
|
|
1434
|
+
return [
|
|
1435
|
+
dosaiToolComponent(
|
|
1436
|
+
dosaiExecutableVersion(producer.executable) ||
|
|
1437
|
+
dosaiReportVersion(report),
|
|
1438
|
+
manifestComponent,
|
|
1439
|
+
producer.bundled,
|
|
1440
|
+
),
|
|
1441
|
+
];
|
|
1442
|
+
}
|
|
1443
|
+
const recorded = dosaiReportVersion(report);
|
|
1444
|
+
if (!recorded) {
|
|
1445
|
+
return [];
|
|
1446
|
+
}
|
|
1447
|
+
const executable = dosaiBin();
|
|
1448
|
+
const configured = executable
|
|
1449
|
+
? dosaiExecutableVersion(executable)
|
|
1450
|
+
: undefined;
|
|
1451
|
+
if (configured?.split("-")[0] === recorded) {
|
|
1452
|
+
return [
|
|
1453
|
+
dosaiToolComponent(
|
|
1454
|
+
configured,
|
|
1455
|
+
manifestComponent,
|
|
1456
|
+
!readEnvironmentVariable("DOSAI_CMD"),
|
|
1457
|
+
),
|
|
1458
|
+
];
|
|
1459
|
+
}
|
|
1460
|
+
return [dosaiToolComponent(recorded, manifestComponent, false)];
|
|
1461
|
+
}
|
|
1462
|
+
|
|
914
1463
|
/**
|
|
915
1464
|
* Build the combined native dosai report object persisted for downstream tools.
|
|
916
1465
|
*
|
|
@@ -971,10 +1520,12 @@ function* combinedDosaiReportChunks(combined, parts) {
|
|
|
971
1520
|
* analyzeGolemProject on branch feat/rusi-persist-report): when a semantics-
|
|
972
1521
|
* slices path is provided, the FULL native report is written there and kept so
|
|
973
1522
|
* downstream tools (depscan) can consume the complete methods + data-flow
|
|
974
|
-
* facts that cdxgen only projects a subset of into the SBOM evidence.
|
|
975
|
-
*
|
|
976
|
-
*
|
|
977
|
-
*
|
|
1523
|
+
* facts that cdxgen only projects a subset of into the SBOM evidence. A
|
|
1524
|
+
* methods report the user supplied with --usages-slices-file is written as
|
|
1525
|
+
* given, with a recorded degradation when it was trimmed with --sections.
|
|
1526
|
+
* dotnet does not otherwise use the semantics slice (atom is never run for
|
|
1527
|
+
* dotnet), so the path is free to carry the combined dosai report. Returns the
|
|
1528
|
+
* resolved durable path when something was persisted, otherwise undefined.
|
|
978
1529
|
*/
|
|
979
1530
|
export function persistDosaiSemanticsReport(
|
|
980
1531
|
options,
|
|
@@ -996,6 +1547,19 @@ export function persistDosaiSemanticsReport(
|
|
|
996
1547
|
const combined = buildCombinedDosaiReport(methodsSlice, dataFlowSlice);
|
|
997
1548
|
const methodsSource = dosaiReportSources.get(methodsSlice);
|
|
998
1549
|
const dataFlowSource = dosaiReportSources.get(dataFlowSlice);
|
|
1550
|
+
// A methods report written with --sections is partial. Persist it as given
|
|
1551
|
+
// (a user-supplied usages slice is their choice), but say so, because the
|
|
1552
|
+
// semantics file is the source of truth for downstream tools.
|
|
1553
|
+
const omittedSections = dosaiOmittedSections(methodsSlice);
|
|
1554
|
+
if (omittedSections.length) {
|
|
1555
|
+
recordDegradation("dosai.semantics.trimmed-report", {
|
|
1556
|
+
ecosystem: "csharp",
|
|
1557
|
+
tool: "dosai",
|
|
1558
|
+
impact: "none",
|
|
1559
|
+
command: `persistDosaiSemanticsReport ${durablePath}`,
|
|
1560
|
+
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.`,
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
999
1563
|
try {
|
|
1000
1564
|
let text;
|
|
1001
1565
|
if (!methodsSource?.trimmed && !dataFlowSource?.trimmed) {
|
|
@@ -1779,6 +2343,12 @@ export function collectDosaiServicesFromMethods(
|
|
|
1779
2343
|
/**
|
|
1780
2344
|
* Normalize a services map into a sorted array of CycloneDX service objects.
|
|
1781
2345
|
*
|
|
2346
|
+
* This is where evinse's service maps — dosai, golem, kosi, scalasem, an
|
|
2347
|
+
* OpenAPI document, and the atom usage and type detectors — become
|
|
2348
|
+
* `services[]`, so it is also where every endpoint is percent-encoded into the
|
|
2349
|
+
* iri-reference CycloneDX requires. The collectors keep the readable template
|
|
2350
|
+
* (`/users/{id}`); the BOM gets `/users/%7Bid%7D`.
|
|
2351
|
+
*
|
|
1782
2352
|
* @param {Object} [servicesMap] Map of service name to service definition with Set-backed endpoints
|
|
1783
2353
|
* @returns {Object[]} Array of service objects with sorted endpoint arrays and properties
|
|
1784
2354
|
*/
|
|
@@ -1786,12 +2356,18 @@ export function normalizeDosaiServiceMap(servicesMap = {}) {
|
|
|
1786
2356
|
return Object.keys(servicesMap)
|
|
1787
2357
|
.map((serviceName) => {
|
|
1788
2358
|
const definition = servicesMap[serviceName];
|
|
2359
|
+
const endpoints = new Set();
|
|
2360
|
+
for (const endpoint of definition.endpoints || []) {
|
|
2361
|
+
if (typeof endpoint === "string" && endpoint) {
|
|
2362
|
+
endpoints.add(encodeUriReference(endpoint));
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
1789
2365
|
const service = {
|
|
1790
2366
|
name:
|
|
1791
2367
|
definition.name ||
|
|
1792
2368
|
serviceName ||
|
|
1793
2369
|
`dosai-${basename(serviceName)}-service`,
|
|
1794
|
-
endpoints: Array.from(
|
|
2370
|
+
endpoints: Array.from(endpoints).sort(),
|
|
1795
2371
|
authenticated: definition.authenticated,
|
|
1796
2372
|
// Accept both spellings. Every pre-4.0.0 producer — collectDosaiServicesFromMethods here, and
|
|
1797
2373
|
// the OpenAPI/Java/Python paths in evinser.js — sets the camelCase `xTrustBoundary`; only the
|