@cyclonedx/cdxgen 11.4.4 → 11.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +68 -64
  2. package/bin/cdxgen.js +31 -9
  3. package/lib/cli/index.js +336 -99
  4. package/lib/evinser/evinser.js +3 -0
  5. package/lib/evinser/{evinser.test.js → evinser.poku.js} +51 -33
  6. package/lib/evinser/{swiftsem.test.js → swiftsem.poku.js} +46 -45
  7. package/lib/helpers/cbomutils.poku.js +8 -0
  8. package/lib/helpers/{display.test.js → display.poku.js} +2 -2
  9. package/lib/helpers/dotnetutils.js +132 -0
  10. package/lib/helpers/dotnetutils.poku.js +429 -0
  11. package/lib/helpers/envcontext.js +20 -18
  12. package/lib/helpers/{envcontext.test.js → envcontext.poku.js} +24 -24
  13. package/lib/helpers/logger.js +0 -2
  14. package/lib/helpers/{protobom.test.js → protobom.poku.js} +12 -10
  15. package/lib/helpers/utils.js +853 -88
  16. package/lib/helpers/{utils.test.js → utils.poku.js} +1999 -1349
  17. package/lib/helpers/validator.js +10 -0
  18. package/lib/managers/binary.js +89 -11
  19. package/lib/managers/docker.js +47 -37
  20. package/lib/managers/{docker.test.js → docker.poku.js} +70 -59
  21. package/lib/parsers/iri.js +504 -0
  22. package/lib/parsers/iri.poku.js +406 -0
  23. package/lib/server/server.js +130 -7
  24. package/lib/server/server.poku.js +387 -0
  25. package/lib/stages/postgen/{annotator.test.js → annotator.poku.js} +11 -14
  26. package/lib/stages/postgen/{postgen.test.js → postgen.poku.js} +15 -15
  27. package/package.json +34 -16
  28. package/types/cli/index.d.ts +295 -0
  29. package/types/cli/index.d.ts.map +1 -0
  30. package/types/evinser/scalasem.d.ts +6 -0
  31. package/types/evinser/scalasem.d.ts.map +1 -0
  32. package/types/evinser/swiftsem.d.ts +103 -0
  33. package/types/evinser/swiftsem.d.ts.map +1 -0
  34. package/types/helpers/analyzer.d.ts +5 -0
  35. package/types/helpers/analyzer.d.ts.map +1 -0
  36. package/types/helpers/cbomutils.d.ts +15 -0
  37. package/types/helpers/cbomutils.d.ts.map +1 -0
  38. package/types/helpers/db.d.ts +19 -0
  39. package/types/helpers/db.d.ts.map +1 -0
  40. package/types/helpers/display.d.ts +12 -0
  41. package/types/helpers/display.d.ts.map +1 -0
  42. package/types/helpers/dotnetutils.d.ts +45 -0
  43. package/types/helpers/dotnetutils.d.ts.map +1 -0
  44. package/types/helpers/envcontext.d.ts +264 -0
  45. package/types/helpers/envcontext.d.ts.map +1 -0
  46. package/types/helpers/logger.d.ts +12 -0
  47. package/types/helpers/logger.d.ts.map +1 -0
  48. package/types/helpers/protobom.d.ts +3 -0
  49. package/types/helpers/protobom.d.ts.map +1 -0
  50. package/types/helpers/utils.d.ts +1556 -0
  51. package/types/helpers/utils.d.ts.map +1 -0
  52. package/types/helpers/validator.d.ts +11 -0
  53. package/types/helpers/validator.d.ts.map +1 -0
  54. package/types/lib/cli/index.d.ts +8 -1
  55. package/types/lib/cli/index.d.ts.map +1 -1
  56. package/types/lib/evinser/evinser.d.ts +2 -1
  57. package/types/lib/helpers/dotnetutils.d.ts +45 -0
  58. package/types/lib/helpers/dotnetutils.d.ts.map +1 -0
  59. package/types/lib/helpers/envcontext.d.ts +3 -8
  60. package/types/lib/helpers/envcontext.d.ts.map +1 -1
  61. package/types/lib/helpers/logger.d.ts +0 -1
  62. package/types/lib/helpers/logger.d.ts.map +1 -1
  63. package/types/lib/helpers/utils.d.ts +68 -66
  64. package/types/lib/helpers/utils.d.ts.map +1 -1
  65. package/types/lib/helpers/validator.d.ts.map +1 -1
  66. package/types/lib/managers/binary.d.ts.map +1 -1
  67. package/types/lib/managers/docker.d.ts.map +1 -1
  68. package/types/lib/server/server.d.ts +22 -2
  69. package/types/lib/server/server.d.ts.map +1 -1
  70. package/types/managers/binary.d.ts +37 -0
  71. package/types/managers/binary.d.ts.map +1 -0
  72. package/types/managers/docker.d.ts +56 -0
  73. package/types/managers/docker.d.ts.map +1 -0
  74. package/types/managers/oci.d.ts +2 -0
  75. package/types/managers/oci.d.ts.map +1 -0
  76. package/types/managers/piptree.d.ts +2 -0
  77. package/types/managers/piptree.d.ts.map +1 -0
  78. package/types/parsers/iri.d.ts +72 -0
  79. package/types/parsers/iri.d.ts.map +1 -0
  80. package/types/server/server.d.ts +34 -0
  81. package/types/server/server.d.ts.map +1 -0
  82. package/types/stages/postgen/annotator.d.ts +27 -0
  83. package/types/stages/postgen/annotator.d.ts.map +1 -0
  84. package/types/stages/postgen/postgen.d.ts +51 -0
  85. package/types/stages/postgen/postgen.d.ts.map +1 -0
  86. package/types/stages/pregen/pregen.d.ts +59 -0
  87. package/types/stages/pregen/pregen.d.ts.map +1 -0
  88. package/jest.config.js +0 -7
  89. package/lib/helpers/cbomutils.test.js +0 -8
  90. package/lib/server/server.test.js +0 -126
package/lib/cli/index.js CHANGED
@@ -111,6 +111,8 @@ import {
111
111
  parseCsProjAssetsData,
112
112
  parseCsProjData,
113
113
  parseEdnData,
114
+ parseFlakeLock,
115
+ parseFlakeNix,
114
116
  parseGemfileLockData,
115
117
  parseGemspecData,
116
118
  parseGitHubWorkflowData,
@@ -524,29 +526,47 @@ const addFormulationSection = (options, context) => {
524
526
  let environmentVars = gitBranch?.length
525
527
  ? [{ name: "GIT_BRANCH", value: gitBranch }]
526
528
  : [];
529
+ const envPrefixes = [
530
+ "GIT",
531
+ "ANDROID",
532
+ "DENO",
533
+ "DOTNET",
534
+ "JAVA_",
535
+ "SDKMAN",
536
+ "CARGO",
537
+ "CONDA",
538
+ "RUST",
539
+ "GEM_",
540
+ "SCALA_",
541
+ "MAVEN_",
542
+ "GRADLE_",
543
+ "NODE_",
544
+ ];
545
+ const envBlocklist = [
546
+ "key",
547
+ "token",
548
+ "pass",
549
+ "secret",
550
+ "user",
551
+ "email",
552
+ "auth",
553
+ "session",
554
+ "proxy",
555
+ "cred",
556
+ ];
557
+
527
558
  for (const aevar of Object.keys(process.env)) {
559
+ const lower = aevar.toLowerCase();
560
+ const value = process.env[aevar] ?? "";
528
561
  if (
529
- (aevar.startsWith("GIT") ||
530
- aevar.startsWith("ANDROID") ||
531
- aevar.startsWith("DENO") ||
532
- aevar.startsWith("DOTNET") ||
533
- aevar.startsWith("JAVA_") ||
534
- aevar.startsWith("SDKMAN") ||
535
- aevar.startsWith("CARGO") ||
536
- aevar.startsWith("CONDA") ||
537
- aevar.startsWith("RUST")) &&
538
- !aevar.toLowerCase().includes("key") &&
539
- !aevar.toLowerCase().includes("token") &&
540
- !aevar.toLowerCase().includes("pass") &&
541
- !aevar.toLowerCase().includes("secret") &&
542
- !aevar.toLowerCase().includes("user") &&
543
- !aevar.toLowerCase().includes("email") &&
544
- process.env[aevar] &&
545
- process.env[aevar].length
562
+ envPrefixes.some((p) => aevar.startsWith(p)) &&
563
+ !envBlocklist.some((b) => lower.includes(b)) &&
564
+ !envBlocklist.some((b) => value.includes(b)) &&
565
+ value.length
546
566
  ) {
547
567
  environmentVars.push({
548
568
  name: aevar,
549
- value: process.env[aevar],
569
+ value,
550
570
  });
551
571
  }
552
572
  }
@@ -1385,6 +1405,10 @@ export async function createJarBom(path, options) {
1385
1405
  if (!options.exclude) {
1386
1406
  options.exclude = [];
1387
1407
  }
1408
+ // We can look for jar files within node_modules directory
1409
+ if (typeof options.includeNodeModulesDir === "undefined" || options.deep) {
1410
+ options.includeNodeModulesDir = true;
1411
+ }
1388
1412
  // Exclude certain directories during oci sbom generation
1389
1413
  if (hasAnyProjectType(["oci"], options, false)) {
1390
1414
  options.exclude.push("**/android-sdk*/**");
@@ -1751,9 +1775,7 @@ export async function createJavaBom(path, options) {
1751
1775
  `**MAVEN**: Let's use Maven to collect packages from ${basePath}.`,
1752
1776
  );
1753
1777
  if (DEBUG_MODE) {
1754
- console.log(
1755
- `Executing 'mvn ${mvnTreeArgs.join(" ")}' in ${basePath}`,
1756
- );
1778
+ console.log(`Executing 'mvn dependency:tree ...' in ${basePath}`);
1757
1779
  }
1758
1780
  // Prefer the built-in maven
1759
1781
  result = safeSpawnSync(
@@ -2305,7 +2327,7 @@ export async function createJavaBom(path, options) {
2305
2327
  ) {
2306
2328
  bArgs = ["--bazelrc=.bazelrc", "build", bazelTarget];
2307
2329
  }
2308
- console.log("Executing", BAZEL_CMD, bArgs.join(" "), "in", basePath);
2330
+ console.log("Executing", BAZEL_CMD, "in", basePath);
2309
2331
  let result = safeSpawnSync(BAZEL_CMD, bArgs, {
2310
2332
  cwd: basePath,
2311
2333
  shell: true,
@@ -2639,7 +2661,7 @@ export async function createJavaBom(path, options) {
2639
2661
  }
2640
2662
  const millArgs = [...millCommonArgs, "__.ivyDepsTree"];
2641
2663
  if (DEBUG_MODE) {
2642
- console.log("Executing", millCmd, millArgs.join(" "), "in", millRootPath);
2664
+ console.log("Executing", millCmd, "in", millRootPath);
2643
2665
  }
2644
2666
  let sresult = safeSpawnSync(millCmd, millArgs, {
2645
2667
  cwd: millRootPath,
@@ -3761,7 +3783,7 @@ export async function createPythonBom(path, options) {
3761
3783
  // Retrieve the tree using virtualenv in deep mode and as a fallback
3762
3784
  // This is a slow operation
3763
3785
  if ((options.deep || !dependencies.length) && !f.endsWith("uv.lock")) {
3764
- retMap = getPipFrozenTree(basePath, f, tempDir, parentComponent);
3786
+ retMap = await getPipFrozenTree(basePath, f, tempDir, parentComponent);
3765
3787
  if (retMap.pkgList?.length) {
3766
3788
  pkgList = pkgList.concat(retMap.pkgList);
3767
3789
  }
@@ -3839,9 +3861,49 @@ export async function createPythonBom(path, options) {
3839
3861
  console.error("Pipfile.lock not found at", path);
3840
3862
  options.failOnError && process.exit(1);
3841
3863
  }
3842
- } else if (requirementsMode) {
3843
- metadataFilename = "requirements.txt";
3844
- if (reqFiles?.length) {
3864
+ } else if (reqDirFiles?.length) {
3865
+ for (const j in reqDirFiles) {
3866
+ const f = reqDirFiles[j];
3867
+ const dlist = await parseReqFile(f, false);
3868
+ if (dlist?.length) {
3869
+ pkgList = pkgList.concat(dlist);
3870
+ }
3871
+ }
3872
+ metadataFilename = reqDirFiles.join(", ");
3873
+ } else if (reqFiles?.length) {
3874
+ for (const f of reqFiles) {
3875
+ const dlist = await parseReqFile(f, true);
3876
+ if (dlist?.length) {
3877
+ pkgList = pkgList.concat(dlist);
3878
+ }
3879
+ }
3880
+ }
3881
+ }
3882
+
3883
+ const parentDependsOn = new Set();
3884
+
3885
+ // Use atom in requirements, setup.py and pyproject.toml mode
3886
+ if (requirementsMode || setupPyMode || pyProjectMode || options.deep) {
3887
+ /**
3888
+ * The order of preference is pyproject.toml (newer) and then setup.py
3889
+ */
3890
+ if (options.installDeps) {
3891
+ let pkgMap;
3892
+ if (pyProjectMode && !poetryMode) {
3893
+ pkgMap = await getPipFrozenTree(
3894
+ path,
3895
+ pyProjectFile,
3896
+ tempDir,
3897
+ parentComponent,
3898
+ );
3899
+ } else if (setupPyMode) {
3900
+ pkgMap = await getPipFrozenTree(
3901
+ path,
3902
+ setupPy,
3903
+ tempDir,
3904
+ parentComponent,
3905
+ );
3906
+ } else if (requirementsMode && reqFiles?.length) {
3845
3907
  if (options.installDeps && DEBUG_MODE) {
3846
3908
  console.log(
3847
3909
  "cdxgen will now attempt to generate an SBOM for 'build' lifecycle phase for Python. This would take some time ...\nTo speed up this step, invoke cdxgen from within a virtual environment with all the dependencies installed.\nAlternatively, pass the argument '--lifecycle pre-build' to generate a faster but less precise SBOM.",
@@ -3849,13 +3911,8 @@ export async function createPythonBom(path, options) {
3849
3911
  }
3850
3912
  for (const f of reqFiles) {
3851
3913
  const basePath = dirname(f);
3852
- let reqData;
3853
- let frozen = false;
3854
- // Attempt to pip freeze in a virtualenv to improve precision
3855
3914
  if (options.installDeps) {
3856
- // If there are multiple requirements files then the tree is getting constructed for each one
3857
- // adding to the delay.
3858
- const pkgMap = getPipFrozenTree(
3915
+ const pkgMap = await getPipFrozenTree(
3859
3916
  basePath,
3860
3917
  f,
3861
3918
  tempDir,
@@ -3863,10 +3920,11 @@ export async function createPythonBom(path, options) {
3863
3920
  );
3864
3921
  if (pkgMap.pkgList?.length) {
3865
3922
  pkgList = pkgList.concat(pkgMap.pkgList);
3866
- frozen = pkgMap.frozen;
3923
+ pkgList = trimComponents(pkgList);
3867
3924
  }
3868
3925
  if (pkgMap.formulationList?.length) {
3869
3926
  formulationList = formulationList.concat(pkgMap.formulationList);
3927
+ formulationList = trimComponents(formulationList);
3870
3928
  }
3871
3929
  if (pkgMap.dependenciesList) {
3872
3930
  dependencies = mergeDependencies(
@@ -3875,61 +3933,31 @@ export async function createPythonBom(path, options) {
3875
3933
  parentComponent,
3876
3934
  );
3877
3935
  }
3878
- }
3879
- // Fallback to parsing manually
3880
- if (!pkgList.length || !frozen) {
3881
- thoughtLog(
3882
- `Manually parsing ${f}. The result would include only direct dependencies.`,
3883
- );
3884
- if (DEBUG_MODE) {
3885
- console.log(
3886
- `Manually parsing ${f}. The result would include only direct dependencies.`,
3936
+ // Add the root packages from this file to the parent's dependencies
3937
+ for (const p of pkgMap.rootList) {
3938
+ if (
3939
+ parentComponent &&
3940
+ p.name === parentComponent.name &&
3941
+ (p.version === parentComponent.version ||
3942
+ p.version === "latest")
3943
+ ) {
3944
+ continue;
3945
+ }
3946
+ parentDependsOn.add(
3947
+ `pkg:pypi/${p.name.toLowerCase()}@${p.version}`,
3887
3948
  );
3888
3949
  }
3889
- reqData = readFileSync(f, { encoding: "utf-8" });
3890
- const dlist = await parseReqFile(reqData, true);
3891
- if (dlist?.length) {
3892
- pkgList = pkgList.concat(dlist);
3893
- }
3894
- }
3895
- } // for
3896
- metadataFilename = reqFiles.join(", ");
3897
- } else if (reqDirFiles?.length) {
3898
- for (const j in reqDirFiles) {
3899
- const f = reqDirFiles[j];
3900
- const reqData = readFileSync(f, { encoding: "utf-8" });
3901
- const dlist = await parseReqFile(reqData, false);
3902
- if (dlist?.length) {
3903
- pkgList = pkgList.concat(dlist);
3904
3950
  }
3905
3951
  }
3906
- metadataFilename = reqDirFiles.join(", ");
3907
- }
3908
- }
3909
- }
3910
- // Use atom in requirements, setup.py and pyproject.toml mode
3911
- if (requirementsMode || setupPyMode || pyProjectMode || options.deep) {
3912
- /**
3913
- * The order of preference is pyproject.toml (newer) and then setup.py
3914
- */
3915
- if (options.installDeps) {
3916
- let pkgMap;
3917
- if (pyProjectMode && !poetryMode) {
3918
- pkgMap = getPipFrozenTree(
3952
+ } else if (!poetryMode) {
3953
+ pkgMap = await getPipFrozenTree(
3919
3954
  path,
3920
- pyProjectFile,
3955
+ undefined,
3921
3956
  tempDir,
3922
3957
  parentComponent,
3923
3958
  );
3924
- } else if (setupPyMode) {
3925
- pkgMap = getPipFrozenTree(path, setupPy, tempDir, parentComponent);
3926
- } else if (!poetryMode) {
3927
- pkgMap = getPipFrozenTree(path, undefined, tempDir, parentComponent);
3928
3959
  }
3929
3960
 
3930
- // Get the imported modules and a dedupe list of packages
3931
- const parentDependsOn = new Set();
3932
-
3933
3961
  // ATOM parsedeps block
3934
3962
  // Atom parsedeps slices can be used to identify packages that are not declared in manifests
3935
3963
  // Since it is a slow operation, we only use it as a fallback or in deep mode
@@ -4253,11 +4281,21 @@ export async function createGoBom(path, options) {
4253
4281
  }
4254
4282
  if (gomodFiles.length) {
4255
4283
  let shouldManuallyParse = false;
4284
+ // Sort go.mod files by depth (shallowest first) to prioritize root modules
4285
+ const sortedGomodFiles = gomodFiles.sort((a, b) => {
4286
+ const relativePathA = relative(path, a);
4287
+ const relativePathB = relative(path, b);
4288
+ const depthA = relativePathA.split("/").length;
4289
+ const depthB = relativePathB.split("/").length;
4290
+ return depthA - depthB;
4291
+ });
4292
+
4293
+ let rootParentComponent = null;
4256
4294
  // Use the go list -deps and go mod why commands to generate a good quality BOM for non-docker invocations
4257
4295
  if (
4258
4296
  !hasAnyProjectType(["docker", "oci", "container", "os"], options, false)
4259
4297
  ) {
4260
- for (const f of gomodFiles) {
4298
+ for (const f of sortedGomodFiles) {
4261
4299
  const basePath = dirname(f);
4262
4300
  // Ignore vendor packages
4263
4301
  if (
@@ -4307,13 +4345,23 @@ export async function createGoBom(path, options) {
4307
4345
  if (retMap.pkgList?.length) {
4308
4346
  pkgList = pkgList.concat(retMap.pkgList);
4309
4347
  }
4310
- // We treat the main module as our parent
4348
+ // Prioritize the shallowest module as the root component
4311
4349
  if (
4312
4350
  retMap.parentComponent &&
4313
4351
  Object.keys(retMap.parentComponent).length
4314
4352
  ) {
4315
- parentComponent = retMap.parentComponent;
4316
- parentComponent.type = "application";
4353
+ if (!rootParentComponent) {
4354
+ // First (shallowest) module becomes the root
4355
+ rootParentComponent = retMap.parentComponent;
4356
+ rootParentComponent.type = "application";
4357
+ parentComponent = rootParentComponent;
4358
+ } else {
4359
+ // Subsequent modules become subcomponents
4360
+ if (!parentComponent.components) {
4361
+ parentComponent.components = [];
4362
+ }
4363
+ parentComponent.components.push(retMap.parentComponent);
4364
+ }
4317
4365
  }
4318
4366
  if (DEBUG_MODE) {
4319
4367
  console.log("Executing go mod graph in", basePath);
@@ -4399,11 +4447,14 @@ export async function createGoBom(path, options) {
4399
4447
  parentComponent,
4400
4448
  );
4401
4449
  }
4402
- // Retain the parent component hierarchy
4450
+ // Retain the parent component hierarchy, prioritizing the shallowest module
4403
4451
  if (Object.keys(retMap.parentComponent).length) {
4404
- if (gomodFiles.length === 1) {
4405
- parentComponent = retMap.parentComponent;
4452
+ if (!rootParentComponent) {
4453
+ // First (shallowest) module becomes the root
4454
+ rootParentComponent = retMap.parentComponent;
4455
+ parentComponent = rootParentComponent;
4406
4456
  } else {
4457
+ // Subsequent modules become subcomponents
4407
4458
  parentComponent.components = parentComponent.components || [];
4408
4459
  parentComponent.components.push(retMap.parentComponent);
4409
4460
  }
@@ -4429,7 +4480,7 @@ export async function createGoBom(path, options) {
4429
4480
  dependencies,
4430
4481
  parentComponent,
4431
4482
  src: path,
4432
- filename: gomodFiles.join(", "),
4483
+ filename: sortedGomodFiles.join(", "),
4433
4484
  });
4434
4485
  }
4435
4486
  }
@@ -4441,7 +4492,7 @@ export async function createGoBom(path, options) {
4441
4492
  "Manually parsing go.mod files. The resultant BOM would be incomplete.",
4442
4493
  );
4443
4494
  }
4444
- for (const f of gomodFiles) {
4495
+ for (const f of sortedGomodFiles) {
4445
4496
  if (DEBUG_MODE) {
4446
4497
  console.log(`Parsing ${f}`);
4447
4498
  }
@@ -4450,11 +4501,14 @@ export async function createGoBom(path, options) {
4450
4501
  if (retMap?.pkgList?.length) {
4451
4502
  pkgList = pkgList.concat(retMap.pkgList);
4452
4503
  }
4453
- // Retain the parent component hierarchy
4504
+ // Retain the parent component hierarchy, prioritizing the shallowest module
4454
4505
  if (Object.keys(retMap.parentComponent).length) {
4455
- if (gomodFiles.length === 1) {
4456
- parentComponent = retMap.parentComponent;
4506
+ if (!rootParentComponent) {
4507
+ // First (shallowest) module becomes the root
4508
+ rootParentComponent = retMap.parentComponent;
4509
+ parentComponent = rootParentComponent;
4457
4510
  } else {
4511
+ // Subsequent modules become subcomponents
4458
4512
  parentComponent.components = parentComponent.components || [];
4459
4513
  parentComponent.components.push(retMap.parentComponent);
4460
4514
  }
@@ -4479,7 +4533,7 @@ export async function createGoBom(path, options) {
4479
4533
  src: path,
4480
4534
  dependencies,
4481
4535
  parentComponent,
4482
- filename: gomodFiles.join(", "),
4536
+ filename: sortedGomodFiles.join(", "),
4483
4537
  });
4484
4538
  }
4485
4539
  if (gopkgLockFiles.length) {
@@ -4927,7 +4981,7 @@ export function createClojureBom(path, options) {
4927
4981
  console.log(`Parsing ${f}`);
4928
4982
  }
4929
4983
  const basePath = dirname(f);
4930
- console.log("Executing", LEIN_CMD, LEIN_ARGS.join(" "), "in", basePath);
4984
+ console.log("Executing", LEIN_CMD, "in", basePath);
4931
4985
  const result = safeSpawnSync(LEIN_CMD, LEIN_ARGS, {
4932
4986
  cwd: basePath,
4933
4987
  encoding: "utf-8",
@@ -4976,7 +5030,7 @@ export function createClojureBom(path, options) {
4976
5030
  }
4977
5031
  for (const f of ednFiles) {
4978
5032
  const basePath = dirname(f);
4979
- console.log("Executing", CLJ_CMD, CLJ_ARGS.join(" "), "in", basePath);
5033
+ console.log("Executing", CLJ_CMD, "in", basePath);
4980
5034
  const result = safeSpawnSync(CLJ_CMD, CLJ_ARGS, {
4981
5035
  cwd: basePath,
4982
5036
  encoding: "utf-8",
@@ -5584,6 +5638,128 @@ export async function createCocoaBom(path, options) {
5584
5638
  }
5585
5639
  }
5586
5640
 
5641
+ /**
5642
+ * Function to create bom string for Nix flakes
5643
+ *
5644
+ * @param {string} path to the project
5645
+ * @param {Object} options Parse options from the cli
5646
+ */
5647
+ export async function createNixBom(path, options) {
5648
+ let pkgList = [];
5649
+ let dependencies = [];
5650
+ let parentComponent = {};
5651
+
5652
+ const flakeNixFiles = getAllFiles(
5653
+ path,
5654
+ `${options.multiProject ? "**/" : ""}flake.nix`,
5655
+ options,
5656
+ );
5657
+ const flakeLockFiles = getAllFiles(
5658
+ path,
5659
+ `${options.multiProject ? "**/" : ""}flake.lock`,
5660
+ options,
5661
+ );
5662
+
5663
+ for (const flakeNixFile of flakeNixFiles) {
5664
+ if (DEBUG_MODE) {
5665
+ console.log(`Parsing ${flakeNixFile}`);
5666
+ }
5667
+ const { pkgList: nixPkgs, dependencies: nixDeps } =
5668
+ parseFlakeNix(flakeNixFile);
5669
+ if (nixPkgs?.length) {
5670
+ pkgList = pkgList.concat(nixPkgs);
5671
+ }
5672
+ if (nixDeps?.length) {
5673
+ dependencies = dependencies.concat(nixDeps);
5674
+ }
5675
+ }
5676
+
5677
+ for (const flakeLockFile of flakeLockFiles) {
5678
+ if (DEBUG_MODE) {
5679
+ console.log(`Parsing ${flakeLockFile}`);
5680
+ }
5681
+ const { pkgList: lockPkgs, dependencies: lockDeps } =
5682
+ parseFlakeLock(flakeLockFile);
5683
+ if (lockPkgs?.length) {
5684
+ const mergedPkgs = [];
5685
+ const existingNames = new Set();
5686
+
5687
+ for (const lockPkg of lockPkgs) {
5688
+ mergedPkgs.push(lockPkg);
5689
+ existingNames.add(lockPkg.name);
5690
+ }
5691
+
5692
+ for (const nixPkg of pkgList) {
5693
+ if (!existingNames.has(nixPkg.name)) {
5694
+ mergedPkgs.push(nixPkg);
5695
+ }
5696
+ }
5697
+
5698
+ pkgList = mergedPkgs;
5699
+ }
5700
+ if (lockDeps?.length) {
5701
+ dependencies = dependencies.concat(lockDeps);
5702
+ }
5703
+
5704
+ // Create parent component from flake.lock if found
5705
+ if (!Object.keys(parentComponent).length) {
5706
+ const flakeDir = dirname(flakeLockFile);
5707
+ const projectName = basename(flakeDir);
5708
+ parentComponent = {
5709
+ type: "application",
5710
+ name: projectName,
5711
+ version: "latest",
5712
+ description: `Nix flake project: ${projectName}`,
5713
+ "bom-ref": `pkg:nix/${projectName}@latest`,
5714
+ properties: [
5715
+ {
5716
+ name: "SrcFile",
5717
+ value: flakeLockFile,
5718
+ },
5719
+ {
5720
+ name: "cdx:nix:flake_dir",
5721
+ value: flakeDir,
5722
+ },
5723
+ ],
5724
+ };
5725
+ }
5726
+ }
5727
+
5728
+ // If no parent component was created from flake.lock, create one from flake.nix
5729
+ if (!Object.keys(parentComponent).length && flakeNixFiles.length > 0) {
5730
+ const flakeDir = dirname(flakeNixFiles[0]);
5731
+ const projectName = basename(flakeDir);
5732
+ parentComponent = {
5733
+ type: "application",
5734
+ name: projectName,
5735
+ version: "latest",
5736
+ description: `Nix flake project: ${projectName}`,
5737
+ "bom-ref": `pkg:nix/${projectName}@latest`,
5738
+ properties: [
5739
+ {
5740
+ name: "SrcFile",
5741
+ value: flakeNixFiles[0],
5742
+ },
5743
+ {
5744
+ name: "cdx:nix:flake_dir",
5745
+ value: flakeDir,
5746
+ },
5747
+ ],
5748
+ };
5749
+ }
5750
+
5751
+ if (pkgList.length || Object.keys(parentComponent).length) {
5752
+ return buildBomNSData(options, pkgList, "nix", {
5753
+ src: path,
5754
+ filename: [...flakeNixFiles, ...flakeLockFiles].join(", "),
5755
+ dependencies,
5756
+ parentComponent,
5757
+ });
5758
+ }
5759
+
5760
+ return {};
5761
+ }
5762
+
5587
5763
  /**
5588
5764
  * Function to create bom string for docker compose
5589
5765
  *
@@ -5921,6 +6097,10 @@ export async function createContainerSpecLikeBom(path, options) {
5921
6097
  export function createPHPBom(path, options) {
5922
6098
  let dependencies = [];
5923
6099
  let parentComponent = {};
6100
+ // We can look for composer files within node_modules directory
6101
+ if (typeof options.includeNodeModulesDir === "undefined" || options.deep) {
6102
+ options.includeNodeModulesDir = true;
6103
+ }
5924
6104
  const composerJsonFiles = getAllFiles(
5925
6105
  path,
5926
6106
  `${options.multiProject ? "**/" : ""}composer.json`,
@@ -6069,6 +6249,10 @@ export function createPHPBom(path, options) {
6069
6249
  * @param {Object} options Parse options from the cli
6070
6250
  */
6071
6251
  export async function createRubyBom(path, options) {
6252
+ // We can look for gem files within node_modules directory
6253
+ if (typeof options.includeNodeModulesDir === "undefined" || options.deep) {
6254
+ options.includeNodeModulesDir = true;
6255
+ }
6072
6256
  const excludeList = (options.exclude || []).concat(["**/vendor/cache/**"]);
6073
6257
  const gemLockExcludeList = (options.exclude || []).concat([
6074
6258
  "**/vendor/bundle/ruby/**/Gemfile.lock",
@@ -6495,7 +6679,7 @@ export async function createCsharpBom(path, options) {
6495
6679
  console.log(`Parsing ${nf}`);
6496
6680
  }
6497
6681
  const retMap = await parseNupkg(nf);
6498
- if (retMap?.pkgList.length) {
6682
+ if (retMap?.pkgList?.length) {
6499
6683
  pkgList = pkgList.concat(retMap.pkgList);
6500
6684
  for (const d of retMap.pkgList) {
6501
6685
  parentDependsOn.add(d["bom-ref"]);
@@ -6861,8 +7045,8 @@ export function trimComponents(components) {
6861
7045
  }
6862
7046
  // comp.evidence.identity can be an array or object
6863
7047
  // Merge the evidence.identity based on methods or objects
6864
- const isArray = Array.isArray(comp.evidence.identity);
6865
- const identities = isArray
7048
+ const isIdentityArray = Array.isArray(comp.evidence.identity);
7049
+ const identities = isIdentityArray
6866
7050
  ? comp.evidence.identity
6867
7051
  : [comp.evidence.identity];
6868
7052
  for (const aident of identities) {
@@ -6893,9 +7077,23 @@ export function trimComponents(components) {
6893
7077
  existingComponent.evidence.identity.push(aident);
6894
7078
  }
6895
7079
  }
6896
- if (!isArray) {
7080
+ if (!isIdentityArray) {
7081
+ const firstIdentity = existingComponent.evidence.identity[0];
7082
+ let identConfidence = firstIdentity?.confidence;
7083
+ // We need to set the confidence to the max of all confidences
7084
+ if (firstIdentity?.methods?.length > 1) {
7085
+ for (const aidentMethod of firstIdentity.methods) {
7086
+ if (
7087
+ aidentMethod?.confidence &&
7088
+ aidentMethod.confidence > identConfidence
7089
+ ) {
7090
+ identConfidence = aidentMethod.confidence;
7091
+ }
7092
+ }
7093
+ }
7094
+ firstIdentity.confidence = identConfidence;
6897
7095
  existingComponent.evidence = {
6898
- identity: existingComponent.evidence.identity[0],
7096
+ identity: firstIdentity,
6899
7097
  };
6900
7098
  }
6901
7099
  }
@@ -7635,6 +7833,27 @@ export async function createMultiXBom(pathList, options) {
7635
7833
  }
7636
7834
  }
7637
7835
  }
7836
+ if (hasAnyProjectType(["oci", "nix"], options)) {
7837
+ bomData = await createNixBom(path, options);
7838
+ if (bomData?.bomJson?.components?.length) {
7839
+ if (DEBUG_MODE) {
7840
+ console.log(
7841
+ `Found ${bomData.bomJson.components.length} Nix flake packages at ${path}`,
7842
+ );
7843
+ }
7844
+ components = components.concat(bomData.bomJson.components);
7845
+ dependencies = mergeDependencies(
7846
+ dependencies,
7847
+ bomData.bomJson.dependencies,
7848
+ );
7849
+ if (
7850
+ bomData.parentComponent &&
7851
+ Object.keys(bomData.parentComponent).length
7852
+ ) {
7853
+ parentSubComponents.push(bomData.parentComponent);
7854
+ }
7855
+ }
7856
+ }
7638
7857
  // Collect any crypto keys
7639
7858
  if (options.specVersion >= 1.6 && options.includeCrypto) {
7640
7859
  if (!hasAnyProjectType(["oci"], options, false)) {
@@ -8063,6 +8282,21 @@ export async function createXBom(path, options) {
8063
8282
  if (cocoaFiles.length) {
8064
8283
  return await createCocoaBom(path, options);
8065
8284
  }
8285
+
8286
+ // Nix flakes
8287
+ const flakeNixFiles = getAllFiles(
8288
+ path,
8289
+ `${options.multiProject ? "**/" : ""}flake.nix`,
8290
+ options,
8291
+ );
8292
+ const flakeLockFiles = getAllFiles(
8293
+ path,
8294
+ `${options.multiProject ? "**/" : ""}flake.lock`,
8295
+ options,
8296
+ );
8297
+ if (flakeNixFiles.length || flakeLockFiles.length) {
8298
+ return await createNixBom(path, options);
8299
+ }
8066
8300
  }
8067
8301
 
8068
8302
  /**
@@ -8308,6 +8542,9 @@ export async function createBom(path, options) {
8308
8542
  if (PROJECT_TYPE_ALIASES["cocoa"].includes(projectType[0])) {
8309
8543
  return await createCocoaBom(path, options);
8310
8544
  }
8545
+ if (PROJECT_TYPE_ALIASES["nix"].includes(projectType[0])) {
8546
+ return await createNixBom(path, options);
8547
+ }
8311
8548
  switch (projectType[0]) {
8312
8549
  case "jar":
8313
8550
  return createJarBom(path, options);
@@ -944,6 +944,9 @@ export function parseSemanticSlices(language, components, semanticsSlice) {
944
944
  if (language === "scala") {
945
945
  return findPurlLocations(components, semanticsSlice);
946
946
  }
947
+ if (!components) {
948
+ return undefined;
949
+ }
947
950
  const componentNamePurlMap = {};
948
951
  const componentSymbolsMap = {};
949
952
  const allObfuscationsMap = {};