@cyclonedx/cdxgen 12.4.1 → 12.4.3

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.
Files changed (34) hide show
  1. package/bin/evinse.js +15 -0
  2. package/lib/cli/index.js +60 -9
  3. package/lib/cli/index.poku.js +161 -0
  4. package/lib/evinser/evinser.js +118 -3
  5. package/lib/helpers/cbomutils.js +162 -2
  6. package/lib/helpers/cbomutils.poku.js +100 -0
  7. package/lib/helpers/ciParsers/githubActions.js +15 -3
  8. package/lib/helpers/ciParsers/githubActions.poku.js +52 -0
  9. package/lib/helpers/display.js +12 -6
  10. package/lib/helpers/display.poku.js +38 -0
  11. package/lib/helpers/dosai.js +433 -0
  12. package/lib/helpers/dosai.poku.js +302 -0
  13. package/lib/helpers/dosaiParsers.js +103 -0
  14. package/lib/helpers/utils.js +198 -1
  15. package/lib/helpers/utils.poku.js +352 -0
  16. package/lib/stages/postgen/annotator.js +2 -1
  17. package/lib/stages/postgen/annotator.poku.js +28 -0
  18. package/package.json +12 -12
  19. package/types/lib/cli/index.d.ts.map +1 -1
  20. package/types/lib/evinser/evinser.d.ts +15 -0
  21. package/types/lib/evinser/evinser.d.ts.map +1 -1
  22. package/types/lib/helpers/bomUtils.d.ts +1 -3
  23. package/types/lib/helpers/bomUtils.d.ts.map +1 -1
  24. package/types/lib/helpers/cbomutils.d.ts +1 -0
  25. package/types/lib/helpers/cbomutils.d.ts.map +1 -1
  26. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  27. package/types/lib/helpers/display.d.ts.map +1 -1
  28. package/types/lib/helpers/dosai.d.ts +24 -0
  29. package/types/lib/helpers/dosai.d.ts.map +1 -0
  30. package/types/lib/helpers/dosaiParsers.d.ts +8 -0
  31. package/types/lib/helpers/dosaiParsers.d.ts.map +1 -0
  32. package/types/lib/helpers/utils.d.ts.map +1 -1
  33. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  34. package/types/lib/validator/bomValidator.d.ts.map +1 -1
@@ -0,0 +1,302 @@
1
+ import esmock from "esmock";
2
+ import { assert, describe, it } from "poku";
3
+ import sinon from "sinon";
4
+
5
+ import {
6
+ buildPurlAliasMap,
7
+ collectDosaiDataFlowFrames,
8
+ collectDosaiPurlEvidence,
9
+ collectDosaiServicesFromMethods,
10
+ isDosaiDotnetLanguage,
11
+ normalizeDosaiServiceMap,
12
+ resolveComponentPurl,
13
+ } from "./dosai.js";
14
+
15
+ describe("dosai helpers", () => {
16
+ it("recognizes C#, VB.NET, and F# language aliases", () => {
17
+ for (const language of [
18
+ "csharp",
19
+ "dotnet",
20
+ "vb",
21
+ "vbnet",
22
+ "visualbasic",
23
+ "f#",
24
+ "fs",
25
+ "fsharp",
26
+ ]) {
27
+ assert.strictEqual(isDosaiDotnetLanguage(language), true);
28
+ }
29
+ });
30
+
31
+ it("matches versionless dosai purls to cdxgen component purls", () => {
32
+ const components = [{ purl: "pkg:nuget/System.Text.Json@10.0.0" }];
33
+ const aliases = buildPurlAliasMap(components);
34
+
35
+ assert.strictEqual(
36
+ resolveComponentPurl("pkg:nuget/System.Text.Json", aliases),
37
+ "pkg:nuget/System.Text.Json@10.0.0",
38
+ );
39
+ });
40
+
41
+ it("collects package occurrence evidence from dosai PackageReachability", () => {
42
+ const methodsSlice = {
43
+ CallGraph: {
44
+ Edges: [
45
+ {
46
+ Id: "e1",
47
+ FileName: "System.Text.Json.dll",
48
+ LineNumber: 12,
49
+ CalledMethodName: "System.Text.Json.JsonSerializer.Deserialize",
50
+ TargetName: "Deserialize",
51
+ },
52
+ ],
53
+ Nodes: [
54
+ {
55
+ Id: "n1",
56
+ FileName: "Program.cs",
57
+ LineNumber: 10,
58
+ ClassName: "Program",
59
+ Name: "Main",
60
+ },
61
+ {
62
+ Id: "n2",
63
+ FileName: "System.Text.Json.dll",
64
+ LineNumber: 0,
65
+ ClassName: "JsonSerializer",
66
+ Name: "Deserialize",
67
+ },
68
+ ],
69
+ },
70
+ PackageReachability: [
71
+ {
72
+ Purl: "pkg:nuget/System.Text.Json",
73
+ EdgeIds: ["e1"],
74
+ NodeIds: ["n1", "n2"],
75
+ SourceLocations: [
76
+ {
77
+ Path: "Controllers/Parser.cs",
78
+ FileName: "Parser.cs",
79
+ LineNumber: 42,
80
+ ColumnNumber: 13,
81
+ Kind: "CallGraphEdge",
82
+ },
83
+ ],
84
+ },
85
+ ],
86
+ };
87
+ const retMap = collectDosaiPurlEvidence(methodsSlice, [
88
+ { purl: "pkg:nuget/System.Text.Json@10.0.0" },
89
+ ]);
90
+
91
+ assert.deepStrictEqual(
92
+ Array.from(
93
+ retMap.purlLocationMap["pkg:nuget/System.Text.Json@10.0.0"],
94
+ ).sort(),
95
+ ["Controllers/Parser.cs#42"],
96
+ );
97
+ assert.ok(
98
+ retMap.purlMethodsMap["pkg:nuget/System.Text.Json@10.0.0"].has(
99
+ "System.Text.Json.JsonSerializer.Deserialize",
100
+ ),
101
+ );
102
+ });
103
+
104
+ it("keeps PackageReachability fallback occurrence evidence source-only", () => {
105
+ const retMap = collectDosaiPurlEvidence(
106
+ {
107
+ CallGraph: {
108
+ Edges: [
109
+ {
110
+ Id: "e1",
111
+ FileName: "System.Text.Json.dll",
112
+ LineNumber: 12,
113
+ CalledMethodName: "System.Text.Json.JsonSerializer.Deserialize",
114
+ CallLocation: {
115
+ FileName: "Program.fs",
116
+ LineNumber: 8,
117
+ },
118
+ },
119
+ {
120
+ Id: "e2",
121
+ FileName: "Controllers/EpisodesController.cs",
122
+ LineNumber: 17,
123
+ CalledMethodName: "System.Text.Json.JsonSerializer.Serialize",
124
+ },
125
+ ],
126
+ },
127
+ PackageReachability: [
128
+ {
129
+ Purl: "pkg:nuget/System.Text.Json",
130
+ EdgeIds: ["e1", "e2"],
131
+ },
132
+ ],
133
+ },
134
+ [{ purl: "pkg:nuget/System.Text.Json@10.0.0" }],
135
+ );
136
+
137
+ assert.deepStrictEqual(
138
+ Array.from(retMap.purlLocationMap["pkg:nuget/System.Text.Json@10.0.0"]),
139
+ ["Program.fs#8", "Controllers/EpisodesController.cs#17"],
140
+ );
141
+ assert.ok(
142
+ !Array.from(
143
+ retMap.purlLocationMap["pkg:nuget/System.Text.Json@10.0.0"],
144
+ ).some((location) => location.includes(".dll")),
145
+ );
146
+ });
147
+
148
+ it("collects package occurrence evidence from dosai Dependencies with purls", () => {
149
+ const retMap = collectDosaiPurlEvidence(
150
+ {
151
+ Dependencies: [
152
+ {
153
+ Path: "Program.vb",
154
+ FileName: "Program.vb",
155
+ Name: "Newtonsoft.Json",
156
+ Purl: "pkg:nuget/Newtonsoft.Json@13.0.3",
157
+ LineNumber: 4,
158
+ ColumnNumber: 9,
159
+ },
160
+ ],
161
+ },
162
+ [{ purl: "pkg:nuget/Newtonsoft.Json@13.0.3" }],
163
+ );
164
+
165
+ assert.deepStrictEqual(
166
+ Array.from(retMap.purlLocationMap["pkg:nuget/Newtonsoft.Json@13.0.3"]),
167
+ ["Program.vb#4"],
168
+ );
169
+ assert.ok(
170
+ retMap.purlModulesMap["pkg:nuget/Newtonsoft.Json@13.0.3"].has(
171
+ "Newtonsoft.Json",
172
+ ),
173
+ );
174
+ });
175
+
176
+ it("builds CycloneDX services from dosai ApiEndpoints without raw policy names", () => {
177
+ const servicesMap = collectDosaiServicesFromMethods({
178
+ ApiEndpoints: [
179
+ {
180
+ Route: "/api/podcasts?sig=secret",
181
+ FileName: "EpisodesController.cs",
182
+ Path: "Controllers/EpisodesController.cs",
183
+ ClassName: "EpisodesController",
184
+ MethodName: "Get",
185
+ HttpMethod: "GET",
186
+ EndpointKind: "Attribute",
187
+ AuthorizationRequired: true,
188
+ AuthorizationPolicies: ["InternalPolicyName"],
189
+ Roles: ["Admin"],
190
+ AllowAnonymous: false,
191
+ LineNumber: 42,
192
+ ColumnNumber: 9,
193
+ },
194
+ ],
195
+ });
196
+ const services = normalizeDosaiServiceMap(servicesMap);
197
+
198
+ assert.strictEqual(services.length, 1);
199
+ assert.deepStrictEqual(services[0].endpoints, ["/api/podcasts"]);
200
+ assert.strictEqual(services[0].authenticated, true);
201
+ assert.ok(
202
+ services[0].properties.some(
203
+ (property) =>
204
+ property.name === "cdx:dosai:authorizationPolicyCount" &&
205
+ property.value === "1",
206
+ ),
207
+ );
208
+ assert.ok(!JSON.stringify(services[0]).includes("InternalPolicyName"));
209
+ });
210
+
211
+ it("collects callstack frames from dosai data-flow slices", () => {
212
+ const frames = collectDosaiDataFlowFrames(
213
+ {
214
+ Nodes: [
215
+ {
216
+ Id: "dfn1",
217
+ Path: "Controllers/EpisodesController.cs",
218
+ Namespace: "Podcast.Api",
219
+ ClassName: "EpisodesController",
220
+ MethodName: "Get",
221
+ LineNumber: 12,
222
+ ColumnNumber: 5,
223
+ },
224
+ {
225
+ Id: "dfn2",
226
+ Path: "Services/JsonLoader.cs",
227
+ Namespace: "Podcast.Api",
228
+ ClassName: "JsonLoader",
229
+ MethodName: "Load",
230
+ LineNumber: 20,
231
+ ColumnNumber: 9,
232
+ },
233
+ ],
234
+ Slices: [
235
+ {
236
+ NodeIds: ["dfn1", "dfn2"],
237
+ Purls: ["pkg:nuget/System.Text.Json"],
238
+ },
239
+ ],
240
+ },
241
+ [{ purl: "pkg:nuget/System.Text.Json@10.0.0" }],
242
+ );
243
+
244
+ assert.strictEqual(frames["pkg:nuget/System.Text.Json@10.0.0"].length, 1);
245
+ assert.strictEqual(
246
+ frames["pkg:nuget/System.Text.Json@10.0.0"][0][1].function,
247
+ "Load",
248
+ );
249
+ });
250
+
251
+ it("rejects unsafe dosai command inputs before spawning", async () => {
252
+ const safeSpawnSync = sinon.stub().returns({ status: 0 });
253
+ const { runDosaiCommand } = await esmock("./dosai.js", {
254
+ "./plugins.js": { resolvePluginBinary: sinon.stub().returns("dosai") },
255
+ "./utils.js": {
256
+ DEBUG_MODE: false,
257
+ getTmpDir: sinon.stub().returns("/tmp"),
258
+ safeExistsSync: sinon.stub().returns(true),
259
+ safeMkdtempSync: sinon.stub(),
260
+ safeRmSync: sinon.stub(),
261
+ safeSpawnSync,
262
+ },
263
+ });
264
+
265
+ assert.strictEqual(
266
+ runDosaiCommand("methods;rm -rf /", "/tmp/project", "/tmp/out.json"),
267
+ false,
268
+ );
269
+ assert.strictEqual(
270
+ runDosaiCommand("methods", "/tmp/project\n--bad", "/tmp/out.json"),
271
+ false,
272
+ );
273
+ sinon.assert.notCalled(safeSpawnSync);
274
+ });
275
+
276
+ it("spawns dosai with argument arrays and shell disabled", async () => {
277
+ const safeSpawnSync = sinon.stub().returns({ status: 0 });
278
+ const { runDosaiCommand } = await esmock("./dosai.js", {
279
+ "./plugins.js": { resolvePluginBinary: sinon.stub().returns("dosai") },
280
+ "./utils.js": {
281
+ DEBUG_MODE: false,
282
+ getTmpDir: sinon.stub().returns("/tmp"),
283
+ safeExistsSync: sinon.stub().returns(true),
284
+ safeMkdtempSync: sinon.stub(),
285
+ safeRmSync: sinon.stub(),
286
+ safeSpawnSync,
287
+ },
288
+ });
289
+
290
+ assert.strictEqual(
291
+ runDosaiCommand("dataflows", "/tmp/project", "/tmp/out.json", {
292
+ dataFlowPatterns: "/tmp/patterns.json",
293
+ patternPacks: "/tmp/packs",
294
+ }),
295
+ true,
296
+ );
297
+ sinon.assert.calledOnce(safeSpawnSync);
298
+ assert.strictEqual(safeSpawnSync.firstCall.args[0], "dosai");
299
+ assert.ok(Array.isArray(safeSpawnSync.firstCall.args[1]));
300
+ assert.strictEqual(safeSpawnSync.firstCall.args[2].shell, false);
301
+ });
302
+ });
@@ -0,0 +1,103 @@
1
+ import { PackageURL } from "packageurl-js";
2
+
3
+ export function normalizeDosaiPurlKey(purl) {
4
+ if (!purl || typeof purl !== "string") {
5
+ return undefined;
6
+ }
7
+ try {
8
+ const purlObj = PackageURL.fromString(purl);
9
+ return [
10
+ purlObj.type?.toLowerCase(),
11
+ purlObj.namespace?.toLowerCase() || "",
12
+ purlObj.name?.toLowerCase(),
13
+ ].join("/");
14
+ } catch (_err) {
15
+ return purl.split("?")[0].split("#")[0].split("@")[0].toLowerCase();
16
+ }
17
+ }
18
+
19
+ export function addDosaiSetValue(map, key, value) {
20
+ if (!key || !value) {
21
+ return;
22
+ }
23
+ map[key] ??= new Set();
24
+ map[key].add(value);
25
+ }
26
+
27
+ export function dosaiLocation(item) {
28
+ const location = item?.Location || item?.CallLocation || item;
29
+ const fileName =
30
+ location?.Path || location?.FileName || item?.Path || item?.FileName;
31
+ if (!fileName || fileName === "<unknown>") {
32
+ return undefined;
33
+ }
34
+ const lineNumber = location?.LineNumber || item?.LineNumber;
35
+ if (lineNumber && lineNumber > 0) {
36
+ return `${fileName}#${lineNumber}`;
37
+ }
38
+ return fileName;
39
+ }
40
+
41
+ function dosaiSourceFileName(item) {
42
+ const location = item?.Location || item?.CallLocation || item;
43
+ return String(
44
+ location?.Path || location?.FileName || item?.Path || item?.FileName || "",
45
+ );
46
+ }
47
+
48
+ function dosaiSourceLineNumber(item) {
49
+ const location = item?.Location || item?.CallLocation || item;
50
+ return location?.LineNumber || item?.LineNumber;
51
+ }
52
+
53
+ export function dosaiSourceLocationFromNode(node) {
54
+ const location = dosaiLocation(node);
55
+ const fileName = dosaiSourceFileName(node).toLowerCase();
56
+ const lineNumber = dosaiSourceLineNumber(node);
57
+ if (!location || !/\.(cs|vb|fs|fsx)$/i.test(fileName)) {
58
+ return undefined;
59
+ }
60
+ if (!lineNumber || lineNumber <= 0) {
61
+ return undefined;
62
+ }
63
+ return location;
64
+ }
65
+
66
+ export function dosaiSourceLocation(location) {
67
+ const sourceLocation = dosaiLocation(location);
68
+ const fileName = dosaiSourceFileName(location);
69
+ const lineNumber = dosaiSourceLineNumber(location);
70
+ if (!sourceLocation || !/\.(cs|vb|fs|fsx)$/i.test(fileName)) {
71
+ return undefined;
72
+ }
73
+ if (!lineNumber || lineNumber <= 0) {
74
+ return undefined;
75
+ }
76
+ return sourceLocation;
77
+ }
78
+
79
+ export function buildDosaiPurlAliasMap(components = []) {
80
+ const purlAliasMap = new Map();
81
+ for (const component of components) {
82
+ if (!component?.purl) {
83
+ continue;
84
+ }
85
+ purlAliasMap.set(component.purl, component.purl);
86
+ const key = normalizeDosaiPurlKey(component.purl);
87
+ if (key && !purlAliasMap.has(key)) {
88
+ purlAliasMap.set(key, component.purl);
89
+ }
90
+ }
91
+ return purlAliasMap;
92
+ }
93
+
94
+ export function resolveDosaiComponentPurl(purl, purlAliasMap) {
95
+ if (!purl) {
96
+ return undefined;
97
+ }
98
+ return (
99
+ purlAliasMap.get(purl) ||
100
+ purlAliasMap.get(normalizeDosaiPurlKey(purl)) ||
101
+ purl
102
+ );
103
+ }
@@ -58,6 +58,13 @@ import Arborist from "../third-party/arborist/lib/index.js";
58
58
  import { analyzeSuspiciousJsFile } from "./analyzer.js";
59
59
  import { DEFAULT_HBOM_AUDIT_CATEGORIES } from "./auditCategories.js";
60
60
  import { parseWorkflowFile } from "./ciParsers/githubActions.js";
61
+ import {
62
+ addDosaiSetValue,
63
+ buildDosaiPurlAliasMap,
64
+ dosaiSourceLocation,
65
+ dosaiSourceLocationFromNode,
66
+ resolveDosaiComponentPurl,
67
+ } from "./dosaiParsers.js";
61
68
  import { extractPackageInfoFromHintPath } from "./dotnetutils.js";
62
69
  import {
63
70
  createOccurrenceEvidence,
@@ -1108,6 +1115,50 @@ function isWindowsShellHijackRisk(command, options) {
1108
1115
 
1109
1116
  const VERSION_PROBE_ARGS = new Set(["--version", "-version", "version"]);
1110
1117
 
1118
+ const POSIX_SHELL_METACHARACTERS = /[;&|<>$`\\\n\r]/;
1119
+ const WINDOWS_SHELL_METACHARACTERS = /[&|<>^%\n\r]/;
1120
+
1121
+ function hasShellMetacharacters(value) {
1122
+ if (value === undefined || value === null) {
1123
+ return false;
1124
+ }
1125
+ const stringValue = String(value);
1126
+ return isWin
1127
+ ? WINDOWS_SHELL_METACHARACTERS.test(stringValue)
1128
+ : POSIX_SHELL_METACHARACTERS.test(stringValue);
1129
+ }
1130
+
1131
+ function getUnsafeShellToken(command, args) {
1132
+ if (hasShellMetacharacters(command)) {
1133
+ return command;
1134
+ }
1135
+ const argList = Array.isArray(args)
1136
+ ? args
1137
+ : args === undefined || args === null
1138
+ ? []
1139
+ : [args];
1140
+ return argList.find((arg) => hasShellMetacharacters(arg));
1141
+ }
1142
+
1143
+ function recordSuspiciousShellPathActivities(files, metadata = {}) {
1144
+ for (const file of files) {
1145
+ if (!hasShellMetacharacters(file)) {
1146
+ continue;
1147
+ }
1148
+ recordActivity({
1149
+ classification: "suspicious-path",
1150
+ discoveryType: metadata.discoveryType,
1151
+ kind: "inspect",
1152
+ pattern: metadata.pattern,
1153
+ reason:
1154
+ "Suspicious path contains shell metacharacters. cdxgen passes direct process arguments as argv values, but review this path before invoking external build tools on untrusted projects.",
1155
+ risk: "shell-metacharacters",
1156
+ status: "completed",
1157
+ target: file,
1158
+ });
1159
+ }
1160
+ }
1161
+
1111
1162
  function detectProbeType(command, args = []) {
1112
1163
  const normalizedCommand = basename(String(command || "")).toLowerCase();
1113
1164
  const normalizedArgs = (args || []).map((arg) => String(arg).toLowerCase());
@@ -1286,8 +1337,30 @@ export function safeSpawnSync(command, args, options) {
1286
1337
  options = {
1287
1338
  ...options,
1288
1339
  };
1340
+ }
1341
+ if (options.cdxgenActivity) {
1289
1342
  delete options.cdxgenActivity;
1290
1343
  }
1344
+ if (options.shell === true) {
1345
+ const unsafeShellToken = getUnsafeShellToken(command, args);
1346
+ if (unsafeShellToken !== undefined) {
1347
+ const blockedReason = `Blocked shell execution for ${command}: command or argument contains shell metacharacters.`;
1348
+ console.warn(`\x1b[1;31mSecurity Alert: ${blockedReason}\x1b[0m`);
1349
+ recordActivity({
1350
+ kind: activityDescriptor.kind,
1351
+ ...activityDescriptor.metadata,
1352
+ reason: blockedReason,
1353
+ status: "blocked",
1354
+ target: activityDescriptor.target,
1355
+ });
1356
+ return {
1357
+ status: 1,
1358
+ stdout: undefined,
1359
+ stderr: undefined,
1360
+ error: new Error(blockedReason),
1361
+ };
1362
+ }
1363
+ }
1291
1364
  // Inject maxBuffer
1292
1365
  if (!options.maxBuffer) {
1293
1366
  options.maxBuffer = MAX_BUFFER;
@@ -1753,6 +1826,10 @@ export const PROJECT_TYPE_ALIASES = {
1753
1826
  "dotnet-framework47",
1754
1827
  "dotnet-framework48",
1755
1828
  "vb",
1829
+ "vbnet",
1830
+ "visualbasic",
1831
+ "f#",
1832
+ "fs",
1756
1833
  "fsharp",
1757
1834
  "twincat",
1758
1835
  "csproj",
@@ -2363,6 +2440,10 @@ export function getAllFilesWithIgnore(
2363
2440
  reasonBuilder: (count) =>
2364
2441
  `Scanned ${dirPath} with glob '${patternValue}' for ${discoveryMetadata.label}; matched ${files.length} path(s)${buildReadCountSuffix(count)}.`,
2365
2442
  });
2443
+ recordSuspiciousShellPathActivities(files, {
2444
+ discoveryType: discoveryMetadata.discoveryType,
2445
+ pattern: patternValue,
2446
+ });
2366
2447
  if (files.length > 1) {
2367
2448
  thoughtLog(
2368
2449
  `Found ${files.length} files for the pattern '${pattern}' at '${dirPath}'.`,
@@ -21739,6 +21820,42 @@ export async function getNugetMetadata(pkgList, dependencies = undefined) {
21739
21820
  };
21740
21821
  }
21741
21822
 
21823
+ function addDotnetIdentityMethod(apkg, value) {
21824
+ if (!value) {
21825
+ return;
21826
+ }
21827
+ apkg.evidence = apkg.evidence || {};
21828
+ const identityList = Array.isArray(apkg.evidence.identity)
21829
+ ? apkg.evidence.identity
21830
+ : undefined;
21831
+ let identity = identityList
21832
+ ? identityList.find((entry) => entry?.field === "purl") ||
21833
+ identityList.find((entry) => !entry?.field)
21834
+ : apkg.evidence.identity;
21835
+ if (!identity) {
21836
+ identity = { field: "purl", confidence: 1, methods: [] };
21837
+ if (identityList) {
21838
+ identityList.push(identity);
21839
+ }
21840
+ }
21841
+ identity.field ??= "purl";
21842
+ identity.confidence ??= 1;
21843
+ identity.methods ??= [];
21844
+ if (
21845
+ !identity.methods.some(
21846
+ (method) =>
21847
+ method.technique === "source-code-analysis" && method.value === value,
21848
+ )
21849
+ ) {
21850
+ identity.methods.push({
21851
+ technique: "source-code-analysis",
21852
+ confidence: 1,
21853
+ value,
21854
+ });
21855
+ }
21856
+ apkg.evidence.identity = identityList || identity;
21857
+ }
21858
+
21742
21859
  /**
21743
21860
  * Enrich .NET package components with occurrence evidence and imported module/method
21744
21861
  * information from a dosai dependency slices file.
@@ -21762,6 +21879,7 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
21762
21879
  const purlLocationMap = {};
21763
21880
  const purlModulesMap = {};
21764
21881
  const purlMethodsMap = {};
21882
+ const purlAliasMap = buildDosaiPurlAliasMap(pkgList);
21765
21883
  for (const apkg of pkgList) {
21766
21884
  if (apkg.properties && Array.isArray(apkg.properties)) {
21767
21885
  apkg.properties
@@ -21778,13 +21896,33 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
21778
21896
  });
21779
21897
  }
21780
21898
  }
21781
- const slicesData = JSON.parse(readFileSync(slicesFile, "utf-8"));
21899
+ let slicesData;
21900
+ try {
21901
+ slicesData = JSON.parse(readFileSync(slicesFile, "utf-8"));
21902
+ } catch (_err) {
21903
+ return pkgList;
21904
+ }
21782
21905
  if (slicesData && Object.keys(slicesData)) {
21783
21906
  thoughtLog(
21784
21907
  "Let's thoroughly inspect the dependency slice to identify where and how the components are used.",
21785
21908
  );
21786
21909
  if (slicesData.Dependencies) {
21787
21910
  for (const adep of slicesData.Dependencies) {
21911
+ if (adep.Purl) {
21912
+ const modPurl = resolveDosaiComponentPurl(adep.Purl, purlAliasMap);
21913
+ if (modPurl) {
21914
+ addDosaiSetValue(
21915
+ purlLocationMap,
21916
+ modPurl,
21917
+ dosaiSourceLocation(adep),
21918
+ );
21919
+ addDosaiSetValue(
21920
+ purlModulesMap,
21921
+ modPurl,
21922
+ adep.Name || adep.Namespace,
21923
+ );
21924
+ }
21925
+ }
21788
21926
  // Case 1: Dependencies slice has the .dll file
21789
21927
  if (adep.Module?.endsWith(".dll") && pkgFilePurlMap[adep.Module]) {
21790
21928
  const modPurl = pkgFilePurlMap[adep.Module];
@@ -21810,6 +21948,64 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
21810
21948
  }
21811
21949
  }
21812
21950
  }
21951
+ if (slicesData.PackageReachability) {
21952
+ const graphEdges = Object.fromEntries(
21953
+ (slicesData.CallGraph?.Edges || []).map((edge) => [edge.Id, edge]),
21954
+ );
21955
+ const graphNodes = Object.fromEntries(
21956
+ (slicesData.CallGraph?.Nodes || []).map((node) => [node.Id, node]),
21957
+ );
21958
+ for (const reachability of slicesData.PackageReachability) {
21959
+ const modPurl = resolveDosaiComponentPurl(
21960
+ reachability.Purl,
21961
+ purlAliasMap,
21962
+ );
21963
+ if (!modPurl) {
21964
+ continue;
21965
+ }
21966
+ let hasExplicitSourceLocations = false;
21967
+ for (const sourceLocation of reachability.SourceLocations || []) {
21968
+ const location = dosaiSourceLocation(sourceLocation);
21969
+ addDosaiSetValue(purlLocationMap, modPurl, location);
21970
+ hasExplicitSourceLocations ||= Boolean(location);
21971
+ }
21972
+ for (const edgeId of reachability.EdgeIds || []) {
21973
+ const edge = graphEdges[edgeId];
21974
+ if (!hasExplicitSourceLocations) {
21975
+ addDosaiSetValue(
21976
+ purlLocationMap,
21977
+ modPurl,
21978
+ dosaiSourceLocation(edge),
21979
+ );
21980
+ }
21981
+ addDosaiSetValue(
21982
+ purlMethodsMap,
21983
+ modPurl,
21984
+ edge?.CalledMethodName || edge?.TargetName,
21985
+ );
21986
+ }
21987
+ for (const nodeId of reachability.NodeIds || []) {
21988
+ const node = graphNodes[nodeId];
21989
+ if (!hasExplicitSourceLocations) {
21990
+ addDosaiSetValue(
21991
+ purlLocationMap,
21992
+ modPurl,
21993
+ dosaiSourceLocationFromNode(node),
21994
+ );
21995
+ }
21996
+ addDosaiSetValue(
21997
+ purlModulesMap,
21998
+ modPurl,
21999
+ node?.ClassName || node?.Module,
22000
+ );
22001
+ addDosaiSetValue(
22002
+ purlMethodsMap,
22003
+ modPurl,
22004
+ node?.Name || node?.Identity?.MethodName,
22005
+ );
22006
+ }
22007
+ }
22008
+ }
21813
22009
  if (slicesData.MethodCalls) {
21814
22010
  for (const amethodCall of slicesData.MethodCalls) {
21815
22011
  if (
@@ -21857,6 +22053,7 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
21857
22053
  apkg.evidence.occurrences = locationOccurrences.map((l) =>
21858
22054
  parseOccurrenceEvidenceLocation(l),
21859
22055
  );
22056
+ addDotnetIdentityMethod(apkg, locationOccurrences[0]);
21860
22057
  // Set the package scope
21861
22058
  apkg.scope = "required";
21862
22059
  }