@cyclonedx/cdxgen 12.3.3 → 12.4.1

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 (175) hide show
  1. package/README.md +69 -25
  2. package/bin/audit.js +21 -7
  3. package/bin/cdxgen.js +270 -127
  4. package/bin/convert.js +34 -15
  5. package/bin/hbom.js +495 -0
  6. package/bin/repl.js +592 -37
  7. package/bin/validate.js +31 -4
  8. package/bin/verify.js +18 -5
  9. package/data/README.md +298 -25
  10. package/data/component-tags.json +6 -0
  11. package/data/crypto-oid.json +16 -0
  12. package/data/cyclonedx-2.0-bundled.schema.json +7182 -0
  13. package/data/predictive-audit-allowlist.json +11 -0
  14. package/data/queries-darwin.json +12 -1
  15. package/data/queries-win.json +7 -1
  16. package/data/queries.json +39 -2
  17. package/data/rules/ai-agent-governance.yaml +16 -0
  18. package/data/rules/asar-archives.yaml +150 -0
  19. package/data/rules/chrome-extensions.yaml +8 -0
  20. package/data/rules/ci-permissions.yaml +42 -18
  21. package/data/rules/container-risk.yaml +14 -7
  22. package/data/rules/dependency-sources.yaml +11 -0
  23. package/data/rules/hbom-compliance.yaml +325 -0
  24. package/data/rules/hbom-performance.yaml +307 -0
  25. package/data/rules/hbom-security.yaml +248 -0
  26. package/data/rules/host-topology.yaml +165 -0
  27. package/data/rules/mcp-servers.yaml +18 -3
  28. package/data/rules/obom-runtime.yaml +907 -22
  29. package/data/rules/package-integrity.yaml +14 -0
  30. package/data/rules/rootfs-hardening.yaml +179 -0
  31. package/data/rules/vscode-extensions.yaml +9 -0
  32. package/lib/audit/index.js +210 -8
  33. package/lib/audit/index.poku.js +332 -0
  34. package/lib/audit/reporters.js +222 -0
  35. package/lib/audit/targets.js +146 -1
  36. package/lib/audit/targets.poku.js +186 -0
  37. package/lib/cli/asar.poku.js +328 -0
  38. package/lib/cli/index.js +527 -99
  39. package/lib/cli/index.poku.js +1469 -212
  40. package/lib/evinser/evinser.js +14 -9
  41. package/lib/helpers/analyzer.js +1406 -29
  42. package/lib/helpers/analyzer.poku.js +342 -0
  43. package/lib/helpers/analyzerScope.js +712 -0
  44. package/lib/helpers/asarutils.js +1556 -0
  45. package/lib/helpers/asarutils.poku.js +443 -0
  46. package/lib/helpers/auditCategories.js +12 -0
  47. package/lib/helpers/auditCategories.poku.js +32 -0
  48. package/lib/helpers/bomUtils.js +155 -1
  49. package/lib/helpers/bomUtils.poku.js +79 -1
  50. package/lib/helpers/cbomutils.js +271 -1
  51. package/lib/helpers/cbomutils.poku.js +248 -5
  52. package/lib/helpers/display.js +291 -1
  53. package/lib/helpers/display.poku.js +149 -0
  54. package/lib/helpers/evidenceUtils.js +58 -0
  55. package/lib/helpers/evidenceUtils.poku.js +54 -0
  56. package/lib/helpers/exportUtils.js +9 -0
  57. package/lib/helpers/gtfobins.js +142 -8
  58. package/lib/helpers/gtfobins.poku.js +24 -1
  59. package/lib/helpers/hbom.js +710 -0
  60. package/lib/helpers/hbom.poku.js +496 -0
  61. package/lib/helpers/hbomAnalysis.js +268 -0
  62. package/lib/helpers/hbomAnalysis.poku.js +249 -0
  63. package/lib/helpers/hbomLoader.js +35 -0
  64. package/lib/helpers/hostTopology.js +803 -0
  65. package/lib/helpers/hostTopology.poku.js +363 -0
  66. package/lib/helpers/inventoryStats.js +69 -0
  67. package/lib/helpers/inventoryStats.poku.js +86 -0
  68. package/lib/helpers/lolbas.js +19 -1
  69. package/lib/helpers/lolbas.poku.js +23 -0
  70. package/lib/helpers/osqueryTransform.js +47 -0
  71. package/lib/helpers/osqueryTransform.poku.js +47 -0
  72. package/lib/helpers/plugins.js +350 -0
  73. package/lib/helpers/plugins.poku.js +57 -0
  74. package/lib/helpers/protobom.js +209 -45
  75. package/lib/helpers/protobom.poku.js +183 -5
  76. package/lib/helpers/protobomLoader.js +43 -0
  77. package/lib/helpers/protobomLoader.poku.js +31 -0
  78. package/lib/helpers/remote/dependency-track.js +36 -3
  79. package/lib/helpers/remote/dependency-track.poku.js +44 -0
  80. package/lib/helpers/source.js +24 -0
  81. package/lib/helpers/source.poku.js +32 -0
  82. package/lib/helpers/utils.js +1438 -93
  83. package/lib/helpers/utils.poku.js +846 -4
  84. package/lib/managers/binary.e2e.poku.js +367 -0
  85. package/lib/managers/binary.js +2293 -353
  86. package/lib/managers/binary.poku.js +1699 -1
  87. package/lib/managers/docker.js +201 -79
  88. package/lib/managers/docker.poku.js +337 -12
  89. package/lib/server/server.js +4 -28
  90. package/lib/stages/postgen/annotator.js +38 -0
  91. package/lib/stages/postgen/annotator.poku.js +107 -1
  92. package/lib/stages/postgen/auditBom.js +121 -18
  93. package/lib/stages/postgen/auditBom.poku.js +1366 -31
  94. package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
  95. package/lib/stages/postgen/postgen.js +406 -8
  96. package/lib/stages/postgen/postgen.poku.js +484 -0
  97. package/lib/stages/postgen/ruleEngine.js +116 -0
  98. package/lib/stages/pregen/envAudit.js +14 -3
  99. package/lib/validator/bomValidator.js +90 -38
  100. package/lib/validator/bomValidator.poku.js +90 -0
  101. package/lib/validator/complianceRules.js +4 -2
  102. package/lib/validator/index.poku.js +14 -0
  103. package/package.json +23 -21
  104. package/types/bin/hbom.d.ts +3 -0
  105. package/types/bin/hbom.d.ts.map +1 -0
  106. package/types/bin/repl.d.ts +1 -1
  107. package/types/bin/repl.d.ts.map +1 -1
  108. package/types/lib/audit/index.d.ts +44 -0
  109. package/types/lib/audit/index.d.ts.map +1 -1
  110. package/types/lib/audit/reporters.d.ts +16 -0
  111. package/types/lib/audit/reporters.d.ts.map +1 -1
  112. package/types/lib/audit/targets.d.ts.map +1 -1
  113. package/types/lib/cli/index.d.ts +16 -0
  114. package/types/lib/cli/index.d.ts.map +1 -1
  115. package/types/lib/evinser/evinser.d.ts +4 -0
  116. package/types/lib/evinser/evinser.d.ts.map +1 -1
  117. package/types/lib/helpers/analyzer.d.ts +33 -0
  118. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  119. package/types/lib/helpers/analyzerScope.d.ts +11 -0
  120. package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
  121. package/types/lib/helpers/asarutils.d.ts +34 -0
  122. package/types/lib/helpers/asarutils.d.ts.map +1 -0
  123. package/types/lib/helpers/auditCategories.d.ts +5 -0
  124. package/types/lib/helpers/auditCategories.d.ts.map +1 -1
  125. package/types/lib/helpers/bomUtils.d.ts +10 -0
  126. package/types/lib/helpers/bomUtils.d.ts.map +1 -1
  127. package/types/lib/helpers/cbomutils.d.ts +3 -2
  128. package/types/lib/helpers/cbomutils.d.ts.map +1 -1
  129. package/types/lib/helpers/display.d.ts.map +1 -1
  130. package/types/lib/helpers/evidenceUtils.d.ts +8 -0
  131. package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
  132. package/types/lib/helpers/exportUtils.d.ts.map +1 -1
  133. package/types/lib/helpers/gtfobins.d.ts +8 -0
  134. package/types/lib/helpers/gtfobins.d.ts.map +1 -1
  135. package/types/lib/helpers/hbom.d.ts +49 -0
  136. package/types/lib/helpers/hbom.d.ts.map +1 -0
  137. package/types/lib/helpers/hbomAnalysis.d.ts +76 -0
  138. package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
  139. package/types/lib/helpers/hbomLoader.d.ts +7 -0
  140. package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
  141. package/types/lib/helpers/hostTopology.d.ts +12 -0
  142. package/types/lib/helpers/hostTopology.d.ts.map +1 -0
  143. package/types/lib/helpers/inventoryStats.d.ts +11 -0
  144. package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
  145. package/types/lib/helpers/lolbas.d.ts.map +1 -1
  146. package/types/lib/helpers/osqueryTransform.d.ts +3 -0
  147. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
  148. package/types/lib/helpers/plugins.d.ts +58 -0
  149. package/types/lib/helpers/plugins.d.ts.map +1 -0
  150. package/types/lib/helpers/protobom.d.ts +5 -4
  151. package/types/lib/helpers/protobom.d.ts.map +1 -1
  152. package/types/lib/helpers/protobomLoader.d.ts +17 -0
  153. package/types/lib/helpers/protobomLoader.d.ts.map +1 -0
  154. package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
  155. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  156. package/types/lib/helpers/source.d.ts.map +1 -1
  157. package/types/lib/helpers/utils.d.ts +45 -8
  158. package/types/lib/helpers/utils.d.ts.map +1 -1
  159. package/types/lib/managers/binary.d.ts +5 -0
  160. package/types/lib/managers/binary.d.ts.map +1 -1
  161. package/types/lib/managers/docker.d.ts.map +1 -1
  162. package/types/lib/server/server.d.ts +2 -1
  163. package/types/lib/server/server.d.ts.map +1 -1
  164. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  165. package/types/lib/stages/postgen/auditBom.d.ts +26 -1
  166. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  167. package/types/lib/stages/postgen/postgen.d.ts +2 -1
  168. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  169. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  170. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  171. package/types/lib/third-party/arborist/lib/node.d.ts +23 -0
  172. package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -1
  173. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  174. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  175. package/data/spdx-model-v3.0.1.jsonld +0 -15999
package/lib/cli/index.js CHANGED
@@ -11,7 +11,6 @@ import { platform as _platform, arch, homedir } from "node:os";
11
11
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
12
12
  import process from "node:process";
13
13
 
14
- import got from "got";
15
14
  import { PackageURL } from "packageurl-js";
16
15
  import { gte, lte } from "semver";
17
16
  import { parse } from "ssri";
@@ -35,8 +34,19 @@ import {
35
34
  detectPythonMcpInventory,
36
35
  findJSImportsExports,
37
36
  } from "../helpers/analyzer.js";
37
+ import {
38
+ cleanupAsarTempDir,
39
+ extractAsarToTempDir,
40
+ parseAsarArchive,
41
+ rewriteExtractedArchivePaths,
42
+ } from "../helpers/asarutils.js";
38
43
  import { expandBomAuditCategories } from "../helpers/auditCategories.js";
44
+ import {
45
+ setCycloneDxFormat,
46
+ toCycloneDxSpecVersionString,
47
+ } from "../helpers/bomUtils.js";
39
48
  import { parseCaxaMetadata } from "../helpers/caxa.js";
49
+ import { collectSourceCryptoComponents } from "../helpers/cbomutils.js";
40
50
  import {
41
51
  CHROME_EXTENSION_PURL_TYPE,
42
52
  collectChromeExtensionsFromPath,
@@ -49,15 +59,19 @@ import {
49
59
  trimComponents,
50
60
  } from "../helpers/depsUtils.js";
51
61
  import { GIT_COMMAND } from "../helpers/envcontext.js";
52
- import { thoughtLog } from "../helpers/logger.js";
53
62
  import {
54
- classifyMcpReference,
55
- enrichComponentWithMcpMetadata,
56
- } from "../helpers/mcp.js";
63
+ createHbomDocument,
64
+ ensureHbomRuntimeSupport,
65
+ ensureNoMixedHbomProjectTypes,
66
+ hasHbomProjectType,
67
+ } from "../helpers/hbom.js";
68
+ import { mergeHostInventoryBoms } from "../helpers/hostTopology.js";
69
+ import { thoughtLog } from "../helpers/logger.js";
70
+ import { enrichComponentWithMcpMetadata } from "../helpers/mcp.js";
57
71
  import { isPyLockFile } from "../helpers/pylockutils.js";
58
72
  import {
59
73
  buildDependencyTrackBomPayload,
60
- getDependencyTrackBomUrl,
74
+ getDependencyTrackBomApiUrl,
61
75
  } from "../helpers/remote/dependency-track.js";
62
76
  import { table } from "../helpers/table.js";
63
77
  import {
@@ -71,6 +85,7 @@ import {
71
85
  CARGO_CMD,
72
86
  CDXGEN_VERSION,
73
87
  CLJ_CMD,
88
+ cdxgenAgent,
74
89
  checksumFile,
75
90
  cleanupPlugin,
76
91
  collectGemModuleNames,
@@ -111,6 +126,7 @@ import {
111
126
  getTmpDir,
112
127
  hasAnyProjectType,
113
128
  includeMavenTestScope,
129
+ isAllowedHttpHost,
114
130
  isDryRun,
115
131
  isFeatureEnabled,
116
132
  isMac,
@@ -201,6 +217,7 @@ import {
201
217
  readZipEntry,
202
218
  recomputeScope,
203
219
  recordActivity,
220
+ recordSensitiveFileRead,
204
221
  resetActivityContext,
205
222
  SWIFT_CMD,
206
223
  safeExistsSync,
@@ -226,10 +243,12 @@ import {
226
243
  VSCODE_EXTENSION_PURL_TYPE,
227
244
  } from "../helpers/vsixutils.js";
228
245
  import {
246
+ enrichOSComponentsWithTrustData,
229
247
  executeOsQuery,
230
248
  getBinaryBom,
231
249
  getDotnetSlices,
232
250
  getOSPackages,
251
+ getPluginToolComponents,
233
252
  } from "../managers/binary.js";
234
253
  import {
235
254
  addSkippedSrcFiles,
@@ -369,6 +388,18 @@ const shouldIncludeNodeModulesDir = (options = {}, baseProjectTypes = []) => {
369
388
  );
370
389
  };
371
390
 
391
+ const hasExplicitProjectTypeSelection = (options, baseProjectType) => {
392
+ options = options || {};
393
+ const projectTypes = Array.isArray(options.projectType)
394
+ ? options.projectType
395
+ : options.projectType
396
+ ? [options.projectType]
397
+ : [];
398
+ return projectTypes.some((selectedProjectType) =>
399
+ PROJECT_TYPE_ALIASES[baseProjectType]?.includes(selectedProjectType),
400
+ );
401
+ };
402
+
372
403
  const determineParentComponent = (options) => {
373
404
  let parentComponent;
374
405
  if (options.parentComponent && Object.keys(options.parentComponent).length) {
@@ -840,6 +871,18 @@ function addMetadata(parentComponent = {}, options = {}, context = {}) {
840
871
  value: options.allOSComponentTypes.sort().join("\\n"),
841
872
  });
842
873
  }
874
+ if (Number.isInteger(options?.unpackagedExecutableCount)) {
875
+ mproperties.push({
876
+ name: "cdx:container:unpackagedExecutableCount",
877
+ value: String(options.unpackagedExecutableCount),
878
+ });
879
+ }
880
+ if (Number.isInteger(options?.unpackagedSharedLibraryCount)) {
881
+ mproperties.push({
882
+ name: "cdx:container:unpackagedSharedLibraryCount",
883
+ value: String(options.unpackagedSharedLibraryCount),
884
+ });
885
+ }
843
886
  // Should we move these to formulation?
844
887
  if (options?.bundledSdks?.length) {
845
888
  for (const sdk of options.bundledSdks) {
@@ -943,6 +986,9 @@ export function listComponents(options, allImports, pkg, ptype = "npm") {
943
986
  return Object.keys(compMap).map((k) => compMap[k]);
944
987
  }
945
988
 
989
+ // These component types do not have PURLs
990
+ const NON_PURL_TYPES = ["cryptographic-asset", "file", "data"];
991
+
946
992
  /**
947
993
  * Given the specified package, create a CycloneDX component and add it to the list.
948
994
  */
@@ -979,9 +1025,9 @@ function addComponent(
979
1025
  }
980
1026
  const version = pkg.version || "";
981
1027
  const licenses = pkg.licenses || getLicenses(pkg);
982
- let purl =
983
- pkg.purl ||
984
- new PackageURL(
1028
+ let purl = pkg.purl;
1029
+ if (!purl && ptype) {
1030
+ purl = new PackageURL(
985
1031
  ptype,
986
1032
  encodeForPurl(group),
987
1033
  encodeForPurl(name),
@@ -989,9 +1035,9 @@ function addComponent(
989
1035
  pkg.qualifiers,
990
1036
  encodeForPurl(pkg.subpath),
991
1037
  );
992
- let purlString = purl.toString();
993
- // There is no purl for cryptographic-asset
994
- if (ptype === "cryptographic-asset") {
1038
+ }
1039
+ let purlString = purl?.toString();
1040
+ if (NON_PURL_TYPES.includes(ptype) || NON_PURL_TYPES.includes(pkg.type)) {
995
1041
  purl = undefined;
996
1042
  purlString = undefined;
997
1043
  }
@@ -1102,7 +1148,7 @@ function addComponent(
1102
1148
  if (pkg.properties?.length) {
1103
1149
  component.properties = pkg.properties;
1104
1150
  }
1105
- if (pkg.cryptoProperties?.length) {
1151
+ if (pkg.cryptoProperties && typeof pkg.cryptoProperties === "object") {
1106
1152
  component.cryptoProperties = pkg.cryptoProperties;
1107
1153
  }
1108
1154
  // Retain nested components
@@ -1351,13 +1397,16 @@ const buildBomNSData = (options, pkgInfo, ptype, context) => {
1351
1397
  // CycloneDX Json Template
1352
1398
  const jsonTpl = {
1353
1399
  bomFormat: "CycloneDX",
1354
- specVersion: `${options.specVersion || "1.7"}`,
1400
+ specVersion: toCycloneDxSpecVersionString(options.specVersion || "1.7"),
1355
1401
  serialNumber: serialNum,
1356
1402
  version: 1,
1357
1403
  metadata: metadata,
1358
1404
  components,
1359
1405
  dependencies,
1360
1406
  };
1407
+ setCycloneDxFormat(jsonTpl, jsonTpl.specVersion, {
1408
+ preserveLegacyBomFormat: true,
1409
+ });
1361
1410
  if (services.length) {
1362
1411
  jsonTpl.services = mergeServices([], services);
1363
1412
  }
@@ -2769,11 +2818,10 @@ export async function createJavaBom(path, options) {
2769
2818
  }
2770
2819
 
2771
2820
  /**
2772
- * Function to create bom string for Node.js projects
2821
+ * Identify the requested AI inventory project types.
2773
2822
  *
2774
- * @param {string} path to the project
2775
2823
  * @param {Object} options Parse options from the cli
2776
- * @returns {Promise<Object>} Promise resolving to BOM object
2824
+ * @returns {string[]} Requested AI inventory types
2777
2825
  */
2778
2826
  function getRequestedAiInventoryTypes(options) {
2779
2827
  return AI_INVENTORY_PROJECT_TYPES.filter((type) =>
@@ -2787,6 +2835,48 @@ function getExcludedAiInventoryTypes(options) {
2787
2835
  );
2788
2836
  }
2789
2837
 
2838
+ function filterIncludedAiInventoryTypes(
2839
+ includedAiInventoryTypes,
2840
+ excludedAiInventoryTypes,
2841
+ ) {
2842
+ return [...new Set(includedAiInventoryTypes)].filter(
2843
+ (type) => !excludedAiInventoryTypes.includes(type),
2844
+ );
2845
+ }
2846
+
2847
+ /**
2848
+ * Determine which AI inventory types should be collected for a scan.
2849
+ *
2850
+ * This combines explicit project-type opt-ins with BOM audit category-driven
2851
+ * opt-ins, then removes any explicitly excluded inventory types.
2852
+ *
2853
+ * @param {Object} options Parse options from the CLI
2854
+ * @returns {string[]} AI inventory types to collect
2855
+ */
2856
+ function getIncludedAiInventoryTypes(options) {
2857
+ const requestedAiInventoryTypes = getRequestedAiInventoryTypes(options);
2858
+ const excludedAiInventoryTypes = getExcludedAiInventoryTypes(options);
2859
+ const exactAiInventoryType = getExactAiInventoryType(options);
2860
+ if (exactAiInventoryType) {
2861
+ return filterIncludedAiInventoryTypes(
2862
+ requestedAiInventoryTypes,
2863
+ excludedAiInventoryTypes,
2864
+ );
2865
+ }
2866
+ const auditCategories = expandBomAuditCategories(options?.bomAuditCategories);
2867
+ const includedAiInventoryTypes = [...requestedAiInventoryTypes];
2868
+ if (auditCategories.includes("ai-agent")) {
2869
+ includedAiInventoryTypes.push("ai-skill");
2870
+ }
2871
+ if (auditCategories.includes("mcp-server")) {
2872
+ includedAiInventoryTypes.push("mcp");
2873
+ }
2874
+ return filterIncludedAiInventoryTypes(
2875
+ includedAiInventoryTypes,
2876
+ excludedAiInventoryTypes,
2877
+ );
2878
+ }
2879
+
2790
2880
  function getExactAiInventoryType(options) {
2791
2881
  const requestedAiInventoryTypes = getRequestedAiInventoryTypes(options);
2792
2882
  return requestedAiInventoryTypes.length === 1 &&
@@ -2796,22 +2886,14 @@ function getExactAiInventoryType(options) {
2796
2886
  : undefined;
2797
2887
  }
2798
2888
 
2799
- function shouldDetectMcpInventory(options, allImports = {}) {
2800
- if (hasAnyProjectType(["mcp"], options, false)) {
2801
- return true;
2802
- }
2803
- const auditCategories = expandBomAuditCategories(options?.bomAuditCategories);
2804
- if (
2805
- auditCategories.some((category) =>
2806
- ["mcp-server", "ai-agent"].includes(category),
2807
- )
2808
- ) {
2809
- return true;
2810
- }
2811
- return Object.keys(allImports).some((importName) => {
2812
- const classification = classifyMcpReference(importName);
2813
- return classification.isMcp;
2814
- });
2889
+ /**
2890
+ * Determine whether MCP source-code analysis should run for the current scan.
2891
+ *
2892
+ * @param {string[]} includedAiInventoryTypes AI inventory types selected for collection
2893
+ * @returns {boolean} True when MCP inventory collection is enabled
2894
+ */
2895
+ function shouldDetectMcpInventory(includedAiInventoryTypes) {
2896
+ return includedAiInventoryTypes.includes("mcp");
2815
2897
  }
2816
2898
 
2817
2899
  function summarizeAiInventoryNames(subjects, discoveryPath, kindSet) {
@@ -2906,14 +2988,8 @@ export async function createNodejsBom(path, options) {
2906
2988
  let parentComponent = {};
2907
2989
  const parentSubComponents = [];
2908
2990
  let ppurl = "";
2909
- const requestedAiInventoryTypes = getRequestedAiInventoryTypes(options);
2910
- const excludedAiInventoryTypes = getExcludedAiInventoryTypes(options);
2911
2991
  const exactAiInventoryType = getExactAiInventoryType(options);
2912
- const includedAiInventoryTypes = exactAiInventoryType
2913
- ? requestedAiInventoryTypes
2914
- : AI_INVENTORY_PROJECT_TYPES.filter(
2915
- (type) => !excludedAiInventoryTypes.includes(type),
2916
- );
2992
+ const includedAiInventoryTypes = getIncludedAiInventoryTypes(options);
2917
2993
  let aiInventory = { components: [], dependencies: [], services: [] };
2918
2994
  // Docker mode requires special handling
2919
2995
  if (hasAnyProjectType(["docker", "oci", "container", "os"], options, false)) {
@@ -2965,16 +3041,13 @@ export async function createNodejsBom(path, options) {
2965
3041
  const retData = await findJSImportsExports(path, options.deep);
2966
3042
  allImports = retData.allImports;
2967
3043
  allExports = retData.allExports;
2968
- if (shouldDetectMcpInventory(options, allImports)) {
3044
+ if (shouldDetectMcpInventory(includedAiInventoryTypes)) {
2969
3045
  mcpInventory = detectMcpInventory(path, options.deep);
2970
3046
  }
2971
3047
  }
2972
3048
  if (includedAiInventoryTypes.length) {
2973
3049
  aiInventory = collectAiInventory(path, options, includedAiInventoryTypes);
2974
3050
  }
2975
- if (excludedAiInventoryTypes.includes("mcp")) {
2976
- mcpInventory = { components: [], dependencies: [], services: [] };
2977
- }
2978
3051
  const aiInventorySummary = summarizeAiInventory(aiInventory);
2979
3052
  if (!exactAiInventoryType) {
2980
3053
  if (aiInventorySummary.instructionCount || aiInventorySummary.skillCount) {
@@ -2987,7 +3060,6 @@ export async function createNodejsBom(path, options) {
2987
3060
  `I found ${aiInventorySummary.mcpConfigCount} MCP config component(s). Use '--exclude-type mcp' to drop them, or '--bom-audit --bom-audit-categories ai-inventory --tlp-classification AMBER' to keep and flag them.`,
2988
3061
  );
2989
3062
  }
2990
- emitAiInventorySummary(aiInventory, path);
2991
3063
  }
2992
3064
  if (exactAiInventoryType === "ai-skill") {
2993
3065
  const exactComponents = trimComponents([...(aiInventory.components || [])]);
@@ -3167,11 +3239,15 @@ export async function createNodejsBom(path, options) {
3167
3239
  }
3168
3240
  const basePath = dirname(apkgJson);
3169
3241
  let npmrcData;
3170
- if (safeExistsSync(join(basePath, ".npmrc"))) {
3242
+ const npmrcFile = join(basePath, ".npmrc");
3243
+ if (safeExistsSync(npmrcFile)) {
3171
3244
  thoughtLog(
3172
3245
  "Wait, there is a .npmrc file here! I'm going to check if it has anything malicious.",
3173
3246
  );
3174
- npmrcData = readFileSync(join(basePath, ".npmrc"), "utf-8");
3247
+ npmrcData = readFileSync(npmrcFile, "utf-8");
3248
+ recordSensitiveFileRead(npmrcFile, {
3249
+ label: "npm registry configuration",
3250
+ });
3175
3251
  const npmrcObj = parseNpmrc(npmrcData);
3176
3252
  for (const [key, value] of Object.entries(npmrcObj)) {
3177
3253
  const baseKey = key.replace(/^(?:\/\/[^/]+\/|@[^:]+:)/, "");
@@ -4102,14 +4178,7 @@ export async function createPythonBom(path, options) {
4102
4178
  let dependencies = [];
4103
4179
  let pkgList = [];
4104
4180
  let formulationList = [];
4105
- const requestedAiInventoryTypes = getRequestedAiInventoryTypes(options);
4106
- const excludedAiInventoryTypes = getExcludedAiInventoryTypes(options);
4107
- const includedAiInventoryTypes = AI_INVENTORY_PROJECT_TYPES.filter(
4108
- (type) =>
4109
- (!requestedAiInventoryTypes.length ||
4110
- requestedAiInventoryTypes.includes(type)) &&
4111
- !excludedAiInventoryTypes.includes(type),
4112
- );
4181
+ const includedAiInventoryTypes = getIncludedAiInventoryTypes(options);
4113
4182
  let aiInventory = { components: [], dependencies: [], services: [] };
4114
4183
  let mcpInventory = { components: [], dependencies: [], services: [] };
4115
4184
  const tempDir = safeMkdtempSync(join(getTmpDir(), "cdxgen-venv-"));
@@ -5955,6 +6024,7 @@ export function createOSBom(_path, options) {
5955
6024
  let pkgList = [];
5956
6025
  let bomData = {};
5957
6026
  let parentComponent = {};
6027
+ let externalTools = getPluginToolComponents(["osquery"]);
5958
6028
  for (const queryCategory of Object.keys(osQueries)) {
5959
6029
  const queryObj = osQueries[queryCategory];
5960
6030
  const results = executeOsQuery(queryObj.query);
@@ -5973,6 +6043,20 @@ export function createOSBom(_path, options) {
5973
6043
  );
5974
6044
  }
5975
6045
  } // for
6046
+ const hostTrustInventory = enrichOSComponentsWithTrustData(pkgList);
6047
+ if (hostTrustInventory?.components?.length) {
6048
+ pkgList = hostTrustInventory.components;
6049
+ }
6050
+ if (hostTrustInventory?.tools?.length) {
6051
+ externalTools = externalTools.concat(hostTrustInventory.tools);
6052
+ }
6053
+ if (externalTools.length) {
6054
+ options.tools = Array.from(
6055
+ new Map(
6056
+ externalTools.map((tool) => [tool["bom-ref"] || tool.name, tool]),
6057
+ ).values(),
6058
+ );
6059
+ }
5976
6060
  if (pkgList.length) {
5977
6061
  bomData = buildBomNSData(options, pkgList, "", {
5978
6062
  src: "",
@@ -7776,6 +7860,10 @@ export async function createVscodeExtensionBom(path, options) {
7776
7860
  let pkgList = [];
7777
7861
  let dependencies = [];
7778
7862
  const tempDirs = [];
7863
+ const shouldDiscoverInstalledIdeExtensions = hasExplicitProjectTypeSelection(
7864
+ options,
7865
+ "vscode-extension",
7866
+ );
7779
7867
 
7780
7868
  // Mode 1: Scan for .vsix files in the given directory, or treat the input
7781
7869
  // path as a single .vsix file.
@@ -7821,7 +7909,7 @@ export async function createVscodeExtensionBom(path, options) {
7821
7909
  }
7822
7910
 
7823
7911
  // Mode 2: Auto-discover extensions from known IDE locations
7824
- if (options.deep || options.projectType?.includes("ide-extensions")) {
7912
+ if (shouldDiscoverInstalledIdeExtensions) {
7825
7913
  const ideDirs = discoverIdeExtensionDirs();
7826
7914
  if (ideDirs.length) {
7827
7915
  if (DEBUG_MODE) {
@@ -7863,6 +7951,198 @@ export async function createVscodeExtensionBom(path, options) {
7863
7951
  });
7864
7952
  }
7865
7953
 
7954
+ /**
7955
+ * Function to create BOM for Electron ASAR archives.
7956
+ *
7957
+ * @param {string} path to a single archive or a directory to scan
7958
+ * @param {Object} options Parse options from the cli
7959
+ * @returns {Promise<Object>} Promise resolving to BOM object
7960
+ */
7961
+ export async function createAsarBom(path, options) {
7962
+ let pkgList = [];
7963
+ let dependencies = [];
7964
+ let parentComponent = {};
7965
+ const tempDirs = [];
7966
+ const processedArchives = new Set();
7967
+ const maxNestedAsarDepth = 4;
7968
+ const explicitAsarPath = path.endsWith(".asar") ? resolve(path) : undefined;
7969
+ const asarFiles = explicitAsarPath
7970
+ ? [explicitAsarPath]
7971
+ : getAllFiles(path, `${options.multiProject ? "**/" : ""}*.asar`, options);
7972
+
7973
+ const aggregateArchiveResults = (
7974
+ archiveAnalysis,
7975
+ isPrimaryArchive = false,
7976
+ ) => {
7977
+ if (
7978
+ archiveAnalysis.parentComponent &&
7979
+ Object.keys(archiveAnalysis.parentComponent).length
7980
+ ) {
7981
+ if (isPrimaryArchive && !Object.keys(parentComponent).length) {
7982
+ parentComponent = archiveAnalysis.parentComponent;
7983
+ } else {
7984
+ pkgList.push(archiveAnalysis.parentComponent);
7985
+ }
7986
+ }
7987
+ if (archiveAnalysis.components?.length) {
7988
+ pkgList = pkgList.concat(archiveAnalysis.components);
7989
+ }
7990
+ if (archiveAnalysis.dependencies?.length) {
7991
+ dependencies = mergeDependencies(
7992
+ dependencies,
7993
+ archiveAnalysis.dependencies,
7994
+ parentComponent,
7995
+ );
7996
+ }
7997
+ };
7998
+
7999
+ const analyzeExtractedArchive = async (
8000
+ extractedDir,
8001
+ archiveAnalysis,
8002
+ archiveIdentityPath,
8003
+ ) => {
8004
+ if (!archiveAnalysis.packageManifestPaths?.length) {
8005
+ return undefined;
8006
+ }
8007
+ const nodeBomOptions = {
8008
+ ...options,
8009
+ installDeps: false,
8010
+ multiProject: true,
8011
+ noBabel: false,
8012
+ path: extractedDir,
8013
+ projectType: ["js"],
8014
+ };
8015
+ const nodeBomData = await createNodejsBom(extractedDir, nodeBomOptions);
8016
+ if (nodeBomData?.bomJson?.components?.length) {
8017
+ rewriteExtractedArchivePaths(
8018
+ nodeBomData.bomJson.components,
8019
+ extractedDir,
8020
+ archiveIdentityPath,
8021
+ );
8022
+ pkgList = pkgList.concat(nodeBomData.bomJson.components);
8023
+ }
8024
+ if (nodeBomData?.bomJson?.dependencies?.length) {
8025
+ dependencies = mergeDependencies(
8026
+ dependencies,
8027
+ nodeBomData.bomJson.dependencies,
8028
+ );
8029
+ }
8030
+ if (
8031
+ archiveAnalysis.parentComponent?.["bom-ref"] &&
8032
+ nodeBomData?.parentComponent?.["bom-ref"] &&
8033
+ archiveAnalysis.parentComponent["bom-ref"] !==
8034
+ nodeBomData.parentComponent["bom-ref"]
8035
+ ) {
8036
+ rewriteExtractedArchivePaths(
8037
+ nodeBomData.parentComponent,
8038
+ extractedDir,
8039
+ archiveIdentityPath,
8040
+ );
8041
+ dependencies = mergeDependencies(dependencies, [
8042
+ {
8043
+ ref: archiveAnalysis.parentComponent["bom-ref"],
8044
+ dependsOn: [nodeBomData.parentComponent["bom-ref"]],
8045
+ },
8046
+ ]);
8047
+ }
8048
+ return nodeBomData;
8049
+ };
8050
+
8051
+ const processAsarArchive = async (
8052
+ archivePath,
8053
+ archiveIdentityPath,
8054
+ isPrimaryArchive = false,
8055
+ depth = 0,
8056
+ ) => {
8057
+ const processedKey = archiveIdentityPath;
8058
+ if (processedArchives.has(processedKey)) {
8059
+ return undefined;
8060
+ }
8061
+ processedArchives.add(processedKey);
8062
+ const archiveAnalysis = await parseAsarArchive(archivePath, {
8063
+ ...options,
8064
+ asarVirtualPath: archiveIdentityPath,
8065
+ });
8066
+ aggregateArchiveResults(archiveAnalysis, isPrimaryArchive);
8067
+ const shouldExtract =
8068
+ archiveAnalysis.packageManifestPaths?.length ||
8069
+ (depth < maxNestedAsarDepth &&
8070
+ archiveAnalysis.summary?.nestedArchiveCount > 0);
8071
+ if (!shouldExtract) {
8072
+ return archiveAnalysis.parentComponent?.["bom-ref"];
8073
+ }
8074
+ const extractedDir = await extractAsarToTempDir(archivePath);
8075
+ if (!extractedDir) {
8076
+ return archiveAnalysis.parentComponent?.["bom-ref"];
8077
+ }
8078
+ tempDirs.push(extractedDir);
8079
+ await analyzeExtractedArchive(
8080
+ extractedDir,
8081
+ archiveAnalysis,
8082
+ archiveIdentityPath,
8083
+ );
8084
+ if (depth < maxNestedAsarDepth) {
8085
+ const nestedAsarFiles = getAllFiles(extractedDir, "**/*.asar", options);
8086
+ for (const nestedArchivePath of nestedAsarFiles) {
8087
+ const relativeNestedArchivePath = relative(
8088
+ extractedDir,
8089
+ nestedArchivePath,
8090
+ ).replaceAll("\\", "/");
8091
+ if (
8092
+ !relativeNestedArchivePath ||
8093
+ relativeNestedArchivePath.startsWith("..")
8094
+ ) {
8095
+ continue;
8096
+ }
8097
+ const nestedArchiveIdentityPath = `${archiveIdentityPath}#/${relativeNestedArchivePath}`;
8098
+ const nestedParentRef = await processAsarArchive(
8099
+ nestedArchivePath,
8100
+ nestedArchiveIdentityPath,
8101
+ false,
8102
+ depth + 1,
8103
+ );
8104
+ if (
8105
+ archiveAnalysis.parentComponent?.["bom-ref"] &&
8106
+ nestedParentRef &&
8107
+ archiveAnalysis.parentComponent["bom-ref"] !== nestedParentRef
8108
+ ) {
8109
+ dependencies = mergeDependencies(dependencies, [
8110
+ {
8111
+ ref: archiveAnalysis.parentComponent["bom-ref"],
8112
+ dependsOn: [nestedParentRef],
8113
+ },
8114
+ ]);
8115
+ }
8116
+ }
8117
+ }
8118
+ return archiveAnalysis.parentComponent?.["bom-ref"];
8119
+ };
8120
+ try {
8121
+ for (const archivePath of asarFiles) {
8122
+ const isPrimaryArchive =
8123
+ explicitAsarPath && resolve(archivePath) === explicitAsarPath;
8124
+ await processAsarArchive(
8125
+ archivePath,
8126
+ resolve(archivePath),
8127
+ isPrimaryArchive,
8128
+ 0,
8129
+ );
8130
+ }
8131
+ } finally {
8132
+ for (const tempDir of tempDirs) {
8133
+ cleanupAsarTempDir(tempDir);
8134
+ }
8135
+ }
8136
+ pkgList = trimComponents(pkgList);
8137
+ return buildBomNSData(options, pkgList, "asar", {
8138
+ src: path,
8139
+ filename: asarFiles.join(", "),
8140
+ nsMapping: {},
8141
+ dependencies,
8142
+ parentComponent,
8143
+ });
8144
+ }
8145
+
7866
8146
  /**
7867
8147
  * Function to create BOM for installed Chrome and Chromium-based browser extensions.
7868
8148
  *
@@ -7874,7 +8154,8 @@ export async function createChromeExtensionBom(path, options) {
7874
8154
  let dependencies = [];
7875
8155
  let sourcePaths = [];
7876
8156
  const directResult = collectChromeExtensionsFromPath(path);
7877
- const chromeDirs = discoverChromiumExtensionDirs();
8157
+ const shouldDiscoverInstalledChromeExtensions =
8158
+ hasExplicitProjectTypeSelection(options, "chrome-extension");
7878
8159
  let pkgList = directResult.components || [];
7879
8160
  if (directResult.extensionDirs?.length) {
7880
8161
  sourcePaths = directResult.extensionDirs.slice();
@@ -7893,20 +8174,23 @@ export async function createChromeExtensionBom(path, options) {
7893
8174
  `Found ${pkgList.length} component(s) from direct Chrome extension path scan`,
7894
8175
  );
7895
8176
  }
7896
- if (chromeDirs.length) {
7897
- if (DEBUG_MODE) {
7898
- thoughtLog(
7899
- `Discovered Chromium extension directories: ${chromeDirs.map((d) => `${d.browser} (${d.channel}): ${d.dir}`).join(", ")}`,
7900
- );
7901
- }
7902
- if (!pkgList.length) {
7903
- pkgList = collectInstalledChromeExtensions(chromeDirs);
7904
- sourcePaths = chromeDirs.map((d) => d.dir);
7905
- }
7906
- if (DEBUG_MODE && pkgList.length && !directResult.components?.length) {
7907
- thoughtLog(
7908
- `Found ${pkgList.length} Chrome/Chromium extension(s) from ${chromeDirs.length} browser location(s)`,
7909
- );
8177
+ if (shouldDiscoverInstalledChromeExtensions) {
8178
+ const chromeDirs = discoverChromiumExtensionDirs();
8179
+ if (chromeDirs.length) {
8180
+ if (DEBUG_MODE) {
8181
+ thoughtLog(
8182
+ `Discovered Chromium extension directories: ${chromeDirs.map((d) => `${d.browser} (${d.channel}): ${d.dir}`).join(", ")}`,
8183
+ );
8184
+ }
8185
+ if (!pkgList.length) {
8186
+ pkgList = collectInstalledChromeExtensions(chromeDirs);
8187
+ sourcePaths = chromeDirs.map((d) => d.dir);
8188
+ }
8189
+ if (DEBUG_MODE && pkgList.length && !directResult.components?.length) {
8190
+ thoughtLog(
8191
+ `Found ${pkgList.length} Chrome/Chromium extension(s) from ${chromeDirs.length} browser location(s)`,
8192
+ );
8193
+ }
7910
8194
  }
7911
8195
  }
7912
8196
  pkgList = trimComponents(pkgList);
@@ -8026,6 +8310,26 @@ export async function createCryptoCertsBom(path, options) {
8026
8310
  for (const f of certFiles) {
8027
8311
  const name = basename(f);
8028
8312
  const fileHash = await checksumFile("sha256", f);
8313
+ let evidence;
8314
+ if (options.evidence) {
8315
+ const identityEvidence = {
8316
+ field: "name",
8317
+ confidence: 1,
8318
+ concludedValue: name,
8319
+ methods: [
8320
+ {
8321
+ technique: "filename",
8322
+ confidence: 1,
8323
+ value: f,
8324
+ },
8325
+ ],
8326
+ };
8327
+ evidence = {
8328
+ identity:
8329
+ options.specVersion >= 1.6 ? [identityEvidence] : identityEvidence,
8330
+ occurrences: [{ location: f }],
8331
+ };
8332
+ }
8029
8333
  const apkg = {
8030
8334
  name,
8031
8335
  type: "cryptographic-asset",
@@ -8038,10 +8342,18 @@ export async function createCryptoCertsBom(path, options) {
8038
8342
  implementationPlatform: "unknown",
8039
8343
  },
8040
8344
  },
8345
+ ...(evidence ? { evidence } : {}),
8041
8346
  properties: [{ name: "SrcFile", value: f }],
8042
8347
  };
8043
8348
  pkgList.push(apkg);
8044
8349
  }
8350
+ const sourceCryptoComponents = await collectSourceCryptoComponents(
8351
+ path,
8352
+ options,
8353
+ );
8354
+ if (sourceCryptoComponents.length) {
8355
+ pkgList.push(...sourceCryptoComponents);
8356
+ }
8045
8357
  return {
8046
8358
  bomJson: {
8047
8359
  components: pkgList,
@@ -8087,16 +8399,19 @@ export function dedupeBom(options, components, parentComponent, dependencies) {
8087
8399
  options,
8088
8400
  parentComponent,
8089
8401
  components,
8090
- bomJson: {
8091
- bomFormat: "CycloneDX",
8092
- specVersion: `${options.specVersion || 1.7}`,
8093
- serialNumber: serialNum,
8094
- version: 1,
8095
- metadata: addMetadata(parentComponent, options, {}),
8096
- components,
8097
- services: options.services || [],
8098
- dependencies,
8099
- },
8402
+ bomJson: setCycloneDxFormat(
8403
+ {
8404
+ specVersion: toCycloneDxSpecVersionString(options.specVersion || 1.7),
8405
+ serialNumber: serialNum,
8406
+ version: 1,
8407
+ metadata: addMetadata(parentComponent, options, {}),
8408
+ components,
8409
+ services: options.services || [],
8410
+ dependencies,
8411
+ },
8412
+ options.specVersion || 1.7,
8413
+ { preserveLegacyBomFormat: true },
8414
+ ),
8100
8415
  };
8101
8416
  }
8102
8417
 
@@ -8132,6 +8447,7 @@ export async function createMultiXBom(pathList, options) {
8132
8447
  ) {
8133
8448
  const {
8134
8449
  osPackages,
8450
+ osPackageFiles,
8135
8451
  dependenciesList,
8136
8452
  allTypes,
8137
8453
  bundledSdks,
@@ -8139,6 +8455,7 @@ export async function createMultiXBom(pathList, options) {
8139
8455
  binPaths,
8140
8456
  executables,
8141
8457
  sharedLibs,
8458
+ services,
8142
8459
  tools,
8143
8460
  } = await getOSPackages(
8144
8461
  options.allLayersExplodedDir,
@@ -8149,14 +8466,16 @@ export async function createMultiXBom(pathList, options) {
8149
8466
  options.bundledSdks = bundledSdks;
8150
8467
  options.bundledRuntimes = bundledRuntimes;
8151
8468
  options.binPaths = binPaths;
8469
+ options.unpackagedExecutableCount = executables?.length || 0;
8470
+ options.unpackagedSharedLibraryCount = sharedLibs?.length || 0;
8152
8471
  if (DEBUG_MODE) {
8153
8472
  console.log(
8154
- `**OS**: Found ${osPackages.length} OS packages, ${executables?.length} executables, and ${sharedLibs.length} shared libraries at ${options.allLayersExplodedDir}`,
8473
+ `**OS**: Found ${osPackages.length} OS packages, ${osPackageFiles?.length} package-owned files, ${executables?.length} unpackaged executables, ${sharedLibs.length} unpackaged shared libraries, and ${services?.length || 0} packaged services at ${options.allLayersExplodedDir}`,
8155
8474
  );
8156
8475
  }
8157
8476
  if (osPackages.length) {
8158
8477
  thoughtLog(
8159
- `I found ${osPackages.length} OS packages and ${executables?.length} executables at ${options.allLayersExplodedDir}`,
8478
+ `I found ${osPackages.length} OS packages, ${osPackageFiles?.length || 0} package-owned files, and ${services?.length || 0} packaged services at ${options.allLayersExplodedDir}`,
8160
8479
  );
8161
8480
  } else if (executables?.length || sharedLibs?.length) {
8162
8481
  thoughtLog(
@@ -8178,14 +8497,20 @@ export async function createMultiXBom(pathList, options) {
8178
8497
  ).concat(tools);
8179
8498
  }
8180
8499
  components = components.concat(osPackages);
8500
+ components = components.concat(osPackageFiles || []);
8181
8501
  components = components.concat(executables);
8182
8502
  components = components.concat(sharedLibs);
8183
8503
  if (dependenciesList?.length) {
8184
8504
  dependencies = mergeDependencies(dependencies, dependenciesList);
8185
8505
  }
8506
+ if (services?.length) {
8507
+ options.services = mergeServices(options.services || [], services);
8508
+ }
8186
8509
  if (parentComponent && Object.keys(parentComponent).length) {
8187
8510
  // Make the parent oci image depend on all os components
8188
- const parentDependsOn = new Set(osPackages.map((p) => p["bom-ref"]));
8511
+ const parentDependsOn = new Set(
8512
+ osPackages.concat(osPackageFiles || []).map((p) => p["bom-ref"]),
8513
+ );
8189
8514
  dependencies.splice(0, 0, {
8190
8515
  ref: parentComponent["bom-ref"],
8191
8516
  dependsOn: Array.from(parentDependsOn).sort(),
@@ -8887,6 +9212,28 @@ export async function createMultiXBom(pathList, options) {
8887
9212
  }
8888
9213
  }
8889
9214
  }
9215
+ if (hasAnyProjectType(["asar"], options)) {
9216
+ setProjectTypeActivityContext("asar", path);
9217
+ bomData = await createAsarBom(path, options);
9218
+ if (bomData?.bomJson?.components?.length) {
9219
+ if (DEBUG_MODE) {
9220
+ console.log(
9221
+ `Found ${bomData.bomJson.components.length} ASAR component(s) at ${path}`,
9222
+ );
9223
+ }
9224
+ components = components.concat(bomData.bomJson.components);
9225
+ dependencies = mergeDependencies(
9226
+ dependencies,
9227
+ bomData.bomJson.dependencies,
9228
+ );
9229
+ if (
9230
+ bomData.parentComponent &&
9231
+ Object.keys(bomData.parentComponent).length
9232
+ ) {
9233
+ parentSubComponents.push(bomData.parentComponent);
9234
+ }
9235
+ }
9236
+ }
8890
9237
  if (hasAnyProjectType(["vscode-extension"], options)) {
8891
9238
  setProjectTypeActivityContext("vscode-extension", path);
8892
9239
  bomData = await createVscodeExtensionBom(path, options);
@@ -9326,6 +9673,16 @@ export async function createXBom(path, options) {
9326
9673
  return await createVscodeExtensionBom(path, options);
9327
9674
  }
9328
9675
 
9676
+ // Electron ASAR archives
9677
+ const asarFiles = getAllFiles(
9678
+ path,
9679
+ `${options.multiProject ? "**/" : ""}*.asar`,
9680
+ options,
9681
+ );
9682
+ if (asarFiles.length) {
9683
+ return await createAsarBom(path, options);
9684
+ }
9685
+
9329
9686
  // Helm charts
9330
9687
  const chartFiles = getAllFiles(
9331
9688
  path,
@@ -9428,6 +9785,35 @@ export async function createXBom(path, options) {
9428
9785
  }
9429
9786
  }
9430
9787
 
9788
+ /**
9789
+ * Function to create a hardware BOM for the current host.
9790
+ *
9791
+ * @param {string} _path Source path (unused for live host HBOM generation)
9792
+ * @param {Object} options Parse options from the cli
9793
+ * @returns {Promise<Object>} Promise resolving to BOM object
9794
+ */
9795
+ export async function createHBom(_path, options) {
9796
+ ensureHbomRuntimeSupport(options, options.commandName || "hbom");
9797
+ let bomJson = await createHbomDocument(options);
9798
+ if (options.includeRuntime) {
9799
+ const runtimeOptions = {
9800
+ ...options,
9801
+ includeRuntime: false,
9802
+ multiProject: false,
9803
+ projectType: ["os"],
9804
+ };
9805
+ const obomData = await createOSBom(_path, runtimeOptions);
9806
+ bomJson = mergeHostInventoryBoms(bomJson, obomData);
9807
+ } else {
9808
+ bomJson = mergeHostInventoryBoms(bomJson);
9809
+ }
9810
+ return {
9811
+ bomJson,
9812
+ dependencies: bomJson.dependencies || [],
9813
+ parentComponent: bomJson.metadata?.component,
9814
+ };
9815
+ }
9816
+
9431
9817
  /**
9432
9818
  * Function to create bom string for various languages
9433
9819
  *
@@ -9440,6 +9826,21 @@ export async function createBom(path, options) {
9440
9826
  if (!projectType) {
9441
9827
  projectType = [];
9442
9828
  }
9829
+ ensureNoMixedHbomProjectTypes(projectType);
9830
+ if (hasHbomProjectType(projectType)) {
9831
+ const selectedHbomProjectType = Array.isArray(projectType)
9832
+ ? projectType[0]
9833
+ : `${projectType}`.split(",")[0];
9834
+ options.projectType = [selectedHbomProjectType];
9835
+ setActivityContext({
9836
+ projectType: selectedHbomProjectType,
9837
+ sourcePath: path,
9838
+ });
9839
+ thoughtLog(
9840
+ "The user wants a Hardware Bill-of-Materials (HBOM) for the current host. Let's use the dedicated hardware collector.",
9841
+ );
9842
+ return await createHBom(path, options);
9843
+ }
9443
9844
  let exportData;
9444
9845
  let isContainerMode = false;
9445
9846
  const isLocalDirectoryInput =
@@ -9574,6 +9975,9 @@ export async function createBom(path, options) {
9574
9975
  if (path.endsWith(".war")) {
9575
9976
  projectType = ["java"];
9576
9977
  }
9978
+ if (!projectType.length && path.endsWith(".asar")) {
9979
+ projectType = ["asar"];
9980
+ }
9577
9981
  if (projectType.length > 1) {
9578
9982
  setActivityContext({
9579
9983
  projectType: projectType.join(","),
@@ -9697,6 +10101,9 @@ export async function createBom(path, options) {
9697
10101
  if (PROJECT_TYPE_ALIASES["caxa"].includes(projectType[0])) {
9698
10102
  return await createCaxaBom(path, options);
9699
10103
  }
10104
+ if (PROJECT_TYPE_ALIASES["asar"].includes(projectType[0])) {
10105
+ return await createAsarBom(path, options);
10106
+ }
9700
10107
  if (PROJECT_TYPE_ALIASES["vscode-extension"].includes(projectType[0])) {
9701
10108
  return await createVscodeExtensionBom(path, options);
9702
10109
  }
@@ -9741,17 +10148,33 @@ export async function createBom(path, options) {
9741
10148
  * @throws {Error} if the request fails
9742
10149
  */
9743
10150
  export async function submitBom(args, bomContents) {
10151
+ const dependencyTrackApiUrl = getDependencyTrackBomApiUrl(args.serverUrl);
10152
+ const serverUrl = dependencyTrackApiUrl?.toString();
10153
+ if (!dependencyTrackApiUrl || !serverUrl) {
10154
+ console.log(
10155
+ "Invalid Dependency-Track server URL. Provide an absolute http(s) URL without dangerous characters.",
10156
+ );
10157
+ args.failOnError && process.exit(1);
10158
+ return undefined;
10159
+ }
10160
+ const serverHost = dependencyTrackApiUrl.hostname;
10161
+ if (!isAllowedHttpHost(serverHost)) {
10162
+ console.log(
10163
+ `Dependency-Track server host '${serverHost}' is not allowed by CDXGEN_ALLOWED_HOSTS.`,
10164
+ );
10165
+ args.failOnError && process.exit(1);
10166
+ return undefined;
10167
+ }
9744
10168
  if (isDryRun) {
9745
10169
  recordActivity({
9746
10170
  kind: "network",
9747
10171
  reason:
9748
10172
  "Dry run mode blocks Dependency-Track submission and reports the request instead.",
9749
10173
  status: "blocked",
9750
- target: getDependencyTrackBomUrl(args.serverUrl),
10174
+ target: serverUrl,
9751
10175
  });
9752
10176
  return undefined;
9753
10177
  }
9754
- const serverUrl = getDependencyTrackBomUrl(args.serverUrl);
9755
10178
  const bomPayload = buildDependencyTrackBomPayload(args, bomContents);
9756
10179
  if (!bomPayload) {
9757
10180
  console.log(
@@ -9770,15 +10193,7 @@ export async function submitBom(args, bomContents) {
9770
10193
  );
9771
10194
  }
9772
10195
  try {
9773
- if (DEBUG_MODE && args.skipDtTlsCheck) {
9774
- console.log(
9775
- "Calling ",
9776
- serverUrl,
9777
- "with --skip-dt-tls-check argument: Skip DT TLS check.",
9778
- );
9779
- }
9780
- // See issue #1963 regarding CRLF hardening
9781
- return await got(serverUrl, {
10196
+ const requestOptions = {
9782
10197
  method: "PUT",
9783
10198
  followRedirect: false,
9784
10199
  https: {
@@ -9787,11 +10202,22 @@ export async function submitBom(args, bomContents) {
9787
10202
  headers: {
9788
10203
  "X-Api-Key": (args.apiKey || "").replace(/[\r\n]/g, ""),
9789
10204
  "Content-Type": "application/json",
9790
- "user-agent": `@CycloneDX/cdxgen ${CDXGEN_VERSION}`,
9791
10205
  },
9792
10206
  json: bomPayload,
9793
10207
  responseType: "json",
9794
- }).json();
10208
+ context: {
10209
+ activityIntent: "bom-submit",
10210
+ },
10211
+ };
10212
+ if (DEBUG_MODE && args.skipDtTlsCheck) {
10213
+ console.log(
10214
+ "Calling ",
10215
+ serverUrl,
10216
+ "with --skip-dt-tls-check argument: Skip DT TLS check.",
10217
+ );
10218
+ }
10219
+ // See issue #1963 regarding CRLF hardening
10220
+ return await cdxgenAgent(dependencyTrackApiUrl, requestOptions).json();
9795
10221
  } catch (error) {
9796
10222
  if (error.response && error.response.statusCode === 401) {
9797
10223
  // Unauthorized
@@ -9804,7 +10230,7 @@ export async function submitBom(args, bomContents) {
9804
10230
  );
9805
10231
  // Method not allowed errors
9806
10232
  try {
9807
- return await got(serverUrl, {
10233
+ return await cdxgenAgent(dependencyTrackApiUrl, {
9808
10234
  method: "POST",
9809
10235
  followRedirect: false,
9810
10236
  https: {
@@ -9813,10 +10239,12 @@ export async function submitBom(args, bomContents) {
9813
10239
  headers: {
9814
10240
  "X-Api-Key": (args.apiKey || "").replace(/[\r\n]/g, ""),
9815
10241
  "Content-Type": "application/json",
9816
- "user-agent": `@CycloneDX/cdxgen ${CDXGEN_VERSION}`,
9817
10242
  },
9818
10243
  json: bomPayload,
9819
10244
  responseType: "json",
10245
+ context: {
10246
+ activityIntent: "bom-submit",
10247
+ },
9820
10248
  }).json();
9821
10249
  } catch (error) {
9822
10250
  if (DEBUG_MODE) {