@cdxgen/cdxgen 13.4.0 → 13.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/bin/cdxgen-secure.js +15 -0
- package/bin/cdxgen.js +25 -5
- package/bin/evinse.js +5 -0
- package/bin/hbom.js +15 -7
- package/bin/repl.js +29 -0
- package/data/cbomosdb-queries.json +13 -1
- package/data/component-tags.json +8 -0
- package/data/queries-darwin.json +25 -1
- package/data/queries-win.json +19 -1
- package/data/queries.json +41 -0
- package/data/remediations.json +283 -1
- 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/lib/audit/index.js +14 -0
- package/lib/cli/bomAssembly.js +28 -3
- package/lib/cli/cliOptions.js +110 -3
- package/lib/cli/index.js +73 -4
- package/lib/cli/jvmBom.js +126 -20
- package/lib/cli/managedBom.js +711 -71
- package/lib/cli/nativeBom.js +225 -28
- package/lib/core/env.js +30 -8
- package/lib/core/fs.js +28 -19
- package/lib/core/httpClient.js +74 -45
- package/lib/core/projectConfig.js +1 -0
- package/lib/core/projectTooling.js +97 -0
- package/lib/core/propertySanitizer.js +72 -0
- package/lib/ecosystems/golem.js +550 -6
- package/lib/ecosystems/gradleutils.js +30 -20
- package/lib/ecosystems/parsers-dotnet.js +117 -18
- package/lib/ecosystems/parsers-go.js +1090 -367
- package/lib/ecosystems/parsers-jvm.js +259 -17
- package/lib/ecosystems/parsers-misc.js +229 -44
- package/lib/ecosystems/parsers-python.js +11 -10
- package/lib/ecosystems/rubyutils.js +6 -3
- package/lib/ecosystems/rusi.js +10 -0
- package/lib/ecosystems/scalacliutils.js +2 -1
- package/lib/ecosystems/scalasem.js +3 -1
- package/lib/evinser/evinser.js +126 -18
- package/lib/evinser/swiftsem.js +68 -5
- package/lib/inventory/agentSkillSpec.js +17 -0
- package/lib/inventory/apkInstalledDb.js +243 -0
- package/lib/inventory/atomUtils.js +33 -0
- package/lib/inventory/auditCategories.js +3 -3
- package/lib/inventory/cacheDir.js +52 -9
- package/lib/inventory/certificates.js +2371 -0
- package/lib/inventory/communityAiConfigParser.js +4 -9
- package/lib/inventory/deps.js +6 -4
- package/lib/inventory/display.js +200 -3
- package/lib/inventory/dosai.js +500 -58
- package/lib/inventory/dynamic.js +45 -3
- package/lib/inventory/envcontext.js +168 -5
- package/lib/inventory/evidenceUtils.js +436 -23
- package/lib/inventory/formulationParsers.js +3 -2
- package/lib/inventory/goBuildInfo.js +140 -0
- package/lib/inventory/hbom.js +67 -7
- package/lib/inventory/hbomAnalysis.js +16 -0
- package/lib/inventory/hostTopology.js +21 -0
- package/lib/inventory/osPackagePurl.js +244 -0
- package/lib/inventory/osPackageResolver.js +89 -87
- package/lib/inventory/osinfo.js +79 -31
- package/lib/inventory/osqueryTransform.js +114 -5
- package/lib/inventory/phpBuildInfo.js +487 -0
- package/lib/inventory/phpHostProbe.js +641 -0
- package/lib/inventory/rootfsCerts.js +426 -0
- package/lib/inventory/source.js +207 -3
- 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 +529 -183
- package/lib/managers/docker.js +166 -12
- package/lib/parsers/email.js +277 -0
- package/lib/parsers/largeJson.js +34 -2
- package/lib/server/server.js +186 -1
- 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 +58 -1
- package/lib/stages/postgen/specVersionCompat.js +30 -0
- package/lib/stages/pregen/pregen.js +117 -5
- package/lib/validator/bomValidator.js +26 -5
- package/lib/validator/generated/runtime.mjs +13 -0
- package/lib/validator/generated/validate-1.6.mjs +26 -7
- package/lib/validator/generated/validate-1.7.mjs +31 -12
- package/lib/validator/generated/validate-2.0.mjs +30 -11
- package/package.json +17 -17
- package/types/bin/cdxgen-secure.d.ts +3 -0
- package/types/bin/cdxgen-secure.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +9 -2
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +43 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/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/env.d.ts +18 -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.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/ecosystems/cppEvidence.d.ts +1 -1
- package/types/lib/ecosystems/golem.d.ts +81 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +10 -8
- package/types/lib/ecosystems/gradleutils.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-jvm.d.ts +28 -2
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +18 -58
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +4 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +25 -0
- 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 +14 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/cacheDir.d.ts +12 -4
- package/types/lib/inventory/cacheDir.d.ts.map +1 -1
- package/types/lib/inventory/certificates.d.ts +205 -0
- package/types/lib/inventory/certificates.d.ts.map +1 -0
- package/types/lib/inventory/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 +145 -6
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- 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 +40 -2
- 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/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
- package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
- package/types/lib/inventory/hostTopology.d.ts.map +1 -1
- package/types/lib/inventory/osPackagePurl.d.ts +99 -0
- package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +6 -2
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/osinfo.d.ts +31 -6
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/osqueryTransform.d.ts +51 -0
- package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
- package/types/lib/inventory/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/source.d.ts +28 -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 +12 -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 +13 -0
- 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/pregen.d.ts +14 -1
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
package/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.
|
|
@@ -717,6 +875,28 @@ export function dosaiRunStopReason(result) {
|
|
|
717
875
|
// The output files of the dosai runs of this process that were stopped, so a
|
|
718
876
|
// caller does not follow the stop warning with advice for another failure.
|
|
719
877
|
const stoppedDosaiOutputs = new Set();
|
|
878
|
+
// The output files of the dosai runs the .NET host refused to start; the
|
|
879
|
+
// warning naming the missing runtime already says why they are absent.
|
|
880
|
+
const unstartedDosaiOutputs = new Set();
|
|
881
|
+
// The executable that wrote each output file of this process, and whether it
|
|
882
|
+
// was the cdxgen-plugins-bin build rather than an override.
|
|
883
|
+
const dosaiOutputProducers = new Map();
|
|
884
|
+
// Executables the .NET host refused to start, once that has been reported to
|
|
885
|
+
// the user, by the pregen check or by a dosai run.
|
|
886
|
+
const dosaiLaunchFailuresReported = new Set();
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Note that the .NET host's refusal to start a dosai executable has been
|
|
890
|
+
* reported, so dosai runs of the same executable do not repeat the warning.
|
|
891
|
+
*
|
|
892
|
+
* @param {string} executable dosai executable
|
|
893
|
+
*/
|
|
894
|
+
export function markDosaiLaunchFailureReported(executable) {
|
|
895
|
+
const safeExecutable = safeDosaiExecutable(executable);
|
|
896
|
+
if (safeExecutable) {
|
|
897
|
+
dosaiLaunchFailuresReported.add(safeExecutable);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
720
900
|
|
|
721
901
|
/**
|
|
722
902
|
* Whether the dosai run that should have written a file was stopped (by the
|
|
@@ -730,6 +910,23 @@ export function dosaiRunWasStopped(outputFile) {
|
|
|
730
910
|
return Boolean(outputPath && stoppedDosaiOutputs.has(outputPath));
|
|
731
911
|
}
|
|
732
912
|
|
|
913
|
+
/**
|
|
914
|
+
* Whether the dosai run that should have written a file failed in a way it has
|
|
915
|
+
* already reported: it was stopped (see dosaiRunWasStopped), or the .NET host
|
|
916
|
+
* refused to start dosai and the warning named the missing runtime.
|
|
917
|
+
*
|
|
918
|
+
* @param {string} outputFile Output file passed to the dosai run
|
|
919
|
+
* @returns {boolean} true when the failure has been reported
|
|
920
|
+
*/
|
|
921
|
+
export function dosaiFailureReported(outputFile) {
|
|
922
|
+
const outputPath = safeDosaiPath(outputFile);
|
|
923
|
+
return Boolean(
|
|
924
|
+
outputPath &&
|
|
925
|
+
(stoppedDosaiOutputs.has(outputPath) ||
|
|
926
|
+
unstartedDosaiOutputs.has(outputPath)),
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
|
|
733
930
|
function dosaiStopMessage(reason, signal) {
|
|
734
931
|
if (reason === "timeout") {
|
|
735
932
|
return `WARN: dosai did not finish within ${formatDuration(TIMEOUT_MS)} and was stopped, so its slices are missing or incomplete. Allow more time with CDXGEN_TIMEOUT_MS (milliseconds, every external command).`;
|
|
@@ -744,10 +941,17 @@ function dosaiStopMessage(reason, signal) {
|
|
|
744
941
|
* Run a dosai subcommand ("methods", "dataflows", or "crypto") against a source
|
|
745
942
|
* tree and write its JSON output to the given file.
|
|
746
943
|
*
|
|
944
|
+
* Flags cdxgen adds for its own benefit that an older dosai may not know
|
|
945
|
+
* (--exclude, --sections, --crypto-dataflows) are optional: when dosai reports
|
|
946
|
+
* them as unknown, every rejected one is dropped in a single retry, later runs
|
|
947
|
+
* of the same executable skip them up front, and dropping is reported once
|
|
948
|
+
* (a warning for --exclude, whose loss changes results; a debug line for the
|
|
949
|
+
* size-only flags). A run that rejects anything else has simply failed.
|
|
950
|
+
*
|
|
747
951
|
* @param {string} command Dosai subcommand to execute
|
|
748
952
|
* @param {string} src Source directory to analyze
|
|
749
953
|
* @param {string} outputFile Path where the dosai JSON output is written
|
|
750
|
-
* @param {Object} [options] Options carrying dosaiCommand, dataFlowPatterns, patternPacks,
|
|
954
|
+
* @param {Object} [options] Options carrying dosaiCommand, dataFlowPatterns, patternPacks, exclude globs, or dosaiMethodsSections
|
|
751
955
|
* @returns {boolean} True when the command succeeded and produced the output file, false otherwise
|
|
752
956
|
*/
|
|
753
957
|
export function runDosaiCommand(command, src, outputFile, options = {}) {
|
|
@@ -779,50 +983,122 @@ export function runDosaiCommand(command, src, outputFile, options = {}) {
|
|
|
779
983
|
} else if (command === "crypto") {
|
|
780
984
|
args.push("--format", "dosai");
|
|
781
985
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
986
|
+
// Optional flag groups: the flag literal, the tokens to append while it is
|
|
987
|
+
// active, and whether dropping it changes what dosai analyses (true, only
|
|
988
|
+
// --exclude) or only the size of the output (false).
|
|
989
|
+
const flagGroups = [];
|
|
990
|
+
if (command === "crypto") {
|
|
991
|
+
// cdxgen reads only Assets, Operations, and Materials from the crypto
|
|
992
|
+
// report, so the data-flow graph it never reads is omitted.
|
|
993
|
+
flagGroups.push({
|
|
994
|
+
flag: "--crypto-dataflows",
|
|
995
|
+
tokens: ["--crypto-dataflows", "none"],
|
|
996
|
+
changesResults: false,
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
const sections =
|
|
1000
|
+
command === "methods"
|
|
1001
|
+
? safeDosaiSections(options.dosaiMethodsSections)
|
|
1002
|
+
: undefined;
|
|
1003
|
+
if (sections) {
|
|
1004
|
+
flagGroups.push({
|
|
1005
|
+
flag: "--sections",
|
|
1006
|
+
tokens: ["--sections", sections],
|
|
1007
|
+
changesResults: false,
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
const rejectedBefore = dosaiRejectedFlagMemo.get(executable);
|
|
1011
|
+
// An executable that rejected --exclude gets no exclude translation at all:
|
|
1012
|
+
// computing it would print warnings about patterns that are never passed.
|
|
1013
|
+
const excludeTokens =
|
|
1014
|
+
rejectedBefore?.has("--exclude") || !options.exclude
|
|
1015
|
+
? []
|
|
1016
|
+
: dosaiExcludeArgs(options.exclude, srcPath);
|
|
1017
|
+
if (excludeTokens.length) {
|
|
1018
|
+
flagGroups.push({
|
|
1019
|
+
flag: "--exclude",
|
|
1020
|
+
tokens: excludeTokens,
|
|
1021
|
+
changesResults: true,
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
const activeGroups = flagGroups.filter(
|
|
1025
|
+
(group) => !rejectedBefore?.has(group.flag),
|
|
1026
|
+
);
|
|
1027
|
+
const commandArgs = [
|
|
1028
|
+
...args,
|
|
1029
|
+
...activeGroups.flatMap((group) => group.tokens),
|
|
1030
|
+
];
|
|
785
1031
|
if (DEBUG_MODE) {
|
|
786
|
-
console.log("Executing", executable,
|
|
1032
|
+
console.log("Executing", executable, commandArgs.join(" "));
|
|
787
1033
|
}
|
|
788
1034
|
stoppedDosaiOutputs.delete(outputPath);
|
|
1035
|
+
unstartedDosaiOutputs.delete(outputPath);
|
|
1036
|
+
dosaiOutputProducers.delete(outputPath);
|
|
789
1037
|
let startedAt = Date.now();
|
|
790
|
-
let result = safeSpawnSync(executable,
|
|
1038
|
+
let result = safeSpawnSync(executable, commandArgs, {
|
|
791
1039
|
cwd: srcPath,
|
|
792
1040
|
shell: false,
|
|
793
1041
|
});
|
|
794
|
-
// Dosai releases that predate
|
|
795
|
-
//
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
)
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1042
|
+
// Dosai releases that predate an optional flag reject it. Rerun once without
|
|
1043
|
+
// every flag this run rejected, so a newer cdxgen flag never turns into a
|
|
1044
|
+
// missing slice on an older dosai.
|
|
1045
|
+
if (result?.status !== 0) {
|
|
1046
|
+
const rejected = dosaiRejectedFlags(result);
|
|
1047
|
+
if (
|
|
1048
|
+
rejected.size &&
|
|
1049
|
+
[...rejected].every((flag) =>
|
|
1050
|
+
activeGroups.some((group) => group.flag === flag),
|
|
1051
|
+
)
|
|
1052
|
+
) {
|
|
1053
|
+
const dropped = flagGroups.filter((group) => rejected.has(group.flag));
|
|
1054
|
+
const memo = dosaiRejectedFlagMemo.get(executable) ?? new Set();
|
|
1055
|
+
for (const group of dropped) {
|
|
1056
|
+
memo.add(group.flag);
|
|
1057
|
+
}
|
|
1058
|
+
dosaiRejectedFlagMemo.set(executable, memo);
|
|
1059
|
+
const droppedExclude = dropped.some((group) => group.changesResults);
|
|
1060
|
+
if (droppedExclude) {
|
|
1061
|
+
warnDosaiOnce(
|
|
1062
|
+
"This dosai build does not support --exclude, so it runs without the exclude patterns. Upgrade dosai or set DOSAI_CMD to a newer build to honour them.",
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
if (DEBUG_MODE) {
|
|
1066
|
+
console.log(
|
|
1067
|
+
`Retrying without the optional dosai flag(s) it rejected: ${dropped.map((group) => group.flag).join(", ")}.`,
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
const retryArgs = [
|
|
1071
|
+
...args,
|
|
1072
|
+
...activeGroups
|
|
1073
|
+
.filter((group) => !rejected.has(group.flag))
|
|
1074
|
+
.flatMap((group) => group.tokens),
|
|
1075
|
+
];
|
|
1076
|
+
startedAt = Date.now();
|
|
1077
|
+
result = safeSpawnSync(executable, retryArgs, {
|
|
1078
|
+
cwd: srcPath,
|
|
1079
|
+
shell: false,
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
810
1082
|
}
|
|
811
|
-
|
|
1083
|
+
const dotnetRequirement = dosaiDotnetRequirement(result);
|
|
1084
|
+
if (dotnetRequirement) {
|
|
1085
|
+
const detail = dosaiDotnetRequirementDetail(dotnetRequirement);
|
|
812
1086
|
recordDegradation("dotnet.sdk.missing", {
|
|
813
1087
|
ecosystem: "csharp",
|
|
814
1088
|
tool: "dotnet",
|
|
815
1089
|
impact: "none",
|
|
816
1090
|
command: `${basename(executable)} ${args.join(" ")}`,
|
|
817
|
-
detail:
|
|
818
|
-
"dosai reported that no .NET SDK is installed, so the requested dosai analysis could not run.",
|
|
1091
|
+
detail: `${detail}, so the requested dosai analysis could not run.`,
|
|
819
1092
|
});
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
)
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1093
|
+
// Visible without debug output, and once per scan: every later run of
|
|
1094
|
+
// this executable fails the same way.
|
|
1095
|
+
if (!dosaiLaunchFailuresReported.has(executable)) {
|
|
1096
|
+
dosaiLaunchFailuresReported.add(executable);
|
|
1097
|
+
console.warn(
|
|
1098
|
+
`WARNING: ${detail}, so dosai could not run and the .NET evidence it provides (call graph, reachability, services, crypto) is missing. ${dosaiDotnetRequirementAdvice(dotnetRequirement)}`,
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
unstartedDosaiOutputs.add(outputPath);
|
|
826
1102
|
}
|
|
827
1103
|
const stopReason = dosaiRunStopReason(result);
|
|
828
1104
|
if (stopReason) {
|
|
@@ -850,6 +1126,10 @@ export function runDosaiCommand(command, src, outputFile, options = {}) {
|
|
|
850
1126
|
}
|
|
851
1127
|
return false;
|
|
852
1128
|
}
|
|
1129
|
+
dosaiOutputProducers.set(outputPath, {
|
|
1130
|
+
executable,
|
|
1131
|
+
bundled: !options.dosaiCommand && !readEnvironmentVariable("DOSAI_CMD"),
|
|
1132
|
+
});
|
|
853
1133
|
return true;
|
|
854
1134
|
}
|
|
855
1135
|
|
|
@@ -858,7 +1138,7 @@ export function runDosaiCommand(command, src, outputFile, options = {}) {
|
|
|
858
1138
|
*
|
|
859
1139
|
* @param {string} src Source directory to analyze
|
|
860
1140
|
* @param {string} outputFile Path where the methods slice JSON is written
|
|
861
|
-
* @param {Object} [options] Options forwarded to runDosaiCommand
|
|
1141
|
+
* @param {Object} [options] Options forwarded to runDosaiCommand; `dosaiMethodsSections` names the top-level sections to write (see dosaiMethodsSectionsArgument), omitted for the full report
|
|
862
1142
|
* @returns {boolean} True when the slice was produced successfully
|
|
863
1143
|
*/
|
|
864
1144
|
export function createDosaiMethodsSlice(src, outputFile, options = {}) {
|
|
@@ -911,6 +1191,141 @@ export function analyzeDosaiCrypto(src, options = {}) {
|
|
|
911
1191
|
}
|
|
912
1192
|
}
|
|
913
1193
|
|
|
1194
|
+
const DOSAI_PURL = "pkg:github/owasp-dep-scan/dosai";
|
|
1195
|
+
const DOSAI_VERSION_PATTERN = /^\d+(?:\.\d+){1,3}(?:-[0-9A-Za-z.-]+)?$/;
|
|
1196
|
+
// The version each dosai executable reported for itself, by executable.
|
|
1197
|
+
const dosaiExecutableVersions = new Map();
|
|
1198
|
+
|
|
1199
|
+
/**
|
|
1200
|
+
* The version a dosai executable reports with `--version`, without its build
|
|
1201
|
+
* metadata: `5.0.0-rc.2+e42b205` reads as `5.0.0-rc.2`. Each executable is
|
|
1202
|
+
* asked once per process.
|
|
1203
|
+
*
|
|
1204
|
+
* @param {string} executable dosai executable
|
|
1205
|
+
* @returns {string|undefined} The version, or undefined when dosai reported none
|
|
1206
|
+
*/
|
|
1207
|
+
function dosaiExecutableVersion(executable) {
|
|
1208
|
+
const safeExecutable = safeDosaiExecutable(executable);
|
|
1209
|
+
if (!safeExecutable) {
|
|
1210
|
+
return undefined;
|
|
1211
|
+
}
|
|
1212
|
+
if (!dosaiExecutableVersions.has(safeExecutable)) {
|
|
1213
|
+
const result = safeSpawnSync(safeExecutable, ["--version"], {
|
|
1214
|
+
shell: false,
|
|
1215
|
+
});
|
|
1216
|
+
const reported =
|
|
1217
|
+
result?.status === 0 && !result.error
|
|
1218
|
+
? `${result.stdout || ""}`.trim().split(/\s+/)[0].split("+")[0]
|
|
1219
|
+
: "";
|
|
1220
|
+
dosaiExecutableVersions.set(
|
|
1221
|
+
safeExecutable,
|
|
1222
|
+
DOSAI_VERSION_PATTERN.test(reported) ? reported : undefined,
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
return dosaiExecutableVersions.get(safeExecutable);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* The dosai version a report records about its producer. AnalyzerVersion is
|
|
1230
|
+
* the four-part assembly version, so `4.1.0.0` reads as `4.1.0`, and a
|
|
1231
|
+
* prerelease build reads as the release it precedes.
|
|
1232
|
+
*
|
|
1233
|
+
* @param {Object} [report] A dosai report
|
|
1234
|
+
* @returns {string|undefined} The version, or undefined when the report names no dosai version
|
|
1235
|
+
*/
|
|
1236
|
+
function dosaiReportVersion(report) {
|
|
1237
|
+
const metadata = report?.Metadata;
|
|
1238
|
+
if (`${metadata?.Tool || ""}`.toLowerCase() !== "dosai") {
|
|
1239
|
+
return undefined;
|
|
1240
|
+
}
|
|
1241
|
+
const match = `${metadata.AnalyzerVersion || ""}`.match(
|
|
1242
|
+
/^(\d+\.\d+\.\d+)(?:\.(\d+))?$/,
|
|
1243
|
+
);
|
|
1244
|
+
if (!match) {
|
|
1245
|
+
return undefined;
|
|
1246
|
+
}
|
|
1247
|
+
return match[2] && match[2] !== "0" ? match[0] : match[1];
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function dosaiToolComponent(version, manifestComponent, bundled) {
|
|
1251
|
+
// The manifest component describes the bundled binary, hashes included
|
|
1252
|
+
if (bundled && version && manifestComponent?.version === version) {
|
|
1253
|
+
return structuredClone(manifestComponent);
|
|
1254
|
+
}
|
|
1255
|
+
const purl = version ? `${DOSAI_PURL}@${version}` : DOSAI_PURL;
|
|
1256
|
+
const component = { type: "application", name: "dosai" };
|
|
1257
|
+
if (version) {
|
|
1258
|
+
component.version = version;
|
|
1259
|
+
}
|
|
1260
|
+
if (manifestComponent?.description) {
|
|
1261
|
+
component.description = manifestComponent.description;
|
|
1262
|
+
}
|
|
1263
|
+
component.purl = purl;
|
|
1264
|
+
component["bom-ref"] = purl;
|
|
1265
|
+
for (const field of ["licenses", "externalReferences"]) {
|
|
1266
|
+
if (manifestComponent?.[field]) {
|
|
1267
|
+
component[field] = structuredClone(manifestComponent[field]);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
return component;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
/**
|
|
1274
|
+
* The tool component naming the dosai behind a methods report.
|
|
1275
|
+
*
|
|
1276
|
+
* A report that a dosai run of this process wrote names the executable that
|
|
1277
|
+
* wrote it, with the version that executable reports. A report reused from an
|
|
1278
|
+
* earlier run names the version recorded in its Metadata, or the configured
|
|
1279
|
+
* dosai's own version when that is a build of the same release. No component
|
|
1280
|
+
* is returned for a report that is missing or that no dosai wrote, such as
|
|
1281
|
+
* when the .NET host refused to start dosai.
|
|
1282
|
+
*
|
|
1283
|
+
* The cdxgen-plugins-bin manifest component is returned as-is only for its
|
|
1284
|
+
* own binary at its own version. Any other dosai gets a component built from
|
|
1285
|
+
* the manifest's project fields (description, licenses, references), without
|
|
1286
|
+
* the bundled binary's hashes or path.
|
|
1287
|
+
*
|
|
1288
|
+
* @param {string} reportFile Path of the dosai methods report
|
|
1289
|
+
* @param {Object} [report] The report as read, for its Metadata
|
|
1290
|
+
* @param {Object} [manifestComponent] dosai component of the cdxgen-plugins-bin manifest
|
|
1291
|
+
* @returns {Object[]} The dosai tool component, or an empty array
|
|
1292
|
+
*/
|
|
1293
|
+
export function dosaiToolComponents(reportFile, report, manifestComponent) {
|
|
1294
|
+
const reportPath = safeDosaiPath(reportFile);
|
|
1295
|
+
if (!reportPath) {
|
|
1296
|
+
return [];
|
|
1297
|
+
}
|
|
1298
|
+
const producer = dosaiOutputProducers.get(reportPath);
|
|
1299
|
+
if (producer) {
|
|
1300
|
+
return [
|
|
1301
|
+
dosaiToolComponent(
|
|
1302
|
+
dosaiExecutableVersion(producer.executable) ||
|
|
1303
|
+
dosaiReportVersion(report),
|
|
1304
|
+
manifestComponent,
|
|
1305
|
+
producer.bundled,
|
|
1306
|
+
),
|
|
1307
|
+
];
|
|
1308
|
+
}
|
|
1309
|
+
const recorded = dosaiReportVersion(report);
|
|
1310
|
+
if (!recorded) {
|
|
1311
|
+
return [];
|
|
1312
|
+
}
|
|
1313
|
+
const executable = dosaiBin();
|
|
1314
|
+
const configured = executable
|
|
1315
|
+
? dosaiExecutableVersion(executable)
|
|
1316
|
+
: undefined;
|
|
1317
|
+
if (configured?.split("-")[0] === recorded) {
|
|
1318
|
+
return [
|
|
1319
|
+
dosaiToolComponent(
|
|
1320
|
+
configured,
|
|
1321
|
+
manifestComponent,
|
|
1322
|
+
!readEnvironmentVariable("DOSAI_CMD"),
|
|
1323
|
+
),
|
|
1324
|
+
];
|
|
1325
|
+
}
|
|
1326
|
+
return [dosaiToolComponent(recorded, manifestComponent, false)];
|
|
1327
|
+
}
|
|
1328
|
+
|
|
914
1329
|
/**
|
|
915
1330
|
* Build the combined native dosai report object persisted for downstream tools.
|
|
916
1331
|
*
|
|
@@ -971,10 +1386,12 @@ function* combinedDosaiReportChunks(combined, parts) {
|
|
|
971
1386
|
* analyzeGolemProject on branch feat/rusi-persist-report): when a semantics-
|
|
972
1387
|
* slices path is provided, the FULL native report is written there and kept so
|
|
973
1388
|
* 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
|
-
*
|
|
1389
|
+
* facts that cdxgen only projects a subset of into the SBOM evidence. A
|
|
1390
|
+
* methods report the user supplied with --usages-slices-file is written as
|
|
1391
|
+
* given, with a recorded degradation when it was trimmed with --sections.
|
|
1392
|
+
* dotnet does not otherwise use the semantics slice (atom is never run for
|
|
1393
|
+
* dotnet), so the path is free to carry the combined dosai report. Returns the
|
|
1394
|
+
* resolved durable path when something was persisted, otherwise undefined.
|
|
978
1395
|
*/
|
|
979
1396
|
export function persistDosaiSemanticsReport(
|
|
980
1397
|
options,
|
|
@@ -996,6 +1413,19 @@ export function persistDosaiSemanticsReport(
|
|
|
996
1413
|
const combined = buildCombinedDosaiReport(methodsSlice, dataFlowSlice);
|
|
997
1414
|
const methodsSource = dosaiReportSources.get(methodsSlice);
|
|
998
1415
|
const dataFlowSource = dosaiReportSources.get(dataFlowSlice);
|
|
1416
|
+
// A methods report written with --sections is partial. Persist it as given
|
|
1417
|
+
// (a user-supplied usages slice is their choice), but say so, because the
|
|
1418
|
+
// semantics file is the source of truth for downstream tools.
|
|
1419
|
+
const omittedSections = dosaiOmittedSections(methodsSlice);
|
|
1420
|
+
if (omittedSections.length) {
|
|
1421
|
+
recordDegradation("dosai.semantics.trimmed-report", {
|
|
1422
|
+
ecosystem: "csharp",
|
|
1423
|
+
tool: "dosai",
|
|
1424
|
+
impact: "none",
|
|
1425
|
+
command: `persistDosaiSemanticsReport ${durablePath}`,
|
|
1426
|
+
detail: `The dosai methods report was written with --sections, so the persisted report omits: ${omittedSections.join(", ")}. Run dosai methods without --sections for the complete report.`,
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
999
1429
|
try {
|
|
1000
1430
|
let text;
|
|
1001
1431
|
if (!methodsSource?.trimmed && !dataFlowSource?.trimmed) {
|
|
@@ -1779,6 +2209,12 @@ export function collectDosaiServicesFromMethods(
|
|
|
1779
2209
|
/**
|
|
1780
2210
|
* Normalize a services map into a sorted array of CycloneDX service objects.
|
|
1781
2211
|
*
|
|
2212
|
+
* This is where evinse's service maps — dosai, golem, kosi, scalasem, an
|
|
2213
|
+
* OpenAPI document, and the atom usage and type detectors — become
|
|
2214
|
+
* `services[]`, so it is also where every endpoint is percent-encoded into the
|
|
2215
|
+
* iri-reference CycloneDX requires. The collectors keep the readable template
|
|
2216
|
+
* (`/users/{id}`); the BOM gets `/users/%7Bid%7D`.
|
|
2217
|
+
*
|
|
1782
2218
|
* @param {Object} [servicesMap] Map of service name to service definition with Set-backed endpoints
|
|
1783
2219
|
* @returns {Object[]} Array of service objects with sorted endpoint arrays and properties
|
|
1784
2220
|
*/
|
|
@@ -1786,12 +2222,18 @@ export function normalizeDosaiServiceMap(servicesMap = {}) {
|
|
|
1786
2222
|
return Object.keys(servicesMap)
|
|
1787
2223
|
.map((serviceName) => {
|
|
1788
2224
|
const definition = servicesMap[serviceName];
|
|
2225
|
+
const endpoints = new Set();
|
|
2226
|
+
for (const endpoint of definition.endpoints || []) {
|
|
2227
|
+
if (typeof endpoint === "string" && endpoint) {
|
|
2228
|
+
endpoints.add(encodeUriReference(endpoint));
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
1789
2231
|
const service = {
|
|
1790
2232
|
name:
|
|
1791
2233
|
definition.name ||
|
|
1792
2234
|
serviceName ||
|
|
1793
2235
|
`dosai-${basename(serviceName)}-service`,
|
|
1794
|
-
endpoints: Array.from(
|
|
2236
|
+
endpoints: Array.from(endpoints).sort(),
|
|
1795
2237
|
authenticated: definition.authenticated,
|
|
1796
2238
|
// Accept both spellings. Every pre-4.0.0 producer — collectDosaiServicesFromMethods here, and
|
|
1797
2239
|
// the OpenAPI/Java/Python paths in evinser.js — sets the camelCase `xTrustBoundary`; only the
|