@cyclonedx/cdxgen 12.3.2 → 12.4.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 (182) hide show
  1. package/README.md +70 -22
  2. package/bin/audit.js +21 -7
  3. package/bin/cdxgen.js +238 -116
  4. package/bin/convert.js +28 -13
  5. package/bin/hbom.js +490 -0
  6. package/bin/repl.js +580 -29
  7. package/bin/validate.js +34 -4
  8. package/bin/verify.js +40 -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/predictive-audit-allowlist.json +11 -0
  13. package/data/queries-darwin.json +12 -1
  14. package/data/queries-win.json +7 -1
  15. package/data/queries.json +39 -2
  16. package/data/rules/ai-agent-governance.yaml +16 -0
  17. package/data/rules/asar-archives.yaml +150 -0
  18. package/data/rules/chrome-extensions.yaml +8 -0
  19. package/data/rules/ci-permissions.yaml +171 -15
  20. package/data/rules/container-risk.yaml +14 -7
  21. package/data/rules/dependency-sources.yaml +76 -5
  22. package/data/rules/hbom-compliance.yaml +325 -0
  23. package/data/rules/hbom-performance.yaml +307 -0
  24. package/data/rules/hbom-security.yaml +248 -0
  25. package/data/rules/host-topology.yaml +165 -0
  26. package/data/rules/mcp-servers.yaml +18 -3
  27. package/data/rules/obom-runtime.yaml +907 -22
  28. package/data/rules/package-integrity.yaml +36 -0
  29. package/data/rules/rootfs-hardening.yaml +179 -0
  30. package/data/rules/vscode-extensions.yaml +9 -0
  31. package/lib/audit/index.js +209 -8
  32. package/lib/audit/index.poku.js +332 -0
  33. package/lib/audit/reporters.js +222 -0
  34. package/lib/audit/targets.js +146 -1
  35. package/lib/audit/targets.poku.js +186 -0
  36. package/lib/cli/asar.poku.js +328 -0
  37. package/lib/cli/index.js +647 -127
  38. package/lib/cli/index.poku.js +1905 -187
  39. package/lib/evinser/evinser.js +14 -9
  40. package/lib/helpers/agentFormulationParser.js +6 -2
  41. package/lib/helpers/agentFormulationParser.poku.js +42 -0
  42. package/lib/helpers/analyzer.js +1444 -38
  43. package/lib/helpers/analyzer.poku.js +409 -0
  44. package/lib/helpers/analyzerScope.js +712 -0
  45. package/lib/helpers/asarutils.js +1556 -0
  46. package/lib/helpers/asarutils.poku.js +443 -0
  47. package/lib/helpers/auditCategories.js +12 -0
  48. package/lib/helpers/auditCategories.poku.js +32 -0
  49. package/lib/helpers/cbomutils.js +271 -1
  50. package/lib/helpers/cbomutils.poku.js +248 -5
  51. package/lib/helpers/chromextutils.js +25 -3
  52. package/lib/helpers/chromextutils.poku.js +68 -0
  53. package/lib/helpers/ciParsers/githubActions.js +79 -0
  54. package/lib/helpers/ciParsers/githubActions.poku.js +103 -0
  55. package/lib/helpers/communityAiConfigParser.js +15 -5
  56. package/lib/helpers/communityAiConfigParser.poku.js +71 -0
  57. package/lib/helpers/depsUtils.js +5 -0
  58. package/lib/helpers/depsUtils.poku.js +55 -0
  59. package/lib/helpers/display.js +336 -23
  60. package/lib/helpers/display.poku.js +179 -43
  61. package/lib/helpers/evidenceUtils.js +58 -0
  62. package/lib/helpers/evidenceUtils.poku.js +54 -0
  63. package/lib/helpers/exportUtils.js +9 -0
  64. package/lib/helpers/gtfobins.js +142 -8
  65. package/lib/helpers/gtfobins.poku.js +24 -1
  66. package/lib/helpers/hbom.js +710 -0
  67. package/lib/helpers/hbom.poku.js +496 -0
  68. package/lib/helpers/hbomAnalysis.js +268 -0
  69. package/lib/helpers/hbomAnalysis.poku.js +249 -0
  70. package/lib/helpers/hbomLoader.js +35 -0
  71. package/lib/helpers/hostTopology.js +803 -0
  72. package/lib/helpers/hostTopology.poku.js +363 -0
  73. package/lib/helpers/inventoryStats.js +69 -0
  74. package/lib/helpers/inventoryStats.poku.js +86 -0
  75. package/lib/helpers/lolbas.js +19 -1
  76. package/lib/helpers/lolbas.poku.js +23 -0
  77. package/lib/helpers/mcpConfigParser.js +21 -5
  78. package/lib/helpers/mcpConfigParser.poku.js +39 -2
  79. package/lib/helpers/osqueryTransform.js +47 -0
  80. package/lib/helpers/osqueryTransform.poku.js +47 -0
  81. package/lib/helpers/plugins.js +349 -0
  82. package/lib/helpers/plugins.poku.js +57 -0
  83. package/lib/helpers/propertySanitizer.js +121 -0
  84. package/lib/helpers/protobom.js +156 -45
  85. package/lib/helpers/protobom.poku.js +140 -5
  86. package/lib/helpers/remote/dependency-track.js +36 -3
  87. package/lib/helpers/remote/dependency-track.poku.js +44 -0
  88. package/lib/helpers/source.js +24 -0
  89. package/lib/helpers/source.poku.js +32 -0
  90. package/lib/helpers/utils.js +2454 -198
  91. package/lib/helpers/utils.poku.js +1798 -74
  92. package/lib/managers/binary.e2e.poku.js +367 -0
  93. package/lib/managers/binary.js +2306 -350
  94. package/lib/managers/binary.poku.js +1700 -1
  95. package/lib/managers/docker.js +441 -95
  96. package/lib/managers/docker.poku.js +1479 -14
  97. package/lib/server/server.js +2 -24
  98. package/lib/server/server.poku.js +36 -1
  99. package/lib/stages/postgen/annotator.js +38 -0
  100. package/lib/stages/postgen/annotator.poku.js +107 -1
  101. package/lib/stages/postgen/auditBom.js +121 -18
  102. package/lib/stages/postgen/auditBom.poku.js +2967 -990
  103. package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
  104. package/lib/stages/postgen/postgen.js +192 -1
  105. package/lib/stages/postgen/postgen.poku.js +321 -0
  106. package/lib/stages/postgen/ruleEngine.js +116 -0
  107. package/lib/stages/pregen/envAudit.js +14 -3
  108. package/package.json +24 -21
  109. package/types/bin/hbom.d.ts +3 -0
  110. package/types/bin/hbom.d.ts.map +1 -0
  111. package/types/bin/repl.d.ts.map +1 -1
  112. package/types/lib/audit/index.d.ts +44 -0
  113. package/types/lib/audit/index.d.ts.map +1 -1
  114. package/types/lib/audit/reporters.d.ts +16 -0
  115. package/types/lib/audit/reporters.d.ts.map +1 -1
  116. package/types/lib/audit/targets.d.ts.map +1 -1
  117. package/types/lib/cli/index.d.ts +16 -0
  118. package/types/lib/cli/index.d.ts.map +1 -1
  119. package/types/lib/evinser/evinser.d.ts +4 -0
  120. package/types/lib/evinser/evinser.d.ts.map +1 -1
  121. package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -1
  122. package/types/lib/helpers/analyzer.d.ts +33 -0
  123. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  124. package/types/lib/helpers/analyzerScope.d.ts +11 -0
  125. package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
  126. package/types/lib/helpers/asarutils.d.ts +34 -0
  127. package/types/lib/helpers/asarutils.d.ts.map +1 -0
  128. package/types/lib/helpers/auditCategories.d.ts +5 -0
  129. package/types/lib/helpers/auditCategories.d.ts.map +1 -1
  130. package/types/lib/helpers/cbomutils.d.ts +3 -2
  131. package/types/lib/helpers/cbomutils.d.ts.map +1 -1
  132. package/types/lib/helpers/chromextutils.d.ts.map +1 -1
  133. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  134. package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -1
  135. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  136. package/types/lib/helpers/display.d.ts +1 -0
  137. package/types/lib/helpers/display.d.ts.map +1 -1
  138. package/types/lib/helpers/evidenceUtils.d.ts +8 -0
  139. package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
  140. package/types/lib/helpers/exportUtils.d.ts.map +1 -1
  141. package/types/lib/helpers/gtfobins.d.ts +8 -0
  142. package/types/lib/helpers/gtfobins.d.ts.map +1 -1
  143. package/types/lib/helpers/hbom.d.ts +49 -0
  144. package/types/lib/helpers/hbom.d.ts.map +1 -0
  145. package/types/lib/helpers/hbomAnalysis.d.ts +62 -0
  146. package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
  147. package/types/lib/helpers/hbomLoader.d.ts +7 -0
  148. package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
  149. package/types/lib/helpers/hostTopology.d.ts +12 -0
  150. package/types/lib/helpers/hostTopology.d.ts.map +1 -0
  151. package/types/lib/helpers/inventoryStats.d.ts +11 -0
  152. package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
  153. package/types/lib/helpers/lolbas.d.ts.map +1 -1
  154. package/types/lib/helpers/mcpConfigParser.d.ts +1 -1
  155. package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -1
  156. package/types/lib/helpers/osqueryTransform.d.ts +3 -0
  157. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
  158. package/types/lib/helpers/plugins.d.ts +58 -0
  159. package/types/lib/helpers/plugins.d.ts.map +1 -0
  160. package/types/lib/helpers/propertySanitizer.d.ts +3 -0
  161. package/types/lib/helpers/propertySanitizer.d.ts.map +1 -0
  162. package/types/lib/helpers/protobom.d.ts +3 -4
  163. package/types/lib/helpers/protobom.d.ts.map +1 -1
  164. package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
  165. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  166. package/types/lib/helpers/source.d.ts.map +1 -1
  167. package/types/lib/helpers/utils.d.ts +74 -8
  168. package/types/lib/helpers/utils.d.ts.map +1 -1
  169. package/types/lib/managers/binary.d.ts +5 -0
  170. package/types/lib/managers/binary.d.ts.map +1 -1
  171. package/types/lib/managers/docker.d.ts +3 -0
  172. package/types/lib/managers/docker.d.ts.map +1 -1
  173. package/types/lib/server/server.d.ts +2 -0
  174. package/types/lib/server/server.d.ts.map +1 -1
  175. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  176. package/types/lib/stages/postgen/auditBom.d.ts +26 -1
  177. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  178. package/types/lib/stages/postgen/postgen.d.ts +2 -1
  179. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  180. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  181. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  182. 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,15 @@ 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";
39
44
  import { parseCaxaMetadata } from "../helpers/caxa.js";
45
+ import { collectSourceCryptoComponents } from "../helpers/cbomutils.js";
40
46
  import {
41
47
  CHROME_EXTENSION_PURL_TYPE,
42
48
  collectChromeExtensionsFromPath,
@@ -49,27 +55,33 @@ import {
49
55
  trimComponents,
50
56
  } from "../helpers/depsUtils.js";
51
57
  import { GIT_COMMAND } from "../helpers/envcontext.js";
52
- import { thoughtLog } from "../helpers/logger.js";
53
58
  import {
54
- classifyMcpReference,
55
- enrichComponentWithMcpMetadata,
56
- } from "../helpers/mcp.js";
59
+ createHbomDocument,
60
+ ensureHbomRuntimeSupport,
61
+ ensureNoMixedHbomProjectTypes,
62
+ hasHbomProjectType,
63
+ } from "../helpers/hbom.js";
64
+ import { mergeHostInventoryBoms } from "../helpers/hostTopology.js";
65
+ import { thoughtLog } from "../helpers/logger.js";
66
+ import { enrichComponentWithMcpMetadata } from "../helpers/mcp.js";
57
67
  import { isPyLockFile } from "../helpers/pylockutils.js";
58
68
  import {
59
69
  buildDependencyTrackBomPayload,
60
- getDependencyTrackBomUrl,
70
+ getDependencyTrackBomApiUrl,
61
71
  } from "../helpers/remote/dependency-track.js";
62
72
  import { table } from "../helpers/table.js";
63
73
  import {
64
74
  addEvidenceForDotnet,
65
75
  addEvidenceForImports,
66
76
  addPlugin,
77
+ attachIdentityTools,
67
78
  buildGradleCommandArguments,
68
79
  buildObjectForCocoaPod,
69
80
  buildObjectForGradleModule,
70
81
  CARGO_CMD,
71
82
  CDXGEN_VERSION,
72
83
  CLJ_CMD,
84
+ cdxgenAgent,
73
85
  checksumFile,
74
86
  cleanupPlugin,
75
87
  collectGemModuleNames,
@@ -88,6 +100,7 @@ import {
88
100
  executeParallelGradleProperties,
89
101
  executePodCommand,
90
102
  extractJarArchive,
103
+ extractToolRefs,
91
104
  frameworksList,
92
105
  generatePixiLockFile,
93
106
  getAllFiles,
@@ -109,6 +122,7 @@ import {
109
122
  getTmpDir,
110
123
  hasAnyProjectType,
111
124
  includeMavenTestScope,
125
+ isAllowedHttpHost,
112
126
  isDryRun,
113
127
  isFeatureEnabled,
114
128
  isMac,
@@ -134,6 +148,7 @@ import {
134
148
  parseCloudBuildData,
135
149
  parseCmakeLikeFile,
136
150
  parseCocoaDependency,
151
+ parseColliderLockData,
137
152
  parseComposerJson,
138
153
  parseComposerLock,
139
154
  parseConanData,
@@ -198,6 +213,7 @@ import {
198
213
  readZipEntry,
199
214
  recomputeScope,
200
215
  recordActivity,
216
+ recordSensitiveFileRead,
201
217
  resetActivityContext,
202
218
  SWIFT_CMD,
203
219
  safeExistsSync,
@@ -223,10 +239,12 @@ import {
223
239
  VSCODE_EXTENSION_PURL_TYPE,
224
240
  } from "../helpers/vsixutils.js";
225
241
  import {
242
+ enrichOSComponentsWithTrustData,
226
243
  executeOsQuery,
227
244
  getBinaryBom,
228
245
  getDotnetSlices,
229
246
  getOSPackages,
247
+ getPluginToolComponents,
230
248
  } from "../managers/binary.js";
231
249
  import {
232
250
  addSkippedSrcFiles,
@@ -329,7 +347,10 @@ const createDefaultParentComponent = (
329
347
  group: options.projectGroup || "",
330
348
  name: compName,
331
349
  version: `${options.projectVersion}` || "latest",
332
- type: compName.endsWith(".tar") ? "container" : "application",
350
+ type:
351
+ type === "container" || compName.endsWith(".tar")
352
+ ? "container"
353
+ : "application",
333
354
  };
334
355
  const ppurl = new PackageURL(
335
356
  type,
@@ -344,6 +365,37 @@ const createDefaultParentComponent = (
344
365
  return parentComponent;
345
366
  };
346
367
 
368
+ const shouldIncludeNodeModulesDir = (options = {}, baseProjectTypes = []) => {
369
+ if (options.deep) {
370
+ return true;
371
+ }
372
+ const projectTypes = Array.isArray(options.projectType)
373
+ ? options.projectType
374
+ : options.projectType
375
+ ? [options.projectType]
376
+ : [];
377
+ if (!projectTypes.length) {
378
+ return true;
379
+ }
380
+ return baseProjectTypes.some((projectType) =>
381
+ projectTypes.every((selectedProjectType) =>
382
+ PROJECT_TYPE_ALIASES[projectType]?.includes(selectedProjectType),
383
+ ),
384
+ );
385
+ };
386
+
387
+ const hasExplicitProjectTypeSelection = (options, baseProjectType) => {
388
+ options = options || {};
389
+ const projectTypes = Array.isArray(options.projectType)
390
+ ? options.projectType
391
+ : options.projectType
392
+ ? [options.projectType]
393
+ : [];
394
+ return projectTypes.some((selectedProjectType) =>
395
+ PROJECT_TYPE_ALIASES[baseProjectType]?.includes(selectedProjectType),
396
+ );
397
+ };
398
+
347
399
  const determineParentComponent = (options) => {
348
400
  let parentComponent;
349
401
  if (options.parentComponent && Object.keys(options.parentComponent).length) {
@@ -815,6 +867,18 @@ function addMetadata(parentComponent = {}, options = {}, context = {}) {
815
867
  value: options.allOSComponentTypes.sort().join("\\n"),
816
868
  });
817
869
  }
870
+ if (Number.isInteger(options?.unpackagedExecutableCount)) {
871
+ mproperties.push({
872
+ name: "cdx:container:unpackagedExecutableCount",
873
+ value: String(options.unpackagedExecutableCount),
874
+ });
875
+ }
876
+ if (Number.isInteger(options?.unpackagedSharedLibraryCount)) {
877
+ mproperties.push({
878
+ name: "cdx:container:unpackagedSharedLibraryCount",
879
+ value: String(options.unpackagedSharedLibraryCount),
880
+ });
881
+ }
818
882
  // Should we move these to formulation?
819
883
  if (options?.bundledSdks?.length) {
820
884
  for (const sdk of options.bundledSdks) {
@@ -918,6 +982,9 @@ export function listComponents(options, allImports, pkg, ptype = "npm") {
918
982
  return Object.keys(compMap).map((k) => compMap[k]);
919
983
  }
920
984
 
985
+ // These component types do not have PURLs
986
+ const NON_PURL_TYPES = ["cryptographic-asset", "file", "data"];
987
+
921
988
  /**
922
989
  * Given the specified package, create a CycloneDX component and add it to the list.
923
990
  */
@@ -954,9 +1021,9 @@ function addComponent(
954
1021
  }
955
1022
  const version = pkg.version || "";
956
1023
  const licenses = pkg.licenses || getLicenses(pkg);
957
- let purl =
958
- pkg.purl ||
959
- new PackageURL(
1024
+ let purl = pkg.purl;
1025
+ if (!purl && ptype) {
1026
+ purl = new PackageURL(
960
1027
  ptype,
961
1028
  encodeForPurl(group),
962
1029
  encodeForPurl(name),
@@ -964,9 +1031,9 @@ function addComponent(
964
1031
  pkg.qualifiers,
965
1032
  encodeForPurl(pkg.subpath),
966
1033
  );
967
- let purlString = purl.toString();
968
- // There is no purl for cryptographic-asset
969
- if (ptype === "cryptographic-asset") {
1034
+ }
1035
+ let purlString = purl?.toString();
1036
+ if (NON_PURL_TYPES.includes(ptype) || NON_PURL_TYPES.includes(pkg.type)) {
970
1037
  purl = undefined;
971
1038
  purlString = undefined;
972
1039
  }
@@ -1077,7 +1144,7 @@ function addComponent(
1077
1144
  if (pkg.properties?.length) {
1078
1145
  component.properties = pkg.properties;
1079
1146
  }
1080
- if (pkg.cryptoProperties?.length) {
1147
+ if (pkg.cryptoProperties && typeof pkg.cryptoProperties === "object") {
1081
1148
  component.cryptoProperties = pkg.cryptoProperties;
1082
1149
  }
1083
1150
  // Retain nested components
@@ -1368,17 +1435,21 @@ export async function createJarBom(path, options) {
1368
1435
  let pkgList = [];
1369
1436
  let jarFiles;
1370
1437
  let nsMapping = {};
1371
- if (!options.exclude) {
1372
- options.exclude = [];
1373
- }
1374
- // We can look for jar files within node_modules directory
1375
- if (typeof options.includeNodeModulesDir === "undefined" || options.deep) {
1376
- options.includeNodeModulesDir = true;
1438
+ const searchOptions = {
1439
+ ...options,
1440
+ exclude: [...(options.exclude || [])],
1441
+ };
1442
+ if (typeof searchOptions.includeNodeModulesDir === "undefined") {
1443
+ searchOptions.includeNodeModulesDir = shouldIncludeNodeModulesDir(options, [
1444
+ "jar",
1445
+ "war",
1446
+ "ear",
1447
+ ]);
1377
1448
  }
1378
1449
  // Exclude certain directories during oci sbom generation
1379
1450
  if (hasAnyProjectType(["oci"], options, false)) {
1380
- options.exclude.push("**/android-sdk*/**");
1381
- options.exclude.push("**/.sdkman/**");
1451
+ searchOptions.exclude.push("**/android-sdk*/**");
1452
+ searchOptions.exclude.push("**/.sdkman/**");
1382
1453
  }
1383
1454
  const parentComponent = createDefaultParentComponent(path, "maven", options);
1384
1455
  if (options.useGradleCache) {
@@ -1402,14 +1473,14 @@ export async function createJarBom(path, options) {
1402
1473
  jarFiles = getAllFiles(
1403
1474
  path,
1404
1475
  `${options.multiProject ? "**/" : ""}*.[jw]ar`,
1405
- options,
1476
+ searchOptions,
1406
1477
  );
1407
1478
  }
1408
1479
  // Jenkins plugins
1409
1480
  const hpiFiles = getAllFiles(
1410
1481
  path,
1411
1482
  `${options.multiProject ? "**/" : ""}*.hpi`,
1412
- options,
1483
+ searchOptions,
1413
1484
  );
1414
1485
  if (hpiFiles.length) {
1415
1486
  jarFiles = jarFiles.concat(hpiFiles);
@@ -1464,6 +1535,13 @@ export function createBinaryBom(path, options) {
1464
1535
  const binaryBom = JSON.parse(
1465
1536
  readFileSync(binaryBomFile, { encoding: "utf-8" }),
1466
1537
  );
1538
+ attachIdentityTools(
1539
+ binaryBom?.components,
1540
+ extractToolRefs(
1541
+ binaryBom?.metadata?.tools,
1542
+ (tool) => tool?.name !== "cdxgen",
1543
+ ),
1544
+ );
1467
1545
  return {
1468
1546
  bomJson: binaryBom,
1469
1547
  dependencies: binaryBom.dependencies,
@@ -1891,6 +1969,10 @@ export async function createJavaBom(path, options) {
1891
1969
  ) {
1892
1970
  tools = bomJsonObj.metadata.tools;
1893
1971
  }
1972
+ const toolRefs = extractToolRefs(
1973
+ bomJsonObj?.metadata?.tools,
1974
+ (tool) => tool?.name !== "cdxgen",
1975
+ );
1894
1976
  if (
1895
1977
  bomJsonObj.metadata?.component &&
1896
1978
  !Object.keys(parentComponent).length
@@ -1927,6 +2009,7 @@ export async function createJavaBom(path, options) {
1927
2009
  name: "SrcFile",
1928
2010
  value: srcPomFile,
1929
2011
  });
2012
+ attachIdentityTools(acomp, toolRefs);
1930
2013
  }
1931
2014
  }
1932
2015
  pkgList = pkgList.concat(bomJsonObj.components);
@@ -2728,11 +2811,10 @@ export async function createJavaBom(path, options) {
2728
2811
  }
2729
2812
 
2730
2813
  /**
2731
- * Function to create bom string for Node.js projects
2814
+ * Identify the requested AI inventory project types.
2732
2815
  *
2733
- * @param {string} path to the project
2734
2816
  * @param {Object} options Parse options from the cli
2735
- * @returns {Promise<Object>} Promise resolving to BOM object
2817
+ * @returns {string[]} Requested AI inventory types
2736
2818
  */
2737
2819
  function getRequestedAiInventoryTypes(options) {
2738
2820
  return AI_INVENTORY_PROJECT_TYPES.filter((type) =>
@@ -2746,6 +2828,48 @@ function getExcludedAiInventoryTypes(options) {
2746
2828
  );
2747
2829
  }
2748
2830
 
2831
+ function filterIncludedAiInventoryTypes(
2832
+ includedAiInventoryTypes,
2833
+ excludedAiInventoryTypes,
2834
+ ) {
2835
+ return [...new Set(includedAiInventoryTypes)].filter(
2836
+ (type) => !excludedAiInventoryTypes.includes(type),
2837
+ );
2838
+ }
2839
+
2840
+ /**
2841
+ * Determine which AI inventory types should be collected for a scan.
2842
+ *
2843
+ * This combines explicit project-type opt-ins with BOM audit category-driven
2844
+ * opt-ins, then removes any explicitly excluded inventory types.
2845
+ *
2846
+ * @param {Object} options Parse options from the CLI
2847
+ * @returns {string[]} AI inventory types to collect
2848
+ */
2849
+ function getIncludedAiInventoryTypes(options) {
2850
+ const requestedAiInventoryTypes = getRequestedAiInventoryTypes(options);
2851
+ const excludedAiInventoryTypes = getExcludedAiInventoryTypes(options);
2852
+ const exactAiInventoryType = getExactAiInventoryType(options);
2853
+ if (exactAiInventoryType) {
2854
+ return filterIncludedAiInventoryTypes(
2855
+ requestedAiInventoryTypes,
2856
+ excludedAiInventoryTypes,
2857
+ );
2858
+ }
2859
+ const auditCategories = expandBomAuditCategories(options?.bomAuditCategories);
2860
+ const includedAiInventoryTypes = [...requestedAiInventoryTypes];
2861
+ if (auditCategories.includes("ai-agent")) {
2862
+ includedAiInventoryTypes.push("ai-skill");
2863
+ }
2864
+ if (auditCategories.includes("mcp-server")) {
2865
+ includedAiInventoryTypes.push("mcp");
2866
+ }
2867
+ return filterIncludedAiInventoryTypes(
2868
+ includedAiInventoryTypes,
2869
+ excludedAiInventoryTypes,
2870
+ );
2871
+ }
2872
+
2749
2873
  function getExactAiInventoryType(options) {
2750
2874
  const requestedAiInventoryTypes = getRequestedAiInventoryTypes(options);
2751
2875
  return requestedAiInventoryTypes.length === 1 &&
@@ -2755,22 +2879,14 @@ function getExactAiInventoryType(options) {
2755
2879
  : undefined;
2756
2880
  }
2757
2881
 
2758
- function shouldDetectMcpInventory(options, allImports = {}) {
2759
- if (hasAnyProjectType(["mcp"], options, false)) {
2760
- return true;
2761
- }
2762
- const auditCategories = expandBomAuditCategories(options?.bomAuditCategories);
2763
- if (
2764
- auditCategories.some((category) =>
2765
- ["mcp-server", "ai-agent"].includes(category),
2766
- )
2767
- ) {
2768
- return true;
2769
- }
2770
- return Object.keys(allImports).some((importName) => {
2771
- const classification = classifyMcpReference(importName);
2772
- return classification.isMcp;
2773
- });
2882
+ /**
2883
+ * Determine whether MCP source-code analysis should run for the current scan.
2884
+ *
2885
+ * @param {string[]} includedAiInventoryTypes AI inventory types selected for collection
2886
+ * @returns {boolean} True when MCP inventory collection is enabled
2887
+ */
2888
+ function shouldDetectMcpInventory(includedAiInventoryTypes) {
2889
+ return includedAiInventoryTypes.includes("mcp");
2774
2890
  }
2775
2891
 
2776
2892
  function summarizeAiInventoryNames(subjects, discoveryPath, kindSet) {
@@ -2865,14 +2981,8 @@ export async function createNodejsBom(path, options) {
2865
2981
  let parentComponent = {};
2866
2982
  const parentSubComponents = [];
2867
2983
  let ppurl = "";
2868
- const requestedAiInventoryTypes = getRequestedAiInventoryTypes(options);
2869
- const excludedAiInventoryTypes = getExcludedAiInventoryTypes(options);
2870
2984
  const exactAiInventoryType = getExactAiInventoryType(options);
2871
- const includedAiInventoryTypes = exactAiInventoryType
2872
- ? requestedAiInventoryTypes
2873
- : AI_INVENTORY_PROJECT_TYPES.filter(
2874
- (type) => !excludedAiInventoryTypes.includes(type),
2875
- );
2985
+ const includedAiInventoryTypes = getIncludedAiInventoryTypes(options);
2876
2986
  let aiInventory = { components: [], dependencies: [], services: [] };
2877
2987
  // Docker mode requires special handling
2878
2988
  if (hasAnyProjectType(["docker", "oci", "container", "os"], options, false)) {
@@ -2924,16 +3034,13 @@ export async function createNodejsBom(path, options) {
2924
3034
  const retData = await findJSImportsExports(path, options.deep);
2925
3035
  allImports = retData.allImports;
2926
3036
  allExports = retData.allExports;
2927
- if (shouldDetectMcpInventory(options, allImports)) {
3037
+ if (shouldDetectMcpInventory(includedAiInventoryTypes)) {
2928
3038
  mcpInventory = detectMcpInventory(path, options.deep);
2929
3039
  }
2930
3040
  }
2931
3041
  if (includedAiInventoryTypes.length) {
2932
3042
  aiInventory = collectAiInventory(path, options, includedAiInventoryTypes);
2933
3043
  }
2934
- if (excludedAiInventoryTypes.includes("mcp")) {
2935
- mcpInventory = { components: [], dependencies: [], services: [] };
2936
- }
2937
3044
  const aiInventorySummary = summarizeAiInventory(aiInventory);
2938
3045
  if (!exactAiInventoryType) {
2939
3046
  if (aiInventorySummary.instructionCount || aiInventorySummary.skillCount) {
@@ -2946,7 +3053,6 @@ export async function createNodejsBom(path, options) {
2946
3053
  `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.`,
2947
3054
  );
2948
3055
  }
2949
- emitAiInventorySummary(aiInventory, path);
2950
3056
  }
2951
3057
  if (exactAiInventoryType === "ai-skill") {
2952
3058
  const exactComponents = trimComponents([...(aiInventory.components || [])]);
@@ -3126,11 +3232,15 @@ export async function createNodejsBom(path, options) {
3126
3232
  }
3127
3233
  const basePath = dirname(apkgJson);
3128
3234
  let npmrcData;
3129
- if (safeExistsSync(join(basePath, ".npmrc"))) {
3235
+ const npmrcFile = join(basePath, ".npmrc");
3236
+ if (safeExistsSync(npmrcFile)) {
3130
3237
  thoughtLog(
3131
3238
  "Wait, there is a .npmrc file here! I'm going to check if it has anything malicious.",
3132
3239
  );
3133
- npmrcData = readFileSync(join(basePath, ".npmrc"), "utf-8");
3240
+ npmrcData = readFileSync(npmrcFile, "utf-8");
3241
+ recordSensitiveFileRead(npmrcFile, {
3242
+ label: "npm registry configuration",
3243
+ });
3134
3244
  const npmrcObj = parseNpmrc(npmrcData);
3135
3245
  for (const [key, value] of Object.entries(npmrcObj)) {
3136
3246
  const baseKey = key.replace(/^(?:\/\/[^/]+\/|@[^:]+:)/, "");
@@ -4061,14 +4171,7 @@ export async function createPythonBom(path, options) {
4061
4171
  let dependencies = [];
4062
4172
  let pkgList = [];
4063
4173
  let formulationList = [];
4064
- const requestedAiInventoryTypes = getRequestedAiInventoryTypes(options);
4065
- const excludedAiInventoryTypes = getExcludedAiInventoryTypes(options);
4066
- const includedAiInventoryTypes = AI_INVENTORY_PROJECT_TYPES.filter(
4067
- (type) =>
4068
- (!requestedAiInventoryTypes.length ||
4069
- requestedAiInventoryTypes.includes(type)) &&
4070
- !excludedAiInventoryTypes.includes(type),
4071
- );
4174
+ const includedAiInventoryTypes = getIncludedAiInventoryTypes(options);
4072
4175
  let aiInventory = { components: [], dependencies: [], services: [] };
4073
4176
  let mcpInventory = { components: [], dependencies: [], services: [] };
4074
4177
  const tempDir = safeMkdtempSync(join(getTmpDir(), "cdxgen-venv-"));
@@ -5416,6 +5519,11 @@ export function createCppBom(path, options) {
5416
5519
  let parentComponent;
5417
5520
  let dependencies = [];
5418
5521
  const addedParentComponentsMap = {};
5522
+ const colliderLockFiles = getAllFiles(
5523
+ path,
5524
+ `${options.multiProject ? "**/" : ""}collider.lock`,
5525
+ options,
5526
+ );
5419
5527
  const conanLockFiles = getAllFiles(
5420
5528
  path,
5421
5529
  `${options.multiProject ? "**/" : ""}conan.lock`,
@@ -5492,6 +5600,39 @@ export function createCppBom(path, options) {
5492
5600
  }
5493
5601
  }
5494
5602
  }
5603
+ if (colliderLockFiles.length) {
5604
+ for (const f of colliderLockFiles) {
5605
+ if (DEBUG_MODE) {
5606
+ console.log(`Parsing ${f}`);
5607
+ }
5608
+ const colliderLockData = readFileSync(f, { encoding: "utf-8" });
5609
+ const {
5610
+ pkgList: colliderPkgList,
5611
+ dependencies: colliderDependencies,
5612
+ parentComponentDependencies: parentCompDeps,
5613
+ } = parseColliderLockData(colliderLockData, f);
5614
+
5615
+ if (colliderPkgList.length) {
5616
+ pkgList = pkgList.concat(colliderPkgList);
5617
+ }
5618
+
5619
+ if (Object.keys(colliderDependencies).length) {
5620
+ dependencies = mergeDependencies(
5621
+ dependencies,
5622
+ Object.keys(colliderDependencies).map((dependentBomRef) => ({
5623
+ ref: dependentBomRef,
5624
+ dependsOn: colliderDependencies[dependentBomRef],
5625
+ })),
5626
+ );
5627
+ }
5628
+
5629
+ if (parentCompDeps.length) {
5630
+ parentComponentDependencies = [
5631
+ ...new Set(parentComponentDependencies.concat(parentCompDeps)),
5632
+ ];
5633
+ }
5634
+ }
5635
+ }
5495
5636
  if (cmakeLikeFiles.length) {
5496
5637
  for (const f of cmakeLikeFiles) {
5497
5638
  if (DEBUG_MODE) {
@@ -5876,6 +6017,7 @@ export function createOSBom(_path, options) {
5876
6017
  let pkgList = [];
5877
6018
  let bomData = {};
5878
6019
  let parentComponent = {};
6020
+ let externalTools = getPluginToolComponents(["osquery"]);
5879
6021
  for (const queryCategory of Object.keys(osQueries)) {
5880
6022
  const queryObj = osQueries[queryCategory];
5881
6023
  const results = executeOsQuery(queryObj.query);
@@ -5894,6 +6036,20 @@ export function createOSBom(_path, options) {
5894
6036
  );
5895
6037
  }
5896
6038
  } // for
6039
+ const hostTrustInventory = enrichOSComponentsWithTrustData(pkgList);
6040
+ if (hostTrustInventory?.components?.length) {
6041
+ pkgList = hostTrustInventory.components;
6042
+ }
6043
+ if (hostTrustInventory?.tools?.length) {
6044
+ externalTools = externalTools.concat(hostTrustInventory.tools);
6045
+ }
6046
+ if (externalTools.length) {
6047
+ options.tools = Array.from(
6048
+ new Map(
6049
+ externalTools.map((tool) => [tool["bom-ref"] || tool.name, tool]),
6050
+ ).values(),
6051
+ );
6052
+ }
5897
6053
  if (pkgList.length) {
5898
6054
  bomData = buildBomNSData(options, pkgList, "", {
5899
6055
  src: "",
@@ -6830,24 +6986,26 @@ export async function createContainerSpecLikeBom(path, options) {
6830
6986
  export function createPHPBom(path, options) {
6831
6987
  let dependencies = [];
6832
6988
  let parentComponent = {};
6833
- // We can look for composer files within node_modules directory
6834
- if (typeof options.includeNodeModulesDir === "undefined" || options.deep) {
6835
- options.includeNodeModulesDir = true;
6836
- }
6989
+ const searchOptions = {
6990
+ ...options,
6991
+ includeNodeModulesDir:
6992
+ typeof options.includeNodeModulesDir === "undefined"
6993
+ ? shouldIncludeNodeModulesDir(options, ["php"])
6994
+ : options.includeNodeModulesDir,
6995
+ };
6996
+ const composerLockSearchOptions = {
6997
+ ...searchOptions,
6998
+ exclude: [...(options.exclude || []), "**/vendor/**"],
6999
+ };
6837
7000
  const composerJsonFiles = getAllFiles(
6838
7001
  path,
6839
7002
  `${options.multiProject ? "**/" : ""}composer.json`,
6840
- options,
7003
+ searchOptions,
6841
7004
  );
6842
- if (!options.exclude) {
6843
- options.exclude = [];
6844
- }
6845
- // Ignore vendor directories for lock files
6846
- options.exclude.push("**/vendor/**");
6847
7005
  let composerLockFiles = getAllFiles(
6848
7006
  path,
6849
7007
  `${options.multiProject ? "**/" : ""}composer.lock`,
6850
- options,
7008
+ composerLockSearchOptions,
6851
7009
  );
6852
7010
  let pkgList = [];
6853
7011
  const composerJsonMode = composerJsonFiles.length;
@@ -6910,7 +7068,7 @@ export function createPHPBom(path, options) {
6910
7068
  composerLockFiles = getAllFiles(
6911
7069
  path,
6912
7070
  `${options.multiProject ? "**/" : ""}composer.lock`,
6913
- options,
7071
+ composerLockSearchOptions,
6914
7072
  );
6915
7073
  if (composerLockFiles.length) {
6916
7074
  // Look for any root composer.json to capture the parentComponent
@@ -7695,6 +7853,10 @@ export async function createVscodeExtensionBom(path, options) {
7695
7853
  let pkgList = [];
7696
7854
  let dependencies = [];
7697
7855
  const tempDirs = [];
7856
+ const shouldDiscoverInstalledIdeExtensions = hasExplicitProjectTypeSelection(
7857
+ options,
7858
+ "vscode-extension",
7859
+ );
7698
7860
 
7699
7861
  // Mode 1: Scan for .vsix files in the given directory, or treat the input
7700
7862
  // path as a single .vsix file.
@@ -7740,7 +7902,7 @@ export async function createVscodeExtensionBom(path, options) {
7740
7902
  }
7741
7903
 
7742
7904
  // Mode 2: Auto-discover extensions from known IDE locations
7743
- if (options.deep || options.projectType?.includes("ide-extensions")) {
7905
+ if (shouldDiscoverInstalledIdeExtensions) {
7744
7906
  const ideDirs = discoverIdeExtensionDirs();
7745
7907
  if (ideDirs.length) {
7746
7908
  if (DEBUG_MODE) {
@@ -7782,6 +7944,198 @@ export async function createVscodeExtensionBom(path, options) {
7782
7944
  });
7783
7945
  }
7784
7946
 
7947
+ /**
7948
+ * Function to create BOM for Electron ASAR archives.
7949
+ *
7950
+ * @param {string} path to a single archive or a directory to scan
7951
+ * @param {Object} options Parse options from the cli
7952
+ * @returns {Promise<Object>} Promise resolving to BOM object
7953
+ */
7954
+ export async function createAsarBom(path, options) {
7955
+ let pkgList = [];
7956
+ let dependencies = [];
7957
+ let parentComponent = {};
7958
+ const tempDirs = [];
7959
+ const processedArchives = new Set();
7960
+ const maxNestedAsarDepth = 4;
7961
+ const explicitAsarPath = path.endsWith(".asar") ? resolve(path) : undefined;
7962
+ const asarFiles = explicitAsarPath
7963
+ ? [explicitAsarPath]
7964
+ : getAllFiles(path, `${options.multiProject ? "**/" : ""}*.asar`, options);
7965
+
7966
+ const aggregateArchiveResults = (
7967
+ archiveAnalysis,
7968
+ isPrimaryArchive = false,
7969
+ ) => {
7970
+ if (
7971
+ archiveAnalysis.parentComponent &&
7972
+ Object.keys(archiveAnalysis.parentComponent).length
7973
+ ) {
7974
+ if (isPrimaryArchive && !Object.keys(parentComponent).length) {
7975
+ parentComponent = archiveAnalysis.parentComponent;
7976
+ } else {
7977
+ pkgList.push(archiveAnalysis.parentComponent);
7978
+ }
7979
+ }
7980
+ if (archiveAnalysis.components?.length) {
7981
+ pkgList = pkgList.concat(archiveAnalysis.components);
7982
+ }
7983
+ if (archiveAnalysis.dependencies?.length) {
7984
+ dependencies = mergeDependencies(
7985
+ dependencies,
7986
+ archiveAnalysis.dependencies,
7987
+ parentComponent,
7988
+ );
7989
+ }
7990
+ };
7991
+
7992
+ const analyzeExtractedArchive = async (
7993
+ extractedDir,
7994
+ archiveAnalysis,
7995
+ archiveIdentityPath,
7996
+ ) => {
7997
+ if (!archiveAnalysis.packageManifestPaths?.length) {
7998
+ return undefined;
7999
+ }
8000
+ const nodeBomOptions = {
8001
+ ...options,
8002
+ installDeps: false,
8003
+ multiProject: true,
8004
+ noBabel: false,
8005
+ path: extractedDir,
8006
+ projectType: ["js"],
8007
+ };
8008
+ const nodeBomData = await createNodejsBom(extractedDir, nodeBomOptions);
8009
+ if (nodeBomData?.bomJson?.components?.length) {
8010
+ rewriteExtractedArchivePaths(
8011
+ nodeBomData.bomJson.components,
8012
+ extractedDir,
8013
+ archiveIdentityPath,
8014
+ );
8015
+ pkgList = pkgList.concat(nodeBomData.bomJson.components);
8016
+ }
8017
+ if (nodeBomData?.bomJson?.dependencies?.length) {
8018
+ dependencies = mergeDependencies(
8019
+ dependencies,
8020
+ nodeBomData.bomJson.dependencies,
8021
+ );
8022
+ }
8023
+ if (
8024
+ archiveAnalysis.parentComponent?.["bom-ref"] &&
8025
+ nodeBomData?.parentComponent?.["bom-ref"] &&
8026
+ archiveAnalysis.parentComponent["bom-ref"] !==
8027
+ nodeBomData.parentComponent["bom-ref"]
8028
+ ) {
8029
+ rewriteExtractedArchivePaths(
8030
+ nodeBomData.parentComponent,
8031
+ extractedDir,
8032
+ archiveIdentityPath,
8033
+ );
8034
+ dependencies = mergeDependencies(dependencies, [
8035
+ {
8036
+ ref: archiveAnalysis.parentComponent["bom-ref"],
8037
+ dependsOn: [nodeBomData.parentComponent["bom-ref"]],
8038
+ },
8039
+ ]);
8040
+ }
8041
+ return nodeBomData;
8042
+ };
8043
+
8044
+ const processAsarArchive = async (
8045
+ archivePath,
8046
+ archiveIdentityPath,
8047
+ isPrimaryArchive = false,
8048
+ depth = 0,
8049
+ ) => {
8050
+ const processedKey = archiveIdentityPath;
8051
+ if (processedArchives.has(processedKey)) {
8052
+ return undefined;
8053
+ }
8054
+ processedArchives.add(processedKey);
8055
+ const archiveAnalysis = await parseAsarArchive(archivePath, {
8056
+ ...options,
8057
+ asarVirtualPath: archiveIdentityPath,
8058
+ });
8059
+ aggregateArchiveResults(archiveAnalysis, isPrimaryArchive);
8060
+ const shouldExtract =
8061
+ archiveAnalysis.packageManifestPaths?.length ||
8062
+ (depth < maxNestedAsarDepth &&
8063
+ archiveAnalysis.summary?.nestedArchiveCount > 0);
8064
+ if (!shouldExtract) {
8065
+ return archiveAnalysis.parentComponent?.["bom-ref"];
8066
+ }
8067
+ const extractedDir = await extractAsarToTempDir(archivePath);
8068
+ if (!extractedDir) {
8069
+ return archiveAnalysis.parentComponent?.["bom-ref"];
8070
+ }
8071
+ tempDirs.push(extractedDir);
8072
+ await analyzeExtractedArchive(
8073
+ extractedDir,
8074
+ archiveAnalysis,
8075
+ archiveIdentityPath,
8076
+ );
8077
+ if (depth < maxNestedAsarDepth) {
8078
+ const nestedAsarFiles = getAllFiles(extractedDir, "**/*.asar", options);
8079
+ for (const nestedArchivePath of nestedAsarFiles) {
8080
+ const relativeNestedArchivePath = relative(
8081
+ extractedDir,
8082
+ nestedArchivePath,
8083
+ ).replaceAll("\\", "/");
8084
+ if (
8085
+ !relativeNestedArchivePath ||
8086
+ relativeNestedArchivePath.startsWith("..")
8087
+ ) {
8088
+ continue;
8089
+ }
8090
+ const nestedArchiveIdentityPath = `${archiveIdentityPath}#/${relativeNestedArchivePath}`;
8091
+ const nestedParentRef = await processAsarArchive(
8092
+ nestedArchivePath,
8093
+ nestedArchiveIdentityPath,
8094
+ false,
8095
+ depth + 1,
8096
+ );
8097
+ if (
8098
+ archiveAnalysis.parentComponent?.["bom-ref"] &&
8099
+ nestedParentRef &&
8100
+ archiveAnalysis.parentComponent["bom-ref"] !== nestedParentRef
8101
+ ) {
8102
+ dependencies = mergeDependencies(dependencies, [
8103
+ {
8104
+ ref: archiveAnalysis.parentComponent["bom-ref"],
8105
+ dependsOn: [nestedParentRef],
8106
+ },
8107
+ ]);
8108
+ }
8109
+ }
8110
+ }
8111
+ return archiveAnalysis.parentComponent?.["bom-ref"];
8112
+ };
8113
+ try {
8114
+ for (const archivePath of asarFiles) {
8115
+ const isPrimaryArchive =
8116
+ explicitAsarPath && resolve(archivePath) === explicitAsarPath;
8117
+ await processAsarArchive(
8118
+ archivePath,
8119
+ resolve(archivePath),
8120
+ isPrimaryArchive,
8121
+ 0,
8122
+ );
8123
+ }
8124
+ } finally {
8125
+ for (const tempDir of tempDirs) {
8126
+ cleanupAsarTempDir(tempDir);
8127
+ }
8128
+ }
8129
+ pkgList = trimComponents(pkgList);
8130
+ return buildBomNSData(options, pkgList, "asar", {
8131
+ src: path,
8132
+ filename: asarFiles.join(", "),
8133
+ nsMapping: {},
8134
+ dependencies,
8135
+ parentComponent,
8136
+ });
8137
+ }
8138
+
7785
8139
  /**
7786
8140
  * Function to create BOM for installed Chrome and Chromium-based browser extensions.
7787
8141
  *
@@ -7793,7 +8147,8 @@ export async function createChromeExtensionBom(path, options) {
7793
8147
  let dependencies = [];
7794
8148
  let sourcePaths = [];
7795
8149
  const directResult = collectChromeExtensionsFromPath(path);
7796
- const chromeDirs = discoverChromiumExtensionDirs();
8150
+ const shouldDiscoverInstalledChromeExtensions =
8151
+ hasExplicitProjectTypeSelection(options, "chrome-extension");
7797
8152
  let pkgList = directResult.components || [];
7798
8153
  if (directResult.extensionDirs?.length) {
7799
8154
  sourcePaths = directResult.extensionDirs.slice();
@@ -7812,20 +8167,23 @@ export async function createChromeExtensionBom(path, options) {
7812
8167
  `Found ${pkgList.length} component(s) from direct Chrome extension path scan`,
7813
8168
  );
7814
8169
  }
7815
- if (chromeDirs.length) {
7816
- if (DEBUG_MODE) {
7817
- thoughtLog(
7818
- `Discovered Chromium extension directories: ${chromeDirs.map((d) => `${d.browser} (${d.channel}): ${d.dir}`).join(", ")}`,
7819
- );
7820
- }
7821
- if (!pkgList.length) {
7822
- pkgList = collectInstalledChromeExtensions(chromeDirs);
7823
- sourcePaths = chromeDirs.map((d) => d.dir);
7824
- }
7825
- if (DEBUG_MODE && pkgList.length && !directResult.components?.length) {
7826
- thoughtLog(
7827
- `Found ${pkgList.length} Chrome/Chromium extension(s) from ${chromeDirs.length} browser location(s)`,
7828
- );
8170
+ if (shouldDiscoverInstalledChromeExtensions) {
8171
+ const chromeDirs = discoverChromiumExtensionDirs();
8172
+ if (chromeDirs.length) {
8173
+ if (DEBUG_MODE) {
8174
+ thoughtLog(
8175
+ `Discovered Chromium extension directories: ${chromeDirs.map((d) => `${d.browser} (${d.channel}): ${d.dir}`).join(", ")}`,
8176
+ );
8177
+ }
8178
+ if (!pkgList.length) {
8179
+ pkgList = collectInstalledChromeExtensions(chromeDirs);
8180
+ sourcePaths = chromeDirs.map((d) => d.dir);
8181
+ }
8182
+ if (DEBUG_MODE && pkgList.length && !directResult.components?.length) {
8183
+ thoughtLog(
8184
+ `Found ${pkgList.length} Chrome/Chromium extension(s) from ${chromeDirs.length} browser location(s)`,
8185
+ );
8186
+ }
7829
8187
  }
7830
8188
  }
7831
8189
  pkgList = trimComponents(pkgList);
@@ -7945,6 +8303,26 @@ export async function createCryptoCertsBom(path, options) {
7945
8303
  for (const f of certFiles) {
7946
8304
  const name = basename(f);
7947
8305
  const fileHash = await checksumFile("sha256", f);
8306
+ let evidence;
8307
+ if (options.evidence) {
8308
+ const identityEvidence = {
8309
+ field: "name",
8310
+ confidence: 1,
8311
+ concludedValue: name,
8312
+ methods: [
8313
+ {
8314
+ technique: "filename",
8315
+ confidence: 1,
8316
+ value: f,
8317
+ },
8318
+ ],
8319
+ };
8320
+ evidence = {
8321
+ identity:
8322
+ options.specVersion >= 1.6 ? [identityEvidence] : identityEvidence,
8323
+ occurrences: [{ location: f }],
8324
+ };
8325
+ }
7948
8326
  const apkg = {
7949
8327
  name,
7950
8328
  type: "cryptographic-asset",
@@ -7957,10 +8335,18 @@ export async function createCryptoCertsBom(path, options) {
7957
8335
  implementationPlatform: "unknown",
7958
8336
  },
7959
8337
  },
8338
+ ...(evidence ? { evidence } : {}),
7960
8339
  properties: [{ name: "SrcFile", value: f }],
7961
8340
  };
7962
8341
  pkgList.push(apkg);
7963
8342
  }
8343
+ const sourceCryptoComponents = await collectSourceCryptoComponents(
8344
+ path,
8345
+ options,
8346
+ );
8347
+ if (sourceCryptoComponents.length) {
8348
+ pkgList.push(...sourceCryptoComponents);
8349
+ }
7964
8350
  return {
7965
8351
  bomJson: {
7966
8352
  components: pkgList,
@@ -8051,6 +8437,7 @@ export async function createMultiXBom(pathList, options) {
8051
8437
  ) {
8052
8438
  const {
8053
8439
  osPackages,
8440
+ osPackageFiles,
8054
8441
  dependenciesList,
8055
8442
  allTypes,
8056
8443
  bundledSdks,
@@ -8058,6 +8445,8 @@ export async function createMultiXBom(pathList, options) {
8058
8445
  binPaths,
8059
8446
  executables,
8060
8447
  sharedLibs,
8448
+ services,
8449
+ tools,
8061
8450
  } = await getOSPackages(
8062
8451
  options.allLayersExplodedDir,
8063
8452
  options.exportData?.inspectData?.Config,
@@ -8067,14 +8456,16 @@ export async function createMultiXBom(pathList, options) {
8067
8456
  options.bundledSdks = bundledSdks;
8068
8457
  options.bundledRuntimes = bundledRuntimes;
8069
8458
  options.binPaths = binPaths;
8459
+ options.unpackagedExecutableCount = executables?.length || 0;
8460
+ options.unpackagedSharedLibraryCount = sharedLibs?.length || 0;
8070
8461
  if (DEBUG_MODE) {
8071
8462
  console.log(
8072
- `**OS**: Found ${osPackages.length} OS packages, ${executables?.length} executables, and ${sharedLibs.length} shared libraries at ${options.allLayersExplodedDir}`,
8463
+ `**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}`,
8073
8464
  );
8074
8465
  }
8075
8466
  if (osPackages.length) {
8076
8467
  thoughtLog(
8077
- `I found ${osPackages.length} OS packages and ${executables?.length} executables at ${options.allLayersExplodedDir}`,
8468
+ `I found ${osPackages.length} OS packages, ${osPackageFiles?.length || 0} package-owned files, and ${services?.length || 0} packaged services at ${options.allLayersExplodedDir}`,
8078
8469
  );
8079
8470
  } else if (executables?.length || sharedLibs?.length) {
8080
8471
  thoughtLog(
@@ -8088,15 +8479,28 @@ export async function createMultiXBom(pathList, options) {
8088
8479
  if (allTypes?.length) {
8089
8480
  options.allOSComponentTypes = allTypes;
8090
8481
  }
8482
+ if (tools?.length) {
8483
+ options.tools = (
8484
+ Array.isArray(options.tools)
8485
+ ? options.tools
8486
+ : options.tools?.components || []
8487
+ ).concat(tools);
8488
+ }
8091
8489
  components = components.concat(osPackages);
8490
+ components = components.concat(osPackageFiles || []);
8092
8491
  components = components.concat(executables);
8093
8492
  components = components.concat(sharedLibs);
8094
8493
  if (dependenciesList?.length) {
8095
8494
  dependencies = mergeDependencies(dependencies, dependenciesList);
8096
8495
  }
8496
+ if (services?.length) {
8497
+ options.services = mergeServices(options.services || [], services);
8498
+ }
8097
8499
  if (parentComponent && Object.keys(parentComponent).length) {
8098
8500
  // Make the parent oci image depend on all os components
8099
- const parentDependsOn = new Set(osPackages.map((p) => p["bom-ref"]));
8501
+ const parentDependsOn = new Set(
8502
+ osPackages.concat(osPackageFiles || []).map((p) => p["bom-ref"]),
8503
+ );
8100
8504
  dependencies.splice(0, 0, {
8101
8505
  ref: parentComponent["bom-ref"],
8102
8506
  dependsOn: Array.from(parentDependsOn).sort(),
@@ -8798,6 +9202,28 @@ export async function createMultiXBom(pathList, options) {
8798
9202
  }
8799
9203
  }
8800
9204
  }
9205
+ if (hasAnyProjectType(["asar"], options)) {
9206
+ setProjectTypeActivityContext("asar", path);
9207
+ bomData = await createAsarBom(path, options);
9208
+ if (bomData?.bomJson?.components?.length) {
9209
+ if (DEBUG_MODE) {
9210
+ console.log(
9211
+ `Found ${bomData.bomJson.components.length} ASAR component(s) at ${path}`,
9212
+ );
9213
+ }
9214
+ components = components.concat(bomData.bomJson.components);
9215
+ dependencies = mergeDependencies(
9216
+ dependencies,
9217
+ bomData.bomJson.dependencies,
9218
+ );
9219
+ if (
9220
+ bomData.parentComponent &&
9221
+ Object.keys(bomData.parentComponent).length
9222
+ ) {
9223
+ parentSubComponents.push(bomData.parentComponent);
9224
+ }
9225
+ }
9226
+ }
8801
9227
  if (hasAnyProjectType(["vscode-extension"], options)) {
8802
9228
  setProjectTypeActivityContext("vscode-extension", path);
8803
9229
  bomData = await createVscodeExtensionBom(path, options);
@@ -9162,6 +9588,11 @@ export async function createXBom(path, options) {
9162
9588
  `${options.multiProject ? "**/" : ""}conan.lock`,
9163
9589
  options,
9164
9590
  );
9591
+ const colliderLockFiles = getAllFiles(
9592
+ path,
9593
+ `${options.multiProject ? "**/" : ""}collider.lock`,
9594
+ options,
9595
+ );
9165
9596
  const conanFiles = getAllFiles(
9166
9597
  path,
9167
9598
  `${options.multiProject ? "**/" : ""}conanfile.txt`,
@@ -9178,6 +9609,7 @@ export async function createXBom(path, options) {
9178
9609
  options,
9179
9610
  );
9180
9611
  if (
9612
+ colliderLockFiles.length ||
9181
9613
  conanLockFiles.length ||
9182
9614
  conanFiles.length ||
9183
9615
  cmakeListFiles.length ||
@@ -9231,6 +9663,16 @@ export async function createXBom(path, options) {
9231
9663
  return await createVscodeExtensionBom(path, options);
9232
9664
  }
9233
9665
 
9666
+ // Electron ASAR archives
9667
+ const asarFiles = getAllFiles(
9668
+ path,
9669
+ `${options.multiProject ? "**/" : ""}*.asar`,
9670
+ options,
9671
+ );
9672
+ if (asarFiles.length) {
9673
+ return await createAsarBom(path, options);
9674
+ }
9675
+
9234
9676
  // Helm charts
9235
9677
  const chartFiles = getAllFiles(
9236
9678
  path,
@@ -9333,6 +9775,35 @@ export async function createXBom(path, options) {
9333
9775
  }
9334
9776
  }
9335
9777
 
9778
+ /**
9779
+ * Function to create a hardware BOM for the current host.
9780
+ *
9781
+ * @param {string} _path Source path (unused for live host HBOM generation)
9782
+ * @param {Object} options Parse options from the cli
9783
+ * @returns {Promise<Object>} Promise resolving to BOM object
9784
+ */
9785
+ export async function createHBom(_path, options) {
9786
+ ensureHbomRuntimeSupport(options, options.commandName || "hbom");
9787
+ let bomJson = await createHbomDocument(options);
9788
+ if (options.includeRuntime) {
9789
+ const runtimeOptions = {
9790
+ ...options,
9791
+ includeRuntime: false,
9792
+ multiProject: false,
9793
+ projectType: ["os"],
9794
+ };
9795
+ const obomData = await createOSBom(_path, runtimeOptions);
9796
+ bomJson = mergeHostInventoryBoms(bomJson, obomData);
9797
+ } else {
9798
+ bomJson = mergeHostInventoryBoms(bomJson);
9799
+ }
9800
+ return {
9801
+ bomJson,
9802
+ dependencies: bomJson.dependencies || [],
9803
+ parentComponent: bomJson.metadata?.component,
9804
+ };
9805
+ }
9806
+
9336
9807
  /**
9337
9808
  * Function to create bom string for various languages
9338
9809
  *
@@ -9345,8 +9816,27 @@ export async function createBom(path, options) {
9345
9816
  if (!projectType) {
9346
9817
  projectType = [];
9347
9818
  }
9819
+ ensureNoMixedHbomProjectTypes(projectType);
9820
+ if (hasHbomProjectType(projectType)) {
9821
+ const selectedHbomProjectType = Array.isArray(projectType)
9822
+ ? projectType[0]
9823
+ : `${projectType}`.split(",")[0];
9824
+ options.projectType = [selectedHbomProjectType];
9825
+ setActivityContext({
9826
+ projectType: selectedHbomProjectType,
9827
+ sourcePath: path,
9828
+ });
9829
+ thoughtLog(
9830
+ "The user wants a Hardware Bill-of-Materials (HBOM) for the current host. Let's use the dedicated hardware collector.",
9831
+ );
9832
+ return await createHBom(path, options);
9833
+ }
9348
9834
  let exportData;
9349
9835
  let isContainerMode = false;
9836
+ const isLocalDirectoryInput =
9837
+ !options.projectType?.includes("universal") &&
9838
+ safeExistsSync(path) &&
9839
+ lstatSync(path).isDirectory();
9350
9840
  // Docker and image archive support
9351
9841
  // TODO: Support any source archive
9352
9842
  if (path.endsWith(".tar") || path.endsWith(".tar.gz")) {
@@ -9359,6 +9849,22 @@ export async function createBom(path, options) {
9359
9849
  return {};
9360
9850
  }
9361
9851
  isContainerMode = true;
9852
+ } else if (
9853
+ isLocalDirectoryInput &&
9854
+ (hasAnyProjectType(["oci-dir"], options, false) ||
9855
+ hasAnyProjectType(["oci"], options, false))
9856
+ ) {
9857
+ isContainerMode = true;
9858
+ exportData = {
9859
+ inspectData: undefined,
9860
+ lastWorkingDir: "",
9861
+ allLayersDir: path,
9862
+ allLayersExplodedDir: path,
9863
+ };
9864
+ if (safeExistsSync(join(path, "all-layers"))) {
9865
+ exportData.allLayersExplodedDir = join(path, "all-layers");
9866
+ }
9867
+ exportData.pkgPathList = getPkgPathList(exportData, undefined);
9362
9868
  } else if (
9363
9869
  (options.projectType &&
9364
9870
  !options.projectType?.includes("universal") &&
@@ -9386,21 +9892,6 @@ export async function createBom(path, options) {
9386
9892
  console.log(path, "doesn't appear to be a valid container image.");
9387
9893
  }
9388
9894
  }
9389
- } else if (
9390
- !options.projectType?.includes("universal") &&
9391
- hasAnyProjectType(["oci-dir"], options, false)
9392
- ) {
9393
- isContainerMode = true;
9394
- exportData = {
9395
- inspectData: undefined,
9396
- lastWorkingDir: "",
9397
- allLayersDir: path,
9398
- allLayersExplodedDir: path,
9399
- };
9400
- if (safeExistsSync(join(path, "all-layers"))) {
9401
- exportData.allLayersDir = join(path, "all-layers");
9402
- }
9403
- exportData.pkgPathList = getPkgPathList(exportData, undefined);
9404
9895
  }
9405
9896
  if (isContainerMode) {
9406
9897
  options.multiProject = true;
@@ -9474,6 +9965,9 @@ export async function createBom(path, options) {
9474
9965
  if (path.endsWith(".war")) {
9475
9966
  projectType = ["java"];
9476
9967
  }
9968
+ if (!projectType.length && path.endsWith(".asar")) {
9969
+ projectType = ["asar"];
9970
+ }
9477
9971
  if (projectType.length > 1) {
9478
9972
  setActivityContext({
9479
9973
  projectType: projectType.join(","),
@@ -9597,6 +10091,9 @@ export async function createBom(path, options) {
9597
10091
  if (PROJECT_TYPE_ALIASES["caxa"].includes(projectType[0])) {
9598
10092
  return await createCaxaBom(path, options);
9599
10093
  }
10094
+ if (PROJECT_TYPE_ALIASES["asar"].includes(projectType[0])) {
10095
+ return await createAsarBom(path, options);
10096
+ }
9600
10097
  if (PROJECT_TYPE_ALIASES["vscode-extension"].includes(projectType[0])) {
9601
10098
  return await createVscodeExtensionBom(path, options);
9602
10099
  }
@@ -9641,17 +10138,33 @@ export async function createBom(path, options) {
9641
10138
  * @throws {Error} if the request fails
9642
10139
  */
9643
10140
  export async function submitBom(args, bomContents) {
10141
+ const dependencyTrackApiUrl = getDependencyTrackBomApiUrl(args.serverUrl);
10142
+ const serverUrl = dependencyTrackApiUrl?.toString();
10143
+ if (!dependencyTrackApiUrl || !serverUrl) {
10144
+ console.log(
10145
+ "Invalid Dependency-Track server URL. Provide an absolute http(s) URL without dangerous characters.",
10146
+ );
10147
+ args.failOnError && process.exit(1);
10148
+ return undefined;
10149
+ }
10150
+ const serverHost = dependencyTrackApiUrl.hostname;
10151
+ if (!isAllowedHttpHost(serverHost)) {
10152
+ console.log(
10153
+ `Dependency-Track server host '${serverHost}' is not allowed by CDXGEN_ALLOWED_HOSTS.`,
10154
+ );
10155
+ args.failOnError && process.exit(1);
10156
+ return undefined;
10157
+ }
9644
10158
  if (isDryRun) {
9645
10159
  recordActivity({
9646
10160
  kind: "network",
9647
10161
  reason:
9648
10162
  "Dry run mode blocks Dependency-Track submission and reports the request instead.",
9649
10163
  status: "blocked",
9650
- target: getDependencyTrackBomUrl(args.serverUrl),
10164
+ target: serverUrl,
9651
10165
  });
9652
10166
  return undefined;
9653
10167
  }
9654
- const serverUrl = getDependencyTrackBomUrl(args.serverUrl);
9655
10168
  const bomPayload = buildDependencyTrackBomPayload(args, bomContents);
9656
10169
  if (!bomPayload) {
9657
10170
  console.log(
@@ -9670,27 +10183,31 @@ export async function submitBom(args, bomContents) {
9670
10183
  );
9671
10184
  }
9672
10185
  try {
9673
- if (DEBUG_MODE && args.skipDtTlsCheck) {
9674
- console.log(
9675
- "Calling ",
9676
- serverUrl,
9677
- "with --skip-dt-tls-check argument: Skip DT TLS check.",
9678
- );
9679
- }
9680
- // See issue #1963 regarding CRLF hardening
9681
- return await got(serverUrl, {
10186
+ const requestOptions = {
9682
10187
  method: "PUT",
10188
+ followRedirect: false,
9683
10189
  https: {
9684
10190
  rejectUnauthorized: !args.skipDtTlsCheck,
9685
10191
  },
9686
10192
  headers: {
9687
10193
  "X-Api-Key": (args.apiKey || "").replace(/[\r\n]/g, ""),
9688
10194
  "Content-Type": "application/json",
9689
- "user-agent": `@CycloneDX/cdxgen ${CDXGEN_VERSION}`,
9690
10195
  },
9691
10196
  json: bomPayload,
9692
10197
  responseType: "json",
9693
- }).json();
10198
+ context: {
10199
+ activityIntent: "bom-submit",
10200
+ },
10201
+ };
10202
+ if (DEBUG_MODE && args.skipDtTlsCheck) {
10203
+ console.log(
10204
+ "Calling ",
10205
+ serverUrl,
10206
+ "with --skip-dt-tls-check argument: Skip DT TLS check.",
10207
+ );
10208
+ }
10209
+ // See issue #1963 regarding CRLF hardening
10210
+ return await cdxgenAgent(dependencyTrackApiUrl, requestOptions).json();
9694
10211
  } catch (error) {
9695
10212
  if (error.response && error.response.statusCode === 401) {
9696
10213
  // Unauthorized
@@ -9703,18 +10220,21 @@ export async function submitBom(args, bomContents) {
9703
10220
  );
9704
10221
  // Method not allowed errors
9705
10222
  try {
9706
- return await got(serverUrl, {
10223
+ return await cdxgenAgent(dependencyTrackApiUrl, {
9707
10224
  method: "POST",
10225
+ followRedirect: false,
9708
10226
  https: {
9709
10227
  rejectUnauthorized: !args.skipDtTlsCheck,
9710
10228
  },
9711
10229
  headers: {
9712
- "X-Api-Key": args.apiKey,
10230
+ "X-Api-Key": (args.apiKey || "").replace(/[\r\n]/g, ""),
9713
10231
  "Content-Type": "application/json",
9714
- "user-agent": `@CycloneDX/cdxgen ${CDXGEN_VERSION}`,
9715
10232
  },
9716
10233
  json: bomPayload,
9717
10234
  responseType: "json",
10235
+ context: {
10236
+ activityIntent: "bom-submit",
10237
+ },
9718
10238
  }).json();
9719
10239
  } catch (error) {
9720
10240
  if (DEBUG_MODE) {