@cyclonedx/cdxgen 12.1.3 → 12.1.5

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 (107) hide show
  1. package/README.md +1 -1
  2. package/bin/cdxgen.js +12 -0
  3. package/bin/repl.js +2 -2
  4. package/lib/cli/index.js +164 -71
  5. package/lib/evinser/evinser.js +3 -4
  6. package/lib/evinser/swiftsem.js +1 -1
  7. package/lib/helpers/caxa.js +1 -1
  8. package/lib/helpers/display.js +6 -10
  9. package/lib/helpers/envcontext.js +5 -5
  10. package/lib/helpers/pythonutils.js +296 -0
  11. package/lib/helpers/pythonutils.poku.js +469 -0
  12. package/lib/helpers/utils.js +303 -95
  13. package/lib/helpers/utils.poku.js +84 -1
  14. package/lib/managers/piptree.js +1 -1
  15. package/lib/parsers/npmrc.js +88 -0
  16. package/lib/parsers/npmrc.poku.js +492 -0
  17. package/lib/server/openapi.yaml +0 -9
  18. package/lib/server/server.js +18 -5
  19. package/lib/stages/pregen/env-audit.js +34 -0
  20. package/lib/stages/pregen/env-audit.poku.js +290 -0
  21. package/lib/third-party/arborist/lib/deepest-nesting-target.js +1 -1
  22. package/lib/third-party/arborist/lib/node.js +3 -3
  23. package/lib/third-party/arborist/lib/shrinkwrap.js +1 -1
  24. package/lib/third-party/arborist/lib/tree-check.js +1 -1
  25. package/package.json +6 -6
  26. package/types/lib/cli/index.d.ts +39 -39
  27. package/types/lib/cli/index.d.ts.map +1 -1
  28. package/types/lib/evinser/evinser.d.ts +19 -19
  29. package/types/lib/evinser/evinser.d.ts.map +1 -1
  30. package/types/lib/evinser/swiftsem.d.ts +14 -14
  31. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  32. package/types/lib/helpers/cbomutils.d.ts +1 -1
  33. package/types/lib/helpers/cbomutils.d.ts.map +1 -1
  34. package/types/lib/helpers/db.d.ts +2 -2
  35. package/types/lib/helpers/db.d.ts.map +1 -1
  36. package/types/lib/helpers/display.d.ts +2 -2
  37. package/types/lib/helpers/display.d.ts.map +1 -1
  38. package/types/lib/helpers/envcontext.d.ts +14 -14
  39. package/types/lib/helpers/envcontext.d.ts.map +1 -1
  40. package/types/lib/helpers/logger.d.ts +1 -1
  41. package/types/lib/helpers/logger.d.ts.map +1 -1
  42. package/types/lib/helpers/protobom.d.ts +4 -2
  43. package/types/lib/helpers/protobom.d.ts.map +1 -1
  44. package/types/lib/helpers/pythonutils.d.ts +9 -0
  45. package/types/lib/helpers/pythonutils.d.ts.map +1 -0
  46. package/types/lib/helpers/utils.d.ts +103 -88
  47. package/types/lib/helpers/utils.d.ts.map +1 -1
  48. package/types/lib/managers/binary.d.ts +2 -2
  49. package/types/lib/managers/binary.d.ts.map +1 -1
  50. package/types/lib/managers/docker.d.ts +2 -2
  51. package/types/lib/managers/docker.d.ts.map +1 -1
  52. package/types/lib/managers/oci.d.ts +1 -1
  53. package/types/lib/managers/oci.d.ts.map +1 -1
  54. package/types/lib/managers/piptree.d.ts +1 -1
  55. package/types/lib/managers/piptree.d.ts.map +1 -1
  56. package/types/lib/parsers/iri.d.ts +6 -6
  57. package/types/lib/parsers/iri.d.ts.map +1 -1
  58. package/types/lib/parsers/npmrc.d.ts +23 -0
  59. package/types/lib/parsers/npmrc.d.ts.map +1 -0
  60. package/types/lib/server/server.d.ts +1 -1
  61. package/types/lib/server/server.d.ts.map +1 -1
  62. package/types/lib/stages/postgen/annotator.d.ts +3 -3
  63. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  64. package/types/lib/stages/postgen/postgen.d.ts +5 -5
  65. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  66. package/types/lib/stages/pregen/env-audit.d.ts +2 -0
  67. package/types/lib/stages/pregen/env-audit.d.ts.map +1 -0
  68. package/types/lib/stages/pregen/pregen.d.ts +6 -6
  69. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  70. package/types/lib/third-party/arborist/lib/arborist/index.d.ts +4 -3
  71. package/types/lib/third-party/arborist/lib/arborist/index.d.ts.map +1 -1
  72. package/types/lib/third-party/arborist/lib/can-place-dep.d.ts +5 -5
  73. package/types/lib/third-party/arborist/lib/can-place-dep.d.ts.map +1 -1
  74. package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts +4 -4
  75. package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts.map +1 -1
  76. package/types/lib/third-party/arborist/lib/diff.d.ts +3 -3
  77. package/types/lib/third-party/arborist/lib/diff.d.ts.map +1 -1
  78. package/types/lib/third-party/arborist/lib/edge.d.ts +2 -2
  79. package/types/lib/third-party/arborist/lib/edge.d.ts.map +1 -1
  80. package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts +1 -1
  81. package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts.map +1 -1
  82. package/types/lib/third-party/arborist/lib/inventory.d.ts +3 -2
  83. package/types/lib/third-party/arborist/lib/inventory.d.ts.map +1 -1
  84. package/types/lib/third-party/arborist/lib/link.d.ts +10 -7
  85. package/types/lib/third-party/arborist/lib/link.d.ts.map +1 -1
  86. package/types/lib/third-party/arborist/lib/node.d.ts +8 -8
  87. package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -1
  88. package/types/lib/third-party/arborist/lib/optional-set.d.ts +1 -1
  89. package/types/lib/third-party/arborist/lib/optional-set.d.ts.map +1 -1
  90. package/types/lib/third-party/arborist/lib/override-set.d.ts +3 -3
  91. package/types/lib/third-party/arborist/lib/override-set.d.ts.map +1 -1
  92. package/types/lib/third-party/arborist/lib/peer-entry-sets.d.ts +1 -1
  93. package/types/lib/third-party/arborist/lib/peer-entry-sets.d.ts.map +1 -1
  94. package/types/lib/third-party/arborist/lib/place-dep.d.ts +3 -3
  95. package/types/lib/third-party/arborist/lib/place-dep.d.ts.map +1 -1
  96. package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts +7 -7
  97. package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts.map +1 -1
  98. package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts +1 -1
  99. package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts.map +1 -1
  100. package/types/lib/third-party/arborist/lib/yarn-lock.d.ts +4 -3
  101. package/types/lib/third-party/arborist/lib/yarn-lock.d.ts.map +1 -1
  102. package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts +0 -34
  103. package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts.map +0 -1
  104. package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts +0 -24
  105. package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts.map +0 -1
  106. package/types/lib/third-party/arborist/lib/tracker.d.ts +0 -13
  107. package/types/lib/third-party/arborist/lib/tracker.d.ts.map +0 -1
package/README.md CHANGED
@@ -104,7 +104,7 @@ docker run --rm -e CDXGEN_DEBUG_MODE=debug -v /tmp:/tmp -v $(pwd):/app:rw -t ghc
104
104
  In deno applications, cdxgen could be directly imported without any conversion. Please see the section on [integration as a library](#integration-as-library)
105
105
 
106
106
  ```ts
107
- import { createBom, submitBom } from "npm:@cyclonedx/cdxgen@^11.0.0";
107
+ import { createBom, submitBom } from "npm:@cyclonedx/cdxgen@^12.1.0";
108
108
  ```
109
109
 
110
110
  ## Getting Help
package/bin/cdxgen.js CHANGED
@@ -42,6 +42,7 @@ import {
42
42
  } from "../lib/helpers/utils.js";
43
43
  import { validateBom } from "../lib/helpers/validator.js";
44
44
  import { postProcess } from "../lib/stages/postgen/postgen.js";
45
+ import { auditEnvironment } from "../lib/stages/pregen/env-audit.js";
45
46
  import { prepareEnv } from "../lib/stages/pregen/pregen.js";
46
47
 
47
48
  // Support for config files
@@ -840,6 +841,17 @@ const needsBomSigning = ({ generateKeyAndSign }) =>
840
841
  (async () => {
841
842
  // Display the sponsor banner
842
843
  printSponsorBanner(options);
844
+ // Our quest to audit and check the SBOM generation environment to prevent our users from getting exploited
845
+ // during SBOM generation.
846
+ const envWarnings = auditEnvironment();
847
+ if (envWarnings?.length) {
848
+ for (const w of envWarnings) {
849
+ console.log(`SECURE MODE: ${w}`);
850
+ }
851
+ if (isSecureMode) {
852
+ process.exit(1);
853
+ }
854
+ }
843
855
  // Start SBOM server
844
856
  if (options.server) {
845
857
  const serverModule = await import("../lib/server/server.js");
package/bin/repl.js CHANGED
@@ -594,7 +594,7 @@ cdxgenRepl.defineCommand("osinfocategories", {
594
594
  cdxgenRepl.defineCommand("licenses", {
595
595
  help: "visualize license distribution",
596
596
  async action() {
597
- if (!sbom || !sbom.components) {
597
+ if (!sbom?.components) {
598
598
  console.log("⚠ No SBOM loaded.");
599
599
  this.displayPrompt();
600
600
  return;
@@ -649,7 +649,7 @@ cdxgenRepl.defineCommand("inspect", {
649
649
  cdxgenRepl.defineCommand("tagcloud", {
650
650
  help: "generate a text/tag cloud based on component descriptions and tags",
651
651
  action() {
652
- if (!sbom || !sbom.components) {
652
+ if (!sbom?.components) {
653
653
  console.log("⚠ No SBOM loaded.");
654
654
  this.displayPrompt();
655
655
  return;
package/lib/cli/index.js CHANGED
@@ -28,6 +28,7 @@ import { parseCaxaMetadata } from "../helpers/caxa.js";
28
28
  import { collectOSCryptoLibs } from "../helpers/cbomutils.js";
29
29
  import {
30
30
  collectEnvInfo,
31
+ GIT_COMMAND,
31
32
  getBranch,
32
33
  getOriginUrl,
33
34
  gitTreeHashes,
@@ -188,6 +189,7 @@ import {
188
189
  getPkgPathList,
189
190
  parseImageName,
190
191
  } from "../managers/docker.js";
192
+ import { DEFAULT_NPMRC_BLOCKLIST, parseNpmrc } from "../parsers/npmrc.js";
191
193
 
192
194
  const dirName = dirNameStr;
193
195
 
@@ -2879,17 +2881,17 @@ export async function createNodejsBom(path, options) {
2879
2881
  );
2880
2882
  const npmInstallCount =
2881
2883
  Number.parseInt(process.env.NPM_INSTALL_COUNT, 10) || 2;
2884
+ let anyInstallSuccess = false;
2882
2885
  // Automatic npm install logic.
2883
2886
  // Only perform npm install for smaller projects (< 2 package.json) without the correct number of lock files
2884
2887
  if (
2885
2888
  (pkgJsonLockFiles?.length === 0 ||
2886
- pkgJsonLockFiles?.length < pkgJsonFiles?.length) &&
2889
+ pkgJsonLockFiles?.length < pkgJsonFiles?.length - 1) &&
2887
2890
  yarnLockFiles?.length === 0 &&
2888
2891
  pnpmLockFiles?.length === 0 &&
2889
2892
  pkgJsonFiles?.length <= npmInstallCount &&
2890
2893
  options.installDeps
2891
2894
  ) {
2892
- let anyInstallSuccess = false;
2893
2895
  for (const apkgJson of pkgJsonFiles) {
2894
2896
  let pkgMgr = "npm";
2895
2897
  const supPkgMgrs = ["npm", "yarn", "yarnpkg", "pnpm", "pnpx"];
@@ -2930,21 +2932,58 @@ export async function createNodejsBom(path, options) {
2930
2932
  process.env[`${pkgMgr.toUpperCase()}_INSTALL_ARGS`].split(" ");
2931
2933
  installArgs = installArgs.concat(addArgs);
2932
2934
  }
2933
- if (pkgMgr === "npm" && isSecureMode) {
2935
+ // Always invoke the install command with ignore-scripts to guard against version spoofing
2936
+ if (["npm", "pnpm", "yarn"].includes(pkgMgr)) {
2934
2937
  if (!installArgs.includes("--ignore-scripts")) {
2935
2938
  installArgs.push("--ignore-scripts");
2936
2939
  }
2937
- if (!installArgs.includes("--no-audit")) {
2938
- installArgs.push("--no-audit");
2940
+ if (pkgMgr === "pnpm") {
2941
+ installArgs.push("--ignore-pnpmfile");
2939
2942
  }
2943
+ if (pkgMgr === "npm") {
2944
+ for (const c of ["--no-audit", "--no-bin-links"]) {
2945
+ if (!installArgs.includes(c)) {
2946
+ installArgs.push(c);
2947
+ }
2948
+ }
2949
+ installArgs.push(`--git=${GIT_COMMAND}`);
2950
+ }
2951
+ }
2952
+ if (
2953
+ pkgMgr === "npm" &&
2954
+ isSecureMode &&
2955
+ !installArgs.join(" ").includes("--allow-git")
2956
+ ) {
2957
+ console.log(
2958
+ "Consider passing '--allow-git=none' via the environment variable NPM_INSTALL_ARGS to prevent any git dependencies from being fetched and installed via npm.",
2959
+ );
2940
2960
  }
2941
2961
  const basePath = dirname(apkgJson);
2962
+ let npmrcData;
2963
+ if (safeExistsSync(join(basePath, ".npmrc"))) {
2964
+ thoughtLog(
2965
+ "Wait, there is a .npmrc file here! I'm going to check if it has anything malicious.",
2966
+ );
2967
+ npmrcData = readFileSync(join(basePath, ".npmrc"), "utf-8");
2968
+ const npmrcObj = parseNpmrc(npmrcData);
2969
+ for (const [key, value] of Object.entries(npmrcObj)) {
2970
+ const baseKey = key.replace(/^(?:\/\/[^/]+\/|@[^:]+:)/, "");
2971
+ if (
2972
+ DEFAULT_NPMRC_BLOCKLIST.has(baseKey) ||
2973
+ DEFAULT_NPMRC_BLOCKLIST.has(key)
2974
+ ) {
2975
+ console.warn(
2976
+ `\x1b[1;35mSECURE MODE: Dangerous configuration ${key}=${value} detected in .npmrc! Verify if this is a trusted project. Remove this setting or any other problematic configurations to proceed.\x1b[0m`,
2977
+ );
2978
+ process.exit(1);
2979
+ }
2980
+ }
2981
+ }
2942
2982
  // juice-shop mode
2943
2983
  // Projects such as juice-shop prevent lockfile creations using .npmrc files
2944
2984
  // Plus, they might require specific npm install args such as --legacy-peer-deps that could lead to strange node_modules structure
2945
2985
  // To keep life simple, let's look for any .npmrc file that has package-lock=false to toggle before npm install
2946
- if (pkgMgr === "npm" && safeExistsSync(join(basePath, ".npmrc"))) {
2947
- const npmrcData = readFileSync(join(basePath, ".npmrc"));
2986
+ if (pkgMgr === "npm") {
2948
2987
  if (
2949
2988
  npmrcData?.includes("package-lock=false") &&
2950
2989
  !installArgs.includes("--package-lock")
@@ -2962,6 +3001,9 @@ export async function createNodejsBom(path, options) {
2962
3001
  `**PACKAGE MANAGER**: Let's run the '${pkgMgr}' command with the arguments '${installArgs.join(" ")}' to generate the needed lock files.`,
2963
3002
  );
2964
3003
  }
3004
+ console.warn(
3005
+ "\x1b[1;35mNotice: Generating an SBOM without a lockfile is risky and non-deterministic. Consider generating and committing the lockfile to your repository to ensure reproducible builds and SBOMs.\x1b[0m",
3006
+ );
2965
3007
  console.log(
2966
3008
  `Executing '${pkgMgr} ${installArgs.join(" ")}' in`,
2967
3009
  basePath,
@@ -3235,6 +3277,11 @@ export async function createNodejsBom(path, options) {
3235
3277
  pkgLockFiles?.length &&
3236
3278
  isPackageManagerAllowed("npm", ["pnpm", "yarn"], options)
3237
3279
  ) {
3280
+ if (anyInstallSuccess) {
3281
+ thoughtLog(
3282
+ `I have ${pkgLockFiles.length} package-lock.json file(s) now after a successful npm install.`,
3283
+ );
3284
+ }
3238
3285
  manifestFiles = manifestFiles.concat(pkgLockFiles);
3239
3286
  for (const f of pkgLockFiles) {
3240
3287
  if (DEBUG_MODE) {
@@ -3844,7 +3891,6 @@ export async function createPythonBom(path, options) {
3844
3891
  console.log(`Parsing ${f}`);
3845
3892
  }
3846
3893
  let retMap = await parsePyLockData(lockData, f);
3847
- // Should we exit for workspace errors
3848
3894
  if (retMap?.workspaceWarningShown) {
3849
3895
  options.failOnError && process.exit(1);
3850
3896
  }
@@ -3852,7 +3898,6 @@ export async function createPythonBom(path, options) {
3852
3898
  pkgList = pkgList.concat(retMap.pkgList);
3853
3899
  pkgList = trimComponents(pkgList);
3854
3900
  }
3855
- // Retain the parent hierarchy
3856
3901
  if (retMap?.parentComponent?.components?.length) {
3857
3902
  if (!parentComponent.components) {
3858
3903
  parentComponent.components = [];
@@ -3868,36 +3913,81 @@ export async function createPythonBom(path, options) {
3868
3913
  parentComponent,
3869
3914
  );
3870
3915
  }
3871
- // Retrieve the tree using virtualenv in deep mode and as a fallback
3872
- // This is a slow operation
3873
3916
  if ((options.deep || !dependencies.length) && !f.endsWith("uv.lock")) {
3874
- retMap = await getPipFrozenTree(basePath, f, tempDir, parentComponent);
3875
- if (retMap.pkgList?.length) {
3876
- pkgList = pkgList.concat(retMap.pkgList);
3877
- }
3878
- if (retMap.formulationList?.length) {
3879
- formulationList = formulationList.concat(retMap.formulationList);
3880
- }
3881
- if (retMap.dependenciesList) {
3882
- dependencies = mergeDependencies(
3883
- dependencies,
3884
- retMap.dependenciesList,
3917
+ if (options.installDeps) {
3918
+ retMap = await getPipFrozenTree(
3919
+ basePath,
3920
+ f,
3921
+ tempDir,
3885
3922
  parentComponent,
3886
3923
  );
3924
+ if (retMap.pkgList?.length) pkgList = pkgList.concat(retMap.pkgList);
3925
+ if (retMap.formulationList?.length)
3926
+ formulationList = formulationList.concat(retMap.formulationList);
3927
+ if (retMap.dependenciesList)
3928
+ dependencies = mergeDependencies(
3929
+ dependencies,
3930
+ retMap.dependenciesList,
3931
+ parentComponent,
3932
+ );
3933
+ if (retMap.rootList) {
3934
+ const parentDependsOn = new Set();
3935
+ for (const p of retMap.rootList)
3936
+ parentDependsOn.add(
3937
+ `pkg:pypi/${p.name.toLowerCase()}@${p.version}`,
3938
+ );
3939
+ dependencies.splice(0, 0, {
3940
+ ref: parentComponent["bom-ref"],
3941
+ dependsOn: [...parentDependsOn].sort(),
3942
+ });
3943
+ }
3944
+ } else {
3945
+ let exportedReqs = "";
3946
+ if (f.endsWith("poetry.lock")) {
3947
+ thoughtLog(
3948
+ "Using poetry export as a safe, static alternative to pip install.",
3949
+ );
3950
+ const expCmd = safeSpawnSync(
3951
+ "poetry",
3952
+ ["export", "-f", "requirements.txt"],
3953
+ { cwd: basePath, shell: false },
3954
+ );
3955
+ if (expCmd.status === 0 && expCmd.stdout)
3956
+ exportedReqs = expCmd.stdout.toString();
3957
+ } else if (f.endsWith("pdm.lock")) {
3958
+ thoughtLog(
3959
+ "Using pdm export as a safe, static alternative to pip install.",
3960
+ );
3961
+ const expCmd = safeSpawnSync(
3962
+ "pdm",
3963
+ ["export", "-f", "requirements"],
3964
+ { cwd: basePath, shell: false },
3965
+ );
3966
+ if (expCmd.status === 0 && expCmd.stdout)
3967
+ exportedReqs = expCmd.stdout.toString();
3968
+ }
3969
+ if (exportedReqs) {
3970
+ const tmpReqFile = join(
3971
+ tempDir,
3972
+ `exported-${basename(basePath)}-reqs.txt`,
3973
+ );
3974
+ writeFileSync(tmpReqFile, exportedReqs);
3975
+ const dlist = await parseReqFile(tmpReqFile, false);
3976
+ if (dlist?.length) {
3977
+ pkgList = pkgList.concat(dlist);
3978
+ const parentDependsOn = new Set();
3979
+ for (const p of dlist)
3980
+ parentDependsOn.add(
3981
+ `pkg:pypi/${p.name.toLowerCase()}@${p.version}`,
3982
+ );
3983
+ dependencies.splice(0, 0, {
3984
+ ref: parentComponent["bom-ref"],
3985
+ dependsOn: [...parentDependsOn].sort(),
3986
+ });
3987
+ }
3988
+ }
3887
3989
  }
3888
3990
  }
3889
- if (retMap.rootList) {
3890
- const parentDependsOn = new Set();
3891
- // Complete the dependency tree by making parent component depend on the first level
3892
- for (const p of retMap.rootList) {
3893
- parentDependsOn.add(`pkg:pypi/${p.name.toLowerCase()}@${p.version}`);
3894
- }
3895
- const pdependencies = {
3896
- ref: parentComponent["bom-ref"],
3897
- dependsOn: [...parentDependsOn].sort(),
3898
- };
3899
- dependencies.splice(0, 0, pdependencies);
3900
- }
3901
3991
  }
3902
3992
  options.parentComponent = parentComponent;
3903
3993
  } // poetryMode
@@ -3915,7 +4005,7 @@ export async function createPythonBom(path, options) {
3915
4005
  for (const wf of whlFiles) {
3916
4006
  const mData = await readZipEntry(wf, "METADATA");
3917
4007
  if (mData) {
3918
- const dlist = parseBdistMetadata(undefined, mData);
4008
+ const dlist = parseBdistMetadata(join(wf, "METADATA"), mData);
3919
4009
  if (dlist?.length) {
3920
4010
  pkgList = pkgList.concat(dlist);
3921
4011
  }
@@ -3997,29 +4087,29 @@ export async function createPythonBom(path, options) {
3997
4087
  for (const f of reqFiles) {
3998
4088
  const basePath = dirname(f);
3999
4089
  if (options.installDeps) {
4000
- const pkgMap = await getPipFrozenTree(
4090
+ const rpkgMap = await getPipFrozenTree(
4001
4091
  basePath,
4002
4092
  f,
4003
4093
  tempDir,
4004
4094
  parentComponent,
4005
4095
  );
4006
- if (pkgMap.pkgList?.length) {
4007
- pkgList = pkgList.concat(pkgMap.pkgList);
4096
+ if (rpkgMap.pkgList?.length) {
4097
+ pkgList = pkgList.concat(rpkgMap.pkgList);
4008
4098
  pkgList = trimComponents(pkgList);
4009
4099
  }
4010
- if (pkgMap.formulationList?.length) {
4011
- formulationList = formulationList.concat(pkgMap.formulationList);
4100
+ if (rpkgMap.formulationList?.length) {
4101
+ formulationList = formulationList.concat(rpkgMap.formulationList);
4012
4102
  formulationList = trimComponents(formulationList);
4013
4103
  }
4014
- if (pkgMap.dependenciesList) {
4104
+ if (rpkgMap.dependenciesList) {
4015
4105
  dependencies = mergeDependencies(
4016
4106
  dependencies,
4017
- pkgMap.dependenciesList,
4107
+ rpkgMap.dependenciesList,
4018
4108
  parentComponent,
4019
4109
  );
4020
4110
  }
4021
4111
  // Add the root packages from this file to the parent's dependencies
4022
- for (const p of pkgMap.rootList) {
4112
+ for (const p of rpkgMap.rootList) {
4023
4113
  if (
4024
4114
  parentComponent &&
4025
4115
  p.name === parentComponent.name &&
@@ -4042,12 +4132,42 @@ export async function createPythonBom(path, options) {
4042
4132
  parentComponent,
4043
4133
  );
4044
4134
  }
4045
-
4135
+ if (pkgMap) {
4136
+ // Complete the dependency tree by making parent component depend on the first level
4137
+ for (const p of pkgMap.rootList) {
4138
+ if (
4139
+ parentComponent &&
4140
+ p.name === parentComponent.name &&
4141
+ (p.version === parentComponent.version || p.version === "latest")
4142
+ ) {
4143
+ continue;
4144
+ }
4145
+ parentDependsOn.add(`pkg:pypi/${p.name.toLowerCase()}@${p.version}`);
4146
+ }
4147
+ if (pkgMap?.pkgList?.length) {
4148
+ pkgList = pkgList.concat(pkgMap.pkgList);
4149
+ }
4150
+ if (pkgMap?.formulationList?.length) {
4151
+ formulationList = formulationList.concat(pkgMap.formulationList);
4152
+ }
4153
+ if (pkgMap?.dependenciesList) {
4154
+ dependencies = mergeDependencies(
4155
+ dependencies,
4156
+ pkgMap.dependenciesList,
4157
+ parentComponent,
4158
+ );
4159
+ }
4160
+ }
4046
4161
  // ATOM parsedeps block
4047
4162
  // Atom parsedeps slices can be used to identify packages that are not declared in manifests
4048
4163
  // Since it is a slow operation, we only use it as a fallback or in deep mode
4049
4164
  // This change was made in 10.9.2 release onwards
4050
4165
  if (options.deep || !pkgList.length) {
4166
+ if (!pkgList.length) {
4167
+ thoughtLog(
4168
+ "I couldn't find any components yet. Let's try static analysis with atom parsedeps command.",
4169
+ );
4170
+ }
4051
4171
  const retMap = await getPyModules(path, pkgList, options);
4052
4172
  // We need to patch the existing package list to add ImportedModules for evinse to work
4053
4173
  if (retMap.modList?.length) {
@@ -4096,32 +4216,6 @@ export async function createPythonBom(path, options) {
4096
4216
  }
4097
4217
  }
4098
4218
  // ATOM parsedeps block
4099
- if (pkgMap) {
4100
- // Complete the dependency tree by making parent component depend on the first level
4101
- for (const p of pkgMap.rootList) {
4102
- if (
4103
- parentComponent &&
4104
- p.name === parentComponent.name &&
4105
- (p.version === parentComponent.version || p.version === "latest")
4106
- ) {
4107
- continue;
4108
- }
4109
- parentDependsOn.add(`pkg:pypi/${p.name.toLowerCase()}@${p.version}`);
4110
- }
4111
- if (pkgMap?.pkgList?.length) {
4112
- pkgList = pkgList.concat(pkgMap.pkgList);
4113
- }
4114
- if (pkgMap?.formulationList?.length) {
4115
- formulationList = formulationList.concat(pkgMap.formulationList);
4116
- }
4117
- if (pkgMap?.dependenciesList) {
4118
- dependencies = mergeDependencies(
4119
- dependencies,
4120
- pkgMap.dependenciesList,
4121
- parentComponent,
4122
- );
4123
- }
4124
- }
4125
4219
  let parentPresent = false;
4126
4220
  for (const d of dependencies) {
4127
4221
  if (d.ref === parentComponent["bom-ref"]) {
@@ -4140,7 +4234,6 @@ export async function createPythonBom(path, options) {
4140
4234
  }
4141
4235
  }
4142
4236
  }
4143
-
4144
4237
  // Final fallback is to manually parse setup.py if we still
4145
4238
  // have an empty list
4146
4239
  if (!pkgList.length && setupPyMode) {
@@ -407,7 +407,7 @@ export function initFromSbom(components, language) {
407
407
  const purlLocationMap = {};
408
408
  const purlImportsMap = {};
409
409
  for (const comp of components) {
410
- if (!comp || !comp.evidence) {
410
+ if (!comp?.evidence) {
411
411
  continue;
412
412
  }
413
413
  if (["php", "ruby"].includes(language)) {
@@ -648,8 +648,7 @@ export async function parseObjectSlices(
648
648
  ]) {
649
649
  // Skip the library code typically without filename
650
650
  if (
651
- !slice.fileName ||
652
- !slice.fileName.trim().length ||
651
+ !slice.fileName?.trim().length ||
653
652
  slice.fileName === "<empty>" ||
654
653
  slice.fileName === "<unknown>"
655
654
  ) {
@@ -1807,7 +1806,7 @@ export function collectReachableFrames(_language, reachablesSlice) {
1807
1806
  * @returns
1808
1807
  */
1809
1808
  export function framePicker(dfFrames) {
1810
- if (!dfFrames || !dfFrames.length) {
1809
+ if (!dfFrames?.length) {
1811
1810
  return undefined;
1812
1811
  }
1813
1812
  let aframe = dfFrames[0];
@@ -530,7 +530,7 @@ export function moduleInfo(moduleName, compilerArgs) {
530
530
  * @returns {Object|undefined} Parsed classes, protocols, enums and their functions
531
531
  */
532
532
  export function parseModuleInfo(moduleInfoJson) {
533
- if (!moduleInfoJson || !moduleInfoJson["key.annotations"]) {
533
+ if (!moduleInfoJson?.["key.annotations"]) {
534
534
  return undefined;
535
535
  }
536
536
  const classes = new Set();
@@ -9,7 +9,7 @@ export async function parseCaxaMetadata(mfile) {
9
9
  } catch (_e) {
10
10
  return {};
11
11
  }
12
- if (!mdata || !mdata.components) {
12
+ if (!mdata?.components) {
13
13
  return {};
14
14
  }
15
15
  const { parentComponent } = mdata;
@@ -24,7 +24,7 @@ export function printTable(
24
24
  filterTypes = undefined,
25
25
  highlight = undefined,
26
26
  ) {
27
- if (!bomJson || !bomJson.components) {
27
+ if (!bomJson?.components) {
28
28
  return;
29
29
  }
30
30
  if (
@@ -120,7 +120,7 @@ export function printOSTable(bomJson) {
120
120
  }
121
121
  export function printServices(bomJson) {
122
122
  const data = [["Name", "Endpoints", "Authenticated", "X Trust Boundary"]];
123
- if (!bomJson || !bomJson.services) {
123
+ if (!bomJson?.services) {
124
124
  return;
125
125
  }
126
126
  for (const aservice of bomJson.services) {
@@ -144,7 +144,7 @@ export function printServices(bomJson) {
144
144
 
145
145
  export function printFormulation(bomJson) {
146
146
  const data = [["Tyoe", "Name", "Version"]];
147
- if (!bomJson || !bomJson.formulation) {
147
+ if (!bomJson?.formulation) {
148
148
  return;
149
149
  }
150
150
  for (const aform of bomJson.formulation) {
@@ -179,7 +179,7 @@ const locationComparator = (a, b) => {
179
179
  };
180
180
 
181
181
  export function printOccurrences(bomJson) {
182
- if (!bomJson || !bomJson.components) {
182
+ if (!bomJson?.components) {
183
183
  return;
184
184
  }
185
185
  const data = ["Group", "Name", "Version", "Occurrences"];
@@ -219,15 +219,11 @@ export function printOccurrences(bomJson) {
219
219
 
220
220
  export function printCallStack(bomJson) {
221
221
  const data = [["Group", "Name", "Version", "Call Stack"]];
222
- if (!bomJson || !bomJson.components) {
222
+ if (!bomJson?.components) {
223
223
  return;
224
224
  }
225
225
  for (const comp of bomJson.components) {
226
- if (
227
- !comp.evidence ||
228
- !comp.evidence.callstack ||
229
- !comp.evidence.callstack.frames
230
- ) {
226
+ if (!comp.evidence?.callstack?.frames) {
231
227
  continue;
232
228
  }
233
229
  const frames = Array.from(
@@ -30,13 +30,13 @@ export const GIT_COMMAND = process.env.GIT_CMD || "git";
30
30
  // sdkman tool aliases
31
31
  export const SDKMAN_JAVA_TOOL_ALIASES = {
32
32
  java8: process.env.JAVA8_TOOL || "8.0.452-amzn", // Temurin no longer offers java8 :(
33
- java11: process.env.JAVA11_TOOL || "11.0.29-tem",
34
- java17: process.env.JAVA17_TOOL || "17.0.17-tem",
35
- java21: process.env.JAVA21_TOOL || "21.0.9-tem",
33
+ java11: process.env.JAVA11_TOOL || "11.0.30-tem",
34
+ java17: process.env.JAVA17_TOOL || "17.0.18-tem",
35
+ java21: process.env.JAVA21_TOOL || "21.0.10-tem",
36
36
  java22: process.env.JAVA22_TOOL || "22.0.2-tem",
37
37
  java23: process.env.JAVA23_TOOL || "23.0.2-tem",
38
38
  java24: process.env.JAVA24_TOOL || "24.0.2-tem",
39
- java25: process.env.JAVA25_TOOL || "25.0.1-tem",
39
+ java25: process.env.JAVA25_TOOL || "25.0.2-tem",
40
40
  };
41
41
 
42
42
  /**
@@ -206,7 +206,7 @@ export function collectPythonInfo(dir) {
206
206
  ]);
207
207
  const moduleDesc =
208
208
  getCommandOutput(getPythonCommand(), dir, [
209
- "-S",
209
+ "-I",
210
210
  "-m",
211
211
  "pip",
212
212
  "--version",