@cyclonedx/cdxgen 12.4.0 → 12.4.2

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 (71) hide show
  1. package/README.md +6 -4
  2. package/bin/cdxgen.js +32 -11
  3. package/bin/convert.js +12 -8
  4. package/bin/evinse.js +15 -0
  5. package/bin/hbom.js +13 -8
  6. package/bin/repl.js +14 -10
  7. package/bin/validate.js +10 -13
  8. package/bin/verify.js +7 -29
  9. package/data/cyclonedx-2.0-bundled.schema.json +7182 -0
  10. package/lib/audit/index.js +2 -1
  11. package/lib/cli/index.js +77 -16
  12. package/lib/cli/index.poku.js +197 -0
  13. package/lib/evinser/evinser.js +118 -3
  14. package/lib/helpers/bomUtils.js +155 -1
  15. package/lib/helpers/bomUtils.poku.js +79 -1
  16. package/lib/helpers/cbomutils.js +162 -2
  17. package/lib/helpers/cbomutils.poku.js +100 -0
  18. package/lib/helpers/ciParsers/githubActions.js +15 -3
  19. package/lib/helpers/ciParsers/githubActions.poku.js +52 -0
  20. package/lib/helpers/dosai.js +433 -0
  21. package/lib/helpers/dosai.poku.js +302 -0
  22. package/lib/helpers/dosaiParsers.js +103 -0
  23. package/lib/helpers/plugins.js +17 -16
  24. package/lib/helpers/protobom.js +53 -0
  25. package/lib/helpers/protobom.poku.js +44 -1
  26. package/lib/helpers/protobomLoader.js +43 -0
  27. package/lib/helpers/protobomLoader.poku.js +31 -0
  28. package/lib/helpers/utils.js +130 -1
  29. package/lib/helpers/utils.poku.js +295 -0
  30. package/lib/server/server.js +2 -1
  31. package/lib/stages/postgen/annotator.js +2 -1
  32. package/lib/stages/postgen/annotator.poku.js +28 -0
  33. package/lib/stages/postgen/postgen.js +219 -12
  34. package/lib/stages/postgen/postgen.poku.js +163 -0
  35. package/lib/validator/bomValidator.js +90 -38
  36. package/lib/validator/bomValidator.poku.js +90 -0
  37. package/lib/validator/complianceRules.js +4 -2
  38. package/lib/validator/index.poku.js +14 -0
  39. package/package.json +12 -12
  40. package/types/bin/repl.d.ts +1 -1
  41. package/types/bin/repl.d.ts.map +1 -1
  42. package/types/lib/audit/index.d.ts.map +1 -1
  43. package/types/lib/cli/index.d.ts.map +1 -1
  44. package/types/lib/evinser/evinser.d.ts +15 -0
  45. package/types/lib/evinser/evinser.d.ts.map +1 -1
  46. package/types/lib/helpers/bomUtils.d.ts +8 -0
  47. package/types/lib/helpers/bomUtils.d.ts.map +1 -1
  48. package/types/lib/helpers/cbomutils.d.ts +1 -0
  49. package/types/lib/helpers/cbomutils.d.ts.map +1 -1
  50. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  51. package/types/lib/helpers/dosai.d.ts +24 -0
  52. package/types/lib/helpers/dosai.d.ts.map +1 -0
  53. package/types/lib/helpers/dosaiParsers.d.ts +8 -0
  54. package/types/lib/helpers/dosaiParsers.d.ts.map +1 -0
  55. package/types/lib/helpers/hbomAnalysis.d.ts +14 -0
  56. package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -1
  57. package/types/lib/helpers/hostTopology.d.ts.map +1 -1
  58. package/types/lib/helpers/plugins.d.ts.map +1 -1
  59. package/types/lib/helpers/protobom.d.ts +2 -0
  60. package/types/lib/helpers/protobom.d.ts.map +1 -1
  61. package/types/lib/helpers/protobomLoader.d.ts +17 -0
  62. package/types/lib/helpers/protobomLoader.d.ts.map +1 -0
  63. package/types/lib/helpers/utils.d.ts.map +1 -1
  64. package/types/lib/server/server.d.ts.map +1 -1
  65. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  66. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  67. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  68. package/types/lib/third-party/arborist/lib/node.d.ts +23 -0
  69. package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -1
  70. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  71. package/types/lib/validator/complianceRules.d.ts.map +1 -1
@@ -6,6 +6,7 @@ import process from "node:process";
6
6
  import { createBom } from "../cli/index.js";
7
7
  import { DEFAULT_HBOM_AUDIT_CATEGORIES } from "../helpers/auditCategories.js";
8
8
  import {
9
+ getCycloneDxFormat,
9
10
  getNonCycloneDxErrorMessage,
10
11
  isCycloneDxBom,
11
12
  } from "../helpers/bomUtils.js";
@@ -230,7 +231,7 @@ export async function runDirectBomAuditFromBoms(inputBoms, options = {}) {
230
231
  const findings = await auditBom(inputBom.bomJson, directAuditOptions);
231
232
  results.push({
232
233
  auditOptions: directAuditOptions,
233
- bomFormat: inputBom.bomJson?.bomFormat,
234
+ bomFormat: getCycloneDxFormat(inputBom.bomJson),
234
235
  findings,
235
236
  serialNumber: inputBom.bomJson?.serialNumber,
236
237
  source: inputBom.source,
package/lib/cli/index.js CHANGED
@@ -41,8 +41,15 @@ import {
41
41
  rewriteExtractedArchivePaths,
42
42
  } from "../helpers/asarutils.js";
43
43
  import { expandBomAuditCategories } from "../helpers/auditCategories.js";
44
+ import {
45
+ setCycloneDxFormat,
46
+ toCycloneDxSpecVersionString,
47
+ } from "../helpers/bomUtils.js";
44
48
  import { parseCaxaMetadata } from "../helpers/caxa.js";
45
- import { collectSourceCryptoComponents } from "../helpers/cbomutils.js";
49
+ import {
50
+ collectDosaiCryptoComponents,
51
+ collectSourceCryptoComponents,
52
+ } from "../helpers/cbomutils.js";
46
53
  import {
47
54
  CHROME_EXTENSION_PURL_TYPE,
48
55
  collectChromeExtensionsFromPath,
@@ -54,6 +61,13 @@ import {
54
61
  mergeServices,
55
62
  trimComponents,
56
63
  } from "../helpers/depsUtils.js";
64
+ import {
65
+ collectDosaiServicesFromMethods,
66
+ createDosaiMethodsSlice,
67
+ isDosaiDotnetLanguage,
68
+ normalizeDosaiServiceMap,
69
+ readDosaiJsonFile,
70
+ } from "../helpers/dosai.js";
57
71
  import { GIT_COMMAND } from "../helpers/envcontext.js";
58
72
  import {
59
73
  createHbomDocument,
@@ -242,7 +256,6 @@ import {
242
256
  enrichOSComponentsWithTrustData,
243
257
  executeOsQuery,
244
258
  getBinaryBom,
245
- getDotnetSlices,
246
259
  getOSPackages,
247
260
  getPluginToolComponents,
248
261
  } from "../managers/binary.js";
@@ -396,6 +409,27 @@ const hasExplicitProjectTypeSelection = (options, baseProjectType) => {
396
409
  );
397
410
  };
398
411
 
412
+ const hasDotnetProjectIndicators = (src, options = {}) => {
413
+ return Boolean(
414
+ getAllFiles(src, "**/*.{csproj,fsproj,vbproj,sln}", options)?.length,
415
+ );
416
+ };
417
+
418
+ const shouldCollectDosaiCrypto = (src, options = {}) => {
419
+ const projectTypes = Array.isArray(options.projectType)
420
+ ? options.projectType
421
+ : options.projectType
422
+ ? [options.projectType]
423
+ : [];
424
+ if (projectTypes.some((projectType) => isDosaiDotnetLanguage(projectType))) {
425
+ return true;
426
+ }
427
+ if (!projectTypes.length || projectTypes.includes("universal")) {
428
+ return hasDotnetProjectIndicators(src, options);
429
+ }
430
+ return false;
431
+ };
432
+
399
433
  const determineParentComponent = (options) => {
400
434
  let parentComponent;
401
435
  if (options.parentComponent && Object.keys(options.parentComponent).length) {
@@ -1393,13 +1427,16 @@ const buildBomNSData = (options, pkgInfo, ptype, context) => {
1393
1427
  // CycloneDX Json Template
1394
1428
  const jsonTpl = {
1395
1429
  bomFormat: "CycloneDX",
1396
- specVersion: `${options.specVersion || "1.7"}`,
1430
+ specVersion: toCycloneDxSpecVersionString(options.specVersion || "1.7"),
1397
1431
  serialNumber: serialNum,
1398
1432
  version: 1,
1399
1433
  metadata: metadata,
1400
1434
  components,
1401
1435
  dependencies,
1402
1436
  };
1437
+ setCycloneDxFormat(jsonTpl, jsonTpl.specVersion, {
1438
+ preserveLegacyBomFormat: true,
1439
+ });
1403
1440
  if (services.length) {
1404
1441
  jsonTpl.services = mergeServices([], services);
1405
1442
  }
@@ -7735,6 +7772,7 @@ export async function createCsharpBom(path, options) {
7735
7772
  }
7736
7773
  }
7737
7774
  const pkgNameVersions = {};
7775
+ let services = [];
7738
7776
  if (csProjFiles.length) {
7739
7777
  manifestFiles = manifestFiles.concat(csProjFiles);
7740
7778
  // Parsing csproj is quite error-prone. Some project files may not have versions specified
@@ -7796,21 +7834,30 @@ export async function createCsharpBom(path, options) {
7796
7834
  // Perform deep analysis using dosai
7797
7835
  if (options.deep) {
7798
7836
  const slicesFile = resolve(
7799
- join(path, options.depsSlicesFile) || join(getTmpDir(), "dosai.json"),
7837
+ options.depsSlicesFile
7838
+ ? join(path, options.depsSlicesFile)
7839
+ : join(getTmpDir(), "dosai.json"),
7800
7840
  );
7801
7841
  // Create the slices file if it doesn't exist
7802
7842
  if (!safeExistsSync(slicesFile)) {
7803
7843
  thoughtLog(
7804
7844
  "Alright, the next step is to invoke the dosai command to identify evidence of occurrences for various components.",
7805
7845
  );
7806
- const sliceResult = getDotnetSlices(resolve(path), resolve(slicesFile));
7846
+ const sliceResult = createDosaiMethodsSlice(
7847
+ resolve(path),
7848
+ resolve(slicesFile),
7849
+ options,
7850
+ );
7807
7851
  if (!sliceResult && DEBUG_MODE) {
7808
7852
  console.log(
7809
7853
  "Slicing with dosai was unsuccessful. Check the errors reported in the logs above.",
7810
7854
  );
7811
7855
  }
7812
7856
  }
7813
- pkgList = addEvidenceForDotnet(pkgList, slicesFile, options);
7857
+ pkgList = addEvidenceForDotnet(pkgList, slicesFile);
7858
+ const methodsSlice = readDosaiJsonFile(slicesFile);
7859
+ const servicesMap = collectDosaiServicesFromMethods(methodsSlice, {});
7860
+ services = normalizeDosaiServiceMap(servicesMap);
7814
7861
  }
7815
7862
  }
7816
7863
  // Parent dependency tree
@@ -7836,6 +7883,8 @@ export async function createCsharpBom(path, options) {
7836
7883
  filename: manifestFiles.join(", "),
7837
7884
  dependencies,
7838
7885
  parentComponent,
7886
+ services,
7887
+ tools: options.deep ? getPluginToolComponents(["dosai"]) : [],
7839
7888
  });
7840
7889
  }
7841
7890
 
@@ -8347,6 +8396,15 @@ export async function createCryptoCertsBom(path, options) {
8347
8396
  if (sourceCryptoComponents.length) {
8348
8397
  pkgList.push(...sourceCryptoComponents);
8349
8398
  }
8399
+ if (shouldCollectDosaiCrypto(path, options)) {
8400
+ const dosaiCryptoComponents = await collectDosaiCryptoComponents(
8401
+ path,
8402
+ options,
8403
+ );
8404
+ if (dosaiCryptoComponents.length) {
8405
+ pkgList.push(...dosaiCryptoComponents);
8406
+ }
8407
+ }
8350
8408
  return {
8351
8409
  bomJson: {
8352
8410
  components: pkgList,
@@ -8392,16 +8450,19 @@ export function dedupeBom(options, components, parentComponent, dependencies) {
8392
8450
  options,
8393
8451
  parentComponent,
8394
8452
  components,
8395
- bomJson: {
8396
- bomFormat: "CycloneDX",
8397
- specVersion: `${options.specVersion || 1.7}`,
8398
- serialNumber: serialNum,
8399
- version: 1,
8400
- metadata: addMetadata(parentComponent, options, {}),
8401
- components,
8402
- services: options.services || [],
8403
- dependencies,
8404
- },
8453
+ bomJson: setCycloneDxFormat(
8454
+ {
8455
+ specVersion: toCycloneDxSpecVersionString(options.specVersion || 1.7),
8456
+ serialNumber: serialNum,
8457
+ version: 1,
8458
+ metadata: addMetadata(parentComponent, options, {}),
8459
+ components,
8460
+ services: options.services || [],
8461
+ dependencies,
8462
+ },
8463
+ options.specVersion || 1.7,
8464
+ { preserveLegacyBomFormat: true },
8465
+ ),
8405
8466
  };
8406
8467
  }
8407
8468
 
@@ -286,6 +286,86 @@ describe("CLI tests", () => {
286
286
  );
287
287
  assert.strictEqual(components[0].type, "data");
288
288
  });
289
+
290
+ it("does not invoke dosai crypto analysis for non-.NET CBOM scans", async () => {
291
+ const tempDir = mkdtempSync(join(tmpdir(), "cdxgen-cbom-non-dotnet-"));
292
+ const collectDosaiCryptoComponents = sinon.stub().resolves([]);
293
+ try {
294
+ const { createCryptoCertsBom } = await esmock("./index.js", {
295
+ "../helpers/cbomutils.js": {
296
+ collectDosaiCryptoComponents,
297
+ collectSourceCryptoComponents: sinon.stub().resolves([]),
298
+ },
299
+ });
300
+
301
+ await createCryptoCertsBom(tempDir, {
302
+ projectType: ["js"],
303
+ specVersion: 1.7,
304
+ });
305
+
306
+ sinon.assert.notCalled(collectDosaiCryptoComponents);
307
+ } finally {
308
+ rmSync(tempDir, { force: true, recursive: true });
309
+ }
310
+ });
311
+
312
+ it("invokes dosai crypto analysis for explicit .NET CBOM scans", async () => {
313
+ const tempDir = mkdtempSync(join(tmpdir(), "cdxgen-cbom-dotnet-"));
314
+ const collectDosaiCryptoComponents = sinon.stub().resolves([
315
+ {
316
+ name: "sha-256",
317
+ type: "cryptographic-asset",
318
+ "bom-ref": "crypto/algorithm/sha-256@2.16.840.1.101.3.4.2.1",
319
+ cryptoProperties: {
320
+ assetType: "algorithm",
321
+ oid: "2.16.840.1.101.3.4.2.1",
322
+ },
323
+ },
324
+ ]);
325
+ try {
326
+ const { createCryptoCertsBom } = await esmock("./index.js", {
327
+ "../helpers/cbomutils.js": {
328
+ collectDosaiCryptoComponents,
329
+ collectSourceCryptoComponents: sinon.stub().resolves([]),
330
+ },
331
+ });
332
+
333
+ const bomData = await createCryptoCertsBom(tempDir, {
334
+ projectType: ["dotnet"],
335
+ specVersion: 1.7,
336
+ });
337
+
338
+ sinon.assert.calledOnce(collectDosaiCryptoComponents);
339
+ assert.strictEqual(bomData.bomJson.components[0].name, "sha-256");
340
+ } finally {
341
+ rmSync(tempDir, { force: true, recursive: true });
342
+ }
343
+ });
344
+
345
+ it("invokes dosai crypto analysis when universal scans contain .NET project files", async () => {
346
+ const tempDir = mkdtempSync(
347
+ join(tmpdir(), "cdxgen-cbom-dotnet-indicator-"),
348
+ );
349
+ const collectDosaiCryptoComponents = sinon.stub().resolves([]);
350
+ try {
351
+ writeFileSync(join(tempDir, "app.csproj"), "<Project />");
352
+ const { createCryptoCertsBom } = await esmock("./index.js", {
353
+ "../helpers/cbomutils.js": {
354
+ collectDosaiCryptoComponents,
355
+ collectSourceCryptoComponents: sinon.stub().resolves([]),
356
+ },
357
+ });
358
+
359
+ await createCryptoCertsBom(tempDir, {
360
+ projectType: ["universal"],
361
+ specVersion: 1.7,
362
+ });
363
+
364
+ sinon.assert.calledOnce(collectDosaiCryptoComponents);
365
+ } finally {
366
+ rmSync(tempDir, { force: true, recursive: true });
367
+ }
368
+ });
289
369
  });
290
370
 
291
371
  describe("distribution filters", () => {
@@ -623,6 +703,7 @@ describe("CLI tests", () => {
623
703
  false,
624
704
  );
625
705
  },
706
+ isolateDepsSlicesFile: true,
626
707
  expectedSpecVersion: (specVersion) => specVersion,
627
708
  name: "cbom",
628
709
  },
@@ -651,6 +732,13 @@ describe("CLI tests", () => {
651
732
  fixtureRoot,
652
733
  `${scenario.name}-${specVersion}.spdx.json`,
653
734
  );
735
+ const depsSlicesPath = join(
736
+ fixtureRoot,
737
+ `${scenario.name}-${specVersion}.deps.slices.json`,
738
+ );
739
+ const depsSlicesArgs = scenario.isolateDepsSlicesFile
740
+ ? ["--deps-slices-file", depsSlicesPath]
741
+ : [];
654
742
  const generateResult = spawnSync(
655
743
  process.execPath,
656
744
  [
@@ -660,6 +748,7 @@ describe("CLI tests", () => {
660
748
  jsonPath,
661
749
  "--spec-version",
662
750
  specVersion,
751
+ ...depsSlicesArgs,
663
752
  "--export-proto",
664
753
  "--proto-bin-file",
665
754
  protoPath,
@@ -738,6 +827,114 @@ describe("CLI tests", () => {
738
827
  scenario.assertRoundTrip(roundTrippedBom);
739
828
  }
740
829
  }
830
+ assert.strictEqual(
831
+ existsSync(join(repoDir, "deps.slices.json")),
832
+ false,
833
+ "protobuf round-trip tests must not leave deps.slices.json in the repository root",
834
+ );
835
+ } finally {
836
+ rmSync(join(repoDir, "deps.slices.json"), { force: true });
837
+ rmSync(fixtureRoot, { force: true, recursive: true });
838
+ }
839
+ });
840
+ });
841
+
842
+ describe("CycloneDX 2.0 JSON output", () => {
843
+ it("generates valid experimental 2.0-dev JSON with specFormat", () => {
844
+ const fixtureRoot = mkdtempSync(join(tmpdir(), "cdxgen-json20-"));
845
+ try {
846
+ const jsonPath = join(fixtureRoot, "bom-2.0.json");
847
+ const generateResult = spawnSync(
848
+ process.execPath,
849
+ [
850
+ join(repoDir, "bin", "cdxgen.js"),
851
+ "-t",
852
+ "js",
853
+ mcpFixtureDir,
854
+ "-o",
855
+ jsonPath,
856
+ "--spec-version",
857
+ "2.0",
858
+ "--no-banner",
859
+ ],
860
+ {
861
+ cwd: repoDir,
862
+ encoding: "utf8",
863
+ env: buildMinimalCliEnv(),
864
+ },
865
+ );
866
+ assert.strictEqual(
867
+ generateResult.status,
868
+ 0,
869
+ `${generateResult.stdout}${generateResult.stderr}`,
870
+ );
871
+
872
+ const generatedBom = JSON.parse(readFileSync(jsonPath, "utf8"));
873
+ assert.strictEqual(generatedBom.specFormat, "CycloneDX");
874
+ assert.strictEqual(generatedBom.bomFormat, undefined);
875
+ assert.strictEqual(generatedBom.specVersion, "2.0");
876
+ assert.ok(Array.isArray(generatedBom.metadata?.tools?.components));
877
+
878
+ const validateResult = spawnSync(
879
+ process.execPath,
880
+ [
881
+ join(repoDir, "bin", "validate.js"),
882
+ "-i",
883
+ jsonPath,
884
+ "--fail-severity",
885
+ "critical",
886
+ "--no-deep",
887
+ "--report",
888
+ "json",
889
+ ],
890
+ {
891
+ cwd: repoDir,
892
+ encoding: "utf8",
893
+ env: buildMinimalCliEnv(),
894
+ },
895
+ );
896
+ assert.strictEqual(
897
+ validateResult.status,
898
+ 0,
899
+ `${validateResult.stdout}${validateResult.stderr}`,
900
+ );
901
+ } finally {
902
+ rmSync(fixtureRoot, { force: true, recursive: true });
903
+ }
904
+ });
905
+
906
+ it("rejects experimental 2.0-dev protobuf export until cdx-proto supports it", () => {
907
+ const fixtureRoot = mkdtempSync(join(tmpdir(), "cdxgen-proto20-"));
908
+ try {
909
+ const jsonPath = join(fixtureRoot, "bom-2.0.json");
910
+ const protoPath = join(fixtureRoot, "bom-2.0.cdx");
911
+ const generateResult = spawnSync(
912
+ process.execPath,
913
+ [
914
+ join(repoDir, "bin", "cdxgen.js"),
915
+ "-t",
916
+ "js",
917
+ mcpFixtureDir,
918
+ "-o",
919
+ jsonPath,
920
+ "--spec-version",
921
+ "2.0",
922
+ "--export-proto",
923
+ "--proto-bin-file",
924
+ protoPath,
925
+ "--no-banner",
926
+ ],
927
+ {
928
+ cwd: repoDir,
929
+ encoding: "utf8",
930
+ env: buildMinimalCliEnv(),
931
+ },
932
+ );
933
+ assert.strictEqual(generateResult.status, 1);
934
+ assert.match(
935
+ `${generateResult.stdout}${generateResult.stderr}`,
936
+ /CycloneDX 2\.0 is not currently supported for protobuf export/i,
937
+ );
741
938
  } finally {
742
939
  rmSync(fixtureRoot, { force: true, recursive: true });
743
940
  }
@@ -4,7 +4,19 @@ import process from "node:process";
4
4
 
5
5
  import { PackageURL } from "packageurl-js";
6
6
 
7
- import { findCryptoAlgos } from "../helpers/cbomutils.js";
7
+ import {
8
+ collectDosaiCryptoComponents,
9
+ findCryptoAlgos,
10
+ } from "../helpers/cbomutils.js";
11
+ import { mergeServices } from "../helpers/depsUtils.js";
12
+ import {
13
+ collectDosaiDataFlowFrames,
14
+ collectDosaiPurlEvidence,
15
+ collectDosaiServicesFromMethods,
16
+ createDosaiDataFlowSlice,
17
+ createDosaiMethodsSlice,
18
+ isDosaiDotnetLanguage,
19
+ } from "../helpers/dosai.js";
8
20
  import { parseOccurrenceEvidenceLocation } from "../helpers/evidenceUtils.js";
9
21
  import {
10
22
  collectGradleDependencies,
@@ -230,6 +242,8 @@ export async function createSlice(
230
242
  language = "python";
231
243
  } else if (PROJECT_TYPE_ALIASES.scala.includes(language)) {
232
244
  language = "scala";
245
+ } else if (isDosaiDotnetLanguage(language)) {
246
+ language = "csharp";
233
247
  }
234
248
  if (
235
249
  PROJECT_TYPE_ALIASES.swift.includes(language) &&
@@ -270,6 +284,33 @@ export async function createSlice(
270
284
  }
271
285
  return { tempDir: sliceOutputDir, tempDirOwned, slicesFile };
272
286
  }
287
+ if (isDosaiDotnetLanguage(language)) {
288
+ console.log(
289
+ `Creating ${sliceType} slice for ${resolve(filePath)} using dosai. Please wait ...`,
290
+ );
291
+ const sliceResult =
292
+ sliceType === "data-flow"
293
+ ? createDosaiDataFlowSlice(
294
+ resolve(filePath),
295
+ resolve(slicesFile),
296
+ options,
297
+ )
298
+ : createDosaiMethodsSlice(
299
+ resolve(filePath),
300
+ resolve(slicesFile),
301
+ options,
302
+ );
303
+ if (!sliceResult) {
304
+ console.warn(
305
+ `Unable to generate ${sliceType} slice using dosai. Check if this is a supported .NET project.`,
306
+ );
307
+ }
308
+ return {
309
+ tempDir: sliceOutputDir,
310
+ tempDirOwned,
311
+ slicesFile,
312
+ };
313
+ }
273
314
  console.log(
274
315
  `Creating ${sliceType} slice for ${resolve(filePath)}. Please wait ...`,
275
316
  );
@@ -393,6 +434,9 @@ export function purlToLanguage(purl, filePath) {
393
434
  case "gem":
394
435
  language = "ruby";
395
436
  break;
437
+ case "nuget":
438
+ language = "csharp";
439
+ break;
396
440
  case "generic":
397
441
  language = "c";
398
442
  }
@@ -474,6 +518,77 @@ export async function analyzeProject(dbObjMap, options) {
474
518
  // Load any existing purl-location information from the sbom.
475
519
  // For eg: cdxgen populates this information for javascript projects
476
520
  let { purlLocationMap, purlImportsMap } = initFromSbom(components, language);
521
+ if (isDosaiDotnetLanguage(language)) {
522
+ if (options.profile === "research") {
523
+ options.withDataFlow = true;
524
+ options.includeCrypto = true;
525
+ }
526
+ if (
527
+ options.usagesSlicesFile &&
528
+ usableSlicesFile(options.usagesSlicesFile)
529
+ ) {
530
+ usageSlice = JSON.parse(
531
+ fs.readFileSync(options.usagesSlicesFile, "utf-8"),
532
+ );
533
+ usagesSlicesFile = options.usagesSlicesFile;
534
+ } else {
535
+ retMap = await createSlice(language, dirPath, "usages", options);
536
+ if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
537
+ usageSlice = JSON.parse(fs.readFileSync(retMap.slicesFile, "utf-8"));
538
+ usagesSlicesFile = retMap.slicesFile;
539
+ }
540
+ }
541
+ if (usageSlice && Object.keys(usageSlice).length) {
542
+ const dosaiEvidence = collectDosaiPurlEvidence(usageSlice, components);
543
+ for (const [purl, locations] of Object.entries(
544
+ dosaiEvidence.purlLocationMap,
545
+ )) {
546
+ purlLocationMap[purl] ??= new Set();
547
+ for (const location of locations) {
548
+ purlLocationMap[purl].add(location);
549
+ }
550
+ }
551
+ servicesMap = collectDosaiServicesFromMethods(usageSlice, servicesMap);
552
+ userDefinedTypesMap = {};
553
+ }
554
+ if (options.withDataFlow) {
555
+ if (
556
+ options.dataFlowSlicesFile &&
557
+ safeExistsSync(options.dataFlowSlicesFile)
558
+ ) {
559
+ dataFlowSlicesFile = options.dataFlowSlicesFile;
560
+ dataFlowSlice = JSON.parse(
561
+ fs.readFileSync(options.dataFlowSlicesFile, "utf-8"),
562
+ );
563
+ } else {
564
+ retMap = await createSlice(language, dirPath, "data-flow", options);
565
+ if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
566
+ dataFlowSlicesFile = retMap.slicesFile;
567
+ dataFlowSlice = JSON.parse(
568
+ fs.readFileSync(retMap.slicesFile, "utf-8"),
569
+ );
570
+ }
571
+ }
572
+ if (dataFlowSlice && Object.keys(dataFlowSlice).length) {
573
+ dataFlowFrames = collectDosaiDataFlowFrames(dataFlowSlice, components);
574
+ }
575
+ }
576
+ if (options.includeCrypto) {
577
+ cryptoComponents = await collectDosaiCryptoComponents(dirPath, options);
578
+ }
579
+ return {
580
+ usagesSlicesFile,
581
+ dataFlowSlicesFile,
582
+ purlLocationMap,
583
+ servicesMap,
584
+ dataFlowFrames,
585
+ tempDir: retMap?.tempDir,
586
+ tempDirOwned: retMap?.tempDirOwned,
587
+ userDefinedTypesMap,
588
+ cryptoComponents,
589
+ cryptoGeneratePurls,
590
+ };
591
+ }
477
592
  // Do reachables first so that usages slicing can reuse the atom file
478
593
  // We need reachables slicing even when trying to infer crypto packages
479
594
  if (options.withReachables || options.includeCrypto) {
@@ -1472,8 +1587,8 @@ export function createEvinseFile(sliceArtefacts, options) {
1472
1587
  properties: servicesMap[serviceName].properties,
1473
1588
  });
1474
1589
  }
1475
- // Add to existing services
1476
- bomJson.services = (bomJson.services || []).concat(services);
1590
+ // Add to existing services while preserving CycloneDX uniqueItems validity
1591
+ bomJson.services = mergeServices(bomJson.services || [], services);
1477
1592
  servicesPresent = true;
1478
1593
  }
1479
1594
  // Add the crypto components to the components list