@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
@@ -0,0 +1,710 @@
1
+ import { basename } from "node:path";
2
+
3
+ import { getHbomCommandDiagnosticSummary } from "./hbomAnalysis.js";
4
+ import { importHbomModule } from "./hbomLoader.js";
5
+ import { resolveCdxgenPlugins, resolvePluginBinary } from "./plugins.js";
6
+ import { isAllowedPath } from "./source.js";
7
+ import {
8
+ isDryRun,
9
+ isSecureMode,
10
+ readEnvironmentVariable,
11
+ recordActivity,
12
+ } from "./utils.js";
13
+
14
+ const HBOM_PROJECT_TYPE_SET = new Set(["hardware", "hbom"]);
15
+ const HBOM_TRACE_PATH_ACTIVITY_KINDS = new Set([
16
+ "dir-read",
17
+ "file-read",
18
+ "mkdir",
19
+ "symlink-read",
20
+ ]);
21
+
22
+ function parseAllowlistEntries(allowlistValue) {
23
+ if (typeof allowlistValue !== "string") {
24
+ return [];
25
+ }
26
+ return allowlistValue
27
+ .split(",")
28
+ .map((entry) => entry.trim())
29
+ .filter(Boolean);
30
+ }
31
+
32
+ function getConfiguredHbomCommandAllowlist() {
33
+ return readEnvironmentVariable("CDXGEN_ALLOWED_COMMANDS");
34
+ }
35
+
36
+ function getConfiguredHbomPathAllowlist() {
37
+ return (
38
+ readEnvironmentVariable("CDXGEN_ALLOWED_PATHS") ||
39
+ readEnvironmentVariable("CDXGEN_SERVER_ALLOWED_PATHS")
40
+ );
41
+ }
42
+
43
+ function isAllowedHbomCommand(command, allowlistValue = undefined) {
44
+ const normalizedCommand = `${command ?? ""}`.trim();
45
+ if (!normalizedCommand) {
46
+ return true;
47
+ }
48
+ const effectiveAllowlist =
49
+ allowlistValue ?? getConfiguredHbomCommandAllowlist();
50
+ const allowlistEntries = parseAllowlistEntries(effectiveAllowlist);
51
+ if (!allowlistEntries.length) {
52
+ return true;
53
+ }
54
+ const commandName = basename(normalizedCommand);
55
+ return allowlistEntries.some(
56
+ (entry) => entry === normalizedCommand || entry === commandName,
57
+ );
58
+ }
59
+
60
+ function formatHbomCommandTarget(command, args = []) {
61
+ return `${command}${args.length ? ` ${args.join(" ")}` : ""}`;
62
+ }
63
+
64
+ function buildHbomPlanActivities(planEntry, normalizedOptions) {
65
+ const baseArgs = [...(planEntry.args || [])];
66
+ const baseTarget = formatHbomCommandTarget(planEntry.command, baseArgs);
67
+ const activities = [
68
+ {
69
+ args: baseArgs,
70
+ command: planEntry.command,
71
+ id: planEntry.id,
72
+ kind: "command",
73
+ target: baseTarget,
74
+ },
75
+ ];
76
+ if (
77
+ normalizedOptions.includePrivilegedEnrichment === true &&
78
+ planEntry.sudoRetryOnPermissionDenied === true
79
+ ) {
80
+ activities.push({
81
+ args: ["-n", planEntry.command, ...baseArgs],
82
+ command: "sudo",
83
+ id: `${planEntry.id}:sudo-retry`,
84
+ kind: "command-retry",
85
+ target: formatHbomCommandTarget("sudo", [
86
+ "-n",
87
+ planEntry.command,
88
+ ...baseArgs,
89
+ ]),
90
+ });
91
+ }
92
+ return activities;
93
+ }
94
+
95
+ function collectDisallowedHbomCommands(
96
+ activities = [],
97
+ allowlistValue = undefined,
98
+ ) {
99
+ const disallowedCommands = new Map();
100
+ for (const activity of activities) {
101
+ if (!new Set(["command", "command-retry"]).has(activity?.kind)) {
102
+ continue;
103
+ }
104
+ const requestedCommand = `${
105
+ activity.retryCommand ??
106
+ activity.command ??
107
+ activity.requestedCommand ??
108
+ ""
109
+ }`.trim();
110
+ if (
111
+ !requestedCommand ||
112
+ isAllowedHbomCommand(requestedCommand, allowlistValue)
113
+ ) {
114
+ continue;
115
+ }
116
+ const existingEntry = disallowedCommands.get(requestedCommand) ?? {
117
+ command: requestedCommand,
118
+ commandName: basename(requestedCommand),
119
+ ids: new Set(),
120
+ targets: new Set(),
121
+ };
122
+ if (activity.id) {
123
+ existingEntry.ids.add(`${activity.id}`);
124
+ }
125
+ const formattedTarget = `${
126
+ activity.target ||
127
+ formatHbomCommandTarget(requestedCommand, activity.args)
128
+ }`.trim();
129
+ if (formattedTarget) {
130
+ existingEntry.targets.add(formattedTarget);
131
+ }
132
+ disallowedCommands.set(requestedCommand, existingEntry);
133
+ }
134
+ return [...disallowedCommands.values()].sort((leftEntry, rightEntry) =>
135
+ leftEntry.command.localeCompare(rightEntry.command),
136
+ );
137
+ }
138
+
139
+ function collectDisallowedHbomPaths(activities = []) {
140
+ const disallowedPaths = new Map();
141
+ for (const activity of activities) {
142
+ if (!HBOM_TRACE_PATH_ACTIVITY_KINDS.has(activity?.kind)) {
143
+ continue;
144
+ }
145
+ const declaredPath = `${activity.path ?? activity.target ?? ""}`.trim();
146
+ if (!declaredPath || isAllowedPath(declaredPath)) {
147
+ continue;
148
+ }
149
+ const existingEntry = disallowedPaths.get(declaredPath) ?? {
150
+ activityKinds: new Set(),
151
+ ids: new Set(),
152
+ path: declaredPath,
153
+ };
154
+ existingEntry.activityKinds.add(`${activity.kind}`);
155
+ if (activity.id) {
156
+ existingEntry.ids.add(`${activity.id}`);
157
+ }
158
+ disallowedPaths.set(declaredPath, existingEntry);
159
+ }
160
+ return [...disallowedPaths.values()].sort((leftEntry, rightEntry) =>
161
+ leftEntry.path.localeCompare(rightEntry.path),
162
+ );
163
+ }
164
+
165
+ function createHbomAllowlistPreflightError(
166
+ disallowedCommands,
167
+ disallowedPaths,
168
+ ) {
169
+ const messageLines = [
170
+ "HBOM secure-mode preflight blocked live collection because the dry-run plan includes resources outside the configured allowlists.",
171
+ ];
172
+
173
+ if (disallowedCommands.length) {
174
+ messageLines.push("", "Commands not allowed by CDXGEN_ALLOWED_COMMANDS:");
175
+ for (const commandEntry of disallowedCommands) {
176
+ const commandSuffix =
177
+ commandEntry.commandName !== commandEntry.command
178
+ ? ` (basename: ${commandEntry.commandName})`
179
+ : "";
180
+ const detailParts = [];
181
+ if (commandEntry.ids.size) {
182
+ detailParts.push(`ids=${[...commandEntry.ids].join(",")}`);
183
+ }
184
+ if (commandEntry.targets.size) {
185
+ detailParts.push(`targets=${[...commandEntry.targets].join(" | ")}`);
186
+ }
187
+ messageLines.push(
188
+ `- ${commandEntry.command}${commandSuffix}${detailParts.length ? ` — ${detailParts.join("; ")}` : ""}`,
189
+ );
190
+ }
191
+ }
192
+
193
+ if (disallowedPaths.length) {
194
+ messageLines.push("", "Paths not allowed by CDXGEN_ALLOWED_PATHS:");
195
+ for (const pathEntry of disallowedPaths) {
196
+ const detailParts = [];
197
+ if (pathEntry.activityKinds.size) {
198
+ detailParts.push(`kinds=${[...pathEntry.activityKinds].join(",")}`);
199
+ }
200
+ if (pathEntry.ids.size) {
201
+ detailParts.push(`ids=${[...pathEntry.ids].join(",")}`);
202
+ }
203
+ messageLines.push(
204
+ `- ${pathEntry.path}${detailParts.length ? ` — ${detailParts.join("; ")}` : ""}`,
205
+ );
206
+ }
207
+ }
208
+
209
+ messageLines.push(
210
+ "",
211
+ "Review 'hbom --dry-run' (or 'cdxgen --dry-run -t hbom') to inspect the planned commands and declared paths, then expand CDXGEN_ALLOWED_COMMANDS and CDXGEN_ALLOWED_PATHS before retrying secure mode.",
212
+ );
213
+
214
+ return new Error(messageLines.join("\n"));
215
+ }
216
+
217
+ async function enforceSecureModeHbomAllowlists(hbomModule, normalizedOptions) {
218
+ if (isDryRun || normalizedOptions?.isDryRun || !isSecureMode) {
219
+ return;
220
+ }
221
+ const commandAllowlistValue = getConfiguredHbomCommandAllowlist();
222
+ const pathAllowlistValue = getConfiguredHbomPathAllowlist();
223
+ const hasCommandAllowlist =
224
+ parseAllowlistEntries(commandAllowlistValue).length > 0;
225
+ const hasPathAllowlist = parseAllowlistEntries(pathAllowlistValue).length > 0;
226
+ if (!hasCommandAllowlist && !hasPathAllowlist) {
227
+ return;
228
+ }
229
+
230
+ let traceActivities = [];
231
+ if (hasPathAllowlist || typeof hbomModule.getCommandPlan !== "function") {
232
+ if (typeof hbomModule.createCollectorTrace !== "function") {
233
+ throw new Error(
234
+ "HBOM secure mode requires a cdx-hbom build with dry-run trace support to enforce the configured allowlists. Upgrade '@cdxgen/cdx-hbom' and retry.",
235
+ );
236
+ }
237
+ const preflightTrace = hbomModule.createCollectorTrace();
238
+ const preflightBom = await hbomModule.collectHardware({
239
+ ...normalizedOptions,
240
+ dryRun: true,
241
+ trace: preflightTrace,
242
+ });
243
+ traceActivities = Array.isArray(
244
+ hbomModule.getCollectorTrace?.(preflightBom)?.activities,
245
+ )
246
+ ? hbomModule.getCollectorTrace(preflightBom).activities
247
+ : Array.isArray(preflightTrace?.activities)
248
+ ? preflightTrace.activities
249
+ : [];
250
+ } else if (
251
+ hasCommandAllowlist &&
252
+ normalizedOptions.includeCommandEnrichment !== false
253
+ ) {
254
+ traceActivities = hbomModule
255
+ .getCommandPlan(normalizedOptions)
256
+ .flatMap((planEntry) =>
257
+ buildHbomPlanActivities(planEntry, normalizedOptions),
258
+ );
259
+ }
260
+
261
+ const disallowedCommands =
262
+ hasCommandAllowlist && normalizedOptions.includeCommandEnrichment !== false
263
+ ? collectDisallowedHbomCommands(traceActivities, commandAllowlistValue)
264
+ : [];
265
+ const disallowedPaths = hasPathAllowlist
266
+ ? collectDisallowedHbomPaths(traceActivities)
267
+ : [];
268
+
269
+ if (!disallowedCommands.length && !disallowedPaths.length) {
270
+ return;
271
+ }
272
+
273
+ for (const commandEntry of disallowedCommands) {
274
+ recordActivity({
275
+ kind: "policy",
276
+ policyType: "hbom-command-allowlist",
277
+ reason:
278
+ "HBOM secure-mode preflight blocked a planned command outside CDXGEN_ALLOWED_COMMANDS.",
279
+ status: "blocked",
280
+ target: commandEntry.command,
281
+ });
282
+ }
283
+ for (const pathEntry of disallowedPaths) {
284
+ recordActivity({
285
+ kind: "policy",
286
+ policyType: "hbom-path-allowlist",
287
+ reason:
288
+ "HBOM secure-mode preflight blocked a declared path outside CDXGEN_ALLOWED_PATHS.",
289
+ status: "blocked",
290
+ target: pathEntry.path,
291
+ });
292
+ }
293
+
294
+ throw createHbomAllowlistPreflightError(disallowedCommands, disallowedPaths);
295
+ }
296
+
297
+ /**
298
+ * Determine whether the supplied project types include HBOM.
299
+ *
300
+ * @param {string|string[]|undefined|null} projectTypes Project types.
301
+ * @returns {boolean} True when HBOM is requested.
302
+ */
303
+ export function hasHbomProjectType(projectTypes) {
304
+ return normalizeProjectTypes(projectTypes).some((projectType) =>
305
+ HBOM_PROJECT_TYPE_SET.has(projectType),
306
+ );
307
+ }
308
+
309
+ /**
310
+ * Determine whether the supplied project types are exclusively HBOM-oriented.
311
+ *
312
+ * @param {string|string[]|undefined|null} projectTypes Project types.
313
+ * @returns {boolean} True when at least one project type is supplied and all are HBOM-oriented.
314
+ */
315
+ export function isHbomOnlyProjectTypes(projectTypes) {
316
+ const normalizedProjectTypes = normalizeProjectTypes(projectTypes);
317
+ return (
318
+ normalizedProjectTypes.length > 0 &&
319
+ normalizedProjectTypes.every((projectType) =>
320
+ HBOM_PROJECT_TYPE_SET.has(projectType),
321
+ )
322
+ );
323
+ }
324
+
325
+ /**
326
+ * Reject mixed HBOM and non-HBOM project types.
327
+ *
328
+ * @param {string|string[]|undefined|null} projectTypes Project types.
329
+ */
330
+ export function ensureNoMixedHbomProjectTypes(projectTypes) {
331
+ const normalizedProjectTypes = normalizeProjectTypes(projectTypes);
332
+ if (
333
+ !normalizedProjectTypes.length ||
334
+ !hasHbomProjectType(normalizedProjectTypes)
335
+ ) {
336
+ return;
337
+ }
338
+ const nonHbomProjectTypes = normalizedProjectTypes.filter(
339
+ (projectType) => !HBOM_PROJECT_TYPE_SET.has(projectType),
340
+ );
341
+ if (nonHbomProjectTypes.length) {
342
+ throw new Error(
343
+ `HBOM project types cannot be mixed with other project types: ${normalizedProjectTypes.join(", ")}. Generate HBOM separately using 'hbom' or 'cdxgen -t hbom'.`,
344
+ );
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Ensure HBOM generation uses the supported CycloneDX version.
350
+ *
351
+ * @param {number|string|undefined|null} specVersion Requested spec version.
352
+ */
353
+ export function ensureSupportedHbomSpecVersion(specVersion) {
354
+ if (specVersion === undefined || specVersion === null || specVersion === "") {
355
+ return;
356
+ }
357
+ if (Number(specVersion) !== 1.7) {
358
+ throw new Error("HBOM generation currently supports only CycloneDX 1.7.");
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Ensure merged HBOM + runtime collection has access to osquery.
364
+ *
365
+ * @param {object} [options={}] CLI options.
366
+ * @param {string} [commandName="hbom"] Invoked command name for tailored guidance.
367
+ */
368
+ export function ensureHbomRuntimeSupport(options = {}, commandName = "hbom") {
369
+ if (!options.includeRuntime) {
370
+ return;
371
+ }
372
+ const pluginRuntime = resolveCdxgenPlugins();
373
+ const osqueryBinary = resolvePluginBinary("osquery", pluginRuntime);
374
+ if (osqueryBinary) {
375
+ return;
376
+ }
377
+ const normalizedCommandName = `${commandName || "hbom"}`.trim() || "hbom";
378
+ const standardCommandName = normalizedCommandName.endsWith("-slim")
379
+ ? normalizedCommandName.replace(/-slim$/u, "")
380
+ : "hbom";
381
+ const followUpGuidance = normalizedCommandName.endsWith("-slim")
382
+ ? `'${normalizedCommandName}' is hardware-only by default. Use '${standardCommandName}' for bundled '--include-runtime' support, or configure OSQUERY_CMD explicitly.`
383
+ : `'${standardCommandName}' is the bundled option required for '--include-runtime' support. Install the optional '@cdxgen/cdxgen-plugins-bin*' companion bundle or configure OSQUERY_CMD explicitly.`;
384
+ throw new Error(
385
+ `HBOM '--include-runtime' requires the osquery companion binary from '@cdxgen/cdxgen-plugins-bin*'. ${followUpGuidance}`,
386
+ );
387
+ }
388
+
389
+ /**
390
+ * Translate cdxgen CLI options to cdx-hbom collector options.
391
+ *
392
+ * @param {object} [options={}] CLI options.
393
+ * @returns {object} cdx-hbom collector options.
394
+ */
395
+ export function normalizeHbomOptions(options = {}) {
396
+ const timeoutValue = options.timeoutMs ?? options.timeout;
397
+ const timeoutMs =
398
+ timeoutValue === undefined || timeoutValue === null || timeoutValue === ""
399
+ ? undefined
400
+ : Number.parseInt(`${timeoutValue}`, 10);
401
+ const includeCommandEnrichment =
402
+ options.includeCommandEnrichment ?? !options.noCommandEnrichment;
403
+ const allowPartial = options.allowPartial ?? !options.strict;
404
+
405
+ return {
406
+ allowPartial,
407
+ architecture: options.arch ?? options.architecture,
408
+ dryRun: options.dryRun ?? isDryRun,
409
+ includeCommandEnrichment,
410
+ includePlistEnrichment:
411
+ options.includePlistEnrichment ?? options.plistEnrichment ?? false,
412
+ includePrivilegedEnrichment:
413
+ options.includePrivilegedEnrichment ?? options.privileged ?? false,
414
+ includeSensitiveIdentifiers:
415
+ options.includeSensitiveIdentifiers ?? options.sensitive ?? false,
416
+ platform: options.platform,
417
+ timeoutMs:
418
+ Number.isNaN(timeoutMs) || timeoutMs <= 0 ? undefined : timeoutMs,
419
+ };
420
+ }
421
+
422
+ function getHbomTraceKind(activity) {
423
+ if (activity.kind === "command") {
424
+ return "execute";
425
+ }
426
+ if (activity.kind === "file-read" || activity.kind === "symlink-read") {
427
+ return "read";
428
+ }
429
+ if (activity.kind === "dir-read") {
430
+ return "discover";
431
+ }
432
+ return activity.kind || "hbom";
433
+ }
434
+
435
+ function getHbomTraceReason(activity) {
436
+ if (activity.reason) {
437
+ return activity.reason;
438
+ }
439
+ if (activity.kind === "command") {
440
+ if (activity.status === "completed") {
441
+ return undefined;
442
+ }
443
+ return `HBOM command ${activity.id || activity.command || activity.target} did not complete successfully.`;
444
+ }
445
+ return undefined;
446
+ }
447
+
448
+ function recordHbomCollectorTrace(trace) {
449
+ const activities = Array.isArray(trace?.activities) ? trace.activities : [];
450
+ for (const activity of activities) {
451
+ recordActivity({
452
+ category: activity.category,
453
+ commandId: activity.id,
454
+ hbomActivityKind: activity.kind,
455
+ parser: activity.parser,
456
+ phase: activity.phase,
457
+ purpose: activity.purpose,
458
+ kind: getHbomTraceKind(activity),
459
+ reason: getHbomTraceReason(activity),
460
+ status: activity.status,
461
+ target: activity.target,
462
+ });
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Build an activity target for the requested HBOM collection.
468
+ *
469
+ * @param {object} [options={}] CLI options.
470
+ * @returns {string} Activity target description.
471
+ */
472
+ function getHbomCollectionTarget(options = {}) {
473
+ const platform = options.platform ? `${options.platform}`.trim() : "";
474
+ const architecture = options.architecture ?? options.arch;
475
+ const normalizedArchitecture = architecture ? `${architecture}`.trim() : "";
476
+ if (platform && normalizedArchitecture) {
477
+ return `${platform}/${normalizedArchitecture}`;
478
+ }
479
+ if (platform || normalizedArchitecture) {
480
+ return platform || normalizedArchitecture;
481
+ }
482
+ return "current-host";
483
+ }
484
+
485
+ /**
486
+ * Create a minimal CycloneDX HBOM document for dry-run mode.
487
+ *
488
+ * @param {object} [options={}] CLI options.
489
+ * @returns {object} Synthetic CycloneDX HBOM document.
490
+ */
491
+ function createDryRunHbomDocument(options = {}) {
492
+ return {
493
+ bomFormat: "CycloneDX",
494
+ components: [],
495
+ dependencies: [],
496
+ metadata: {
497
+ timestamp: new Date().toISOString(),
498
+ tools: {
499
+ components: [],
500
+ },
501
+ },
502
+ specVersion: `${options.specVersion || "1.7"}`,
503
+ version: 1,
504
+ };
505
+ }
506
+
507
+ function addUniqueStringProperty(properties, propertyName, propertyValue) {
508
+ if (propertyValue === undefined || propertyValue === null) {
509
+ return;
510
+ }
511
+ const normalizedValue = `${propertyValue}`.trim();
512
+ if (!normalizedValue) {
513
+ return;
514
+ }
515
+ if (
516
+ properties.some(
517
+ (property) =>
518
+ property?.name === propertyName &&
519
+ `${property?.value ?? ""}` === normalizedValue,
520
+ )
521
+ ) {
522
+ return;
523
+ }
524
+ properties.push({
525
+ name: propertyName,
526
+ value: normalizedValue,
527
+ });
528
+ }
529
+
530
+ export function addHbomAnalysisProperties(bomJson) {
531
+ if (!bomJson || typeof bomJson !== "object") {
532
+ return bomJson;
533
+ }
534
+
535
+ const commandDiagnosticSummary = getHbomCommandDiagnosticSummary(bomJson);
536
+ const retainedProperties = Array.isArray(bomJson.properties)
537
+ ? bomJson.properties.filter(
538
+ (property) =>
539
+ !`${property?.name || ""}`.startsWith("cdx:hbom:analysis:"),
540
+ )
541
+ : [];
542
+
543
+ addUniqueStringProperty(
544
+ retainedProperties,
545
+ "cdx:hbom:analysis:commandDiagnosticCount",
546
+ commandDiagnosticSummary.commandDiagnosticCount,
547
+ );
548
+ addUniqueStringProperty(
549
+ retainedProperties,
550
+ "cdx:hbom:analysis:actionableDiagnosticCount",
551
+ commandDiagnosticSummary.actionableDiagnosticCount,
552
+ );
553
+ addUniqueStringProperty(
554
+ retainedProperties,
555
+ "cdx:hbom:analysis:missingCommandCount",
556
+ commandDiagnosticSummary.missingCommandCount,
557
+ );
558
+ addUniqueStringProperty(
559
+ retainedProperties,
560
+ "cdx:hbom:analysis:installHintCount",
561
+ commandDiagnosticSummary.installHintCount,
562
+ );
563
+ addUniqueStringProperty(
564
+ retainedProperties,
565
+ "cdx:hbom:analysis:permissionDeniedCount",
566
+ commandDiagnosticSummary.permissionDeniedCount,
567
+ );
568
+ addUniqueStringProperty(
569
+ retainedProperties,
570
+ "cdx:hbom:analysis:privilegeHintCount",
571
+ commandDiagnosticSummary.privilegeHintCount,
572
+ );
573
+ addUniqueStringProperty(
574
+ retainedProperties,
575
+ "cdx:hbom:analysis:partialSupportCount",
576
+ commandDiagnosticSummary.partialSupportCount,
577
+ );
578
+ addUniqueStringProperty(
579
+ retainedProperties,
580
+ "cdx:hbom:analysis:timeoutCount",
581
+ commandDiagnosticSummary.timeoutCount,
582
+ );
583
+ addUniqueStringProperty(
584
+ retainedProperties,
585
+ "cdx:hbom:analysis:commandErrorCount",
586
+ commandDiagnosticSummary.commandErrorCount,
587
+ );
588
+ if (commandDiagnosticSummary.diagnosticIssues.length) {
589
+ addUniqueStringProperty(
590
+ retainedProperties,
591
+ "cdx:hbom:analysis:diagnosticIssues",
592
+ commandDiagnosticSummary.diagnosticIssues.join(","),
593
+ );
594
+ }
595
+ if (commandDiagnosticSummary.missingCommands.length) {
596
+ addUniqueStringProperty(
597
+ retainedProperties,
598
+ "cdx:hbom:analysis:missingCommands",
599
+ commandDiagnosticSummary.missingCommands.join(","),
600
+ );
601
+ }
602
+ if (commandDiagnosticSummary.missingCommandIds.length) {
603
+ addUniqueStringProperty(
604
+ retainedProperties,
605
+ "cdx:hbom:analysis:missingCommandIds",
606
+ commandDiagnosticSummary.missingCommandIds.join(","),
607
+ );
608
+ }
609
+ if (commandDiagnosticSummary.permissionDeniedCommands.length) {
610
+ addUniqueStringProperty(
611
+ retainedProperties,
612
+ "cdx:hbom:analysis:permissionDeniedCommands",
613
+ commandDiagnosticSummary.permissionDeniedCommands.join(","),
614
+ );
615
+ }
616
+ if (commandDiagnosticSummary.permissionDeniedIds.length) {
617
+ addUniqueStringProperty(
618
+ retainedProperties,
619
+ "cdx:hbom:analysis:permissionDeniedIds",
620
+ commandDiagnosticSummary.permissionDeniedIds.join(","),
621
+ );
622
+ }
623
+ if (commandDiagnosticSummary.partialSupportIds.length) {
624
+ addUniqueStringProperty(
625
+ retainedProperties,
626
+ "cdx:hbom:analysis:partialSupportIds",
627
+ commandDiagnosticSummary.partialSupportIds.join(","),
628
+ );
629
+ }
630
+ if (commandDiagnosticSummary.timeoutIds.length) {
631
+ addUniqueStringProperty(
632
+ retainedProperties,
633
+ "cdx:hbom:analysis:timeoutIds",
634
+ commandDiagnosticSummary.timeoutIds.join(","),
635
+ );
636
+ }
637
+ if (commandDiagnosticSummary.commandErrorIds.length) {
638
+ addUniqueStringProperty(
639
+ retainedProperties,
640
+ "cdx:hbom:analysis:commandErrorIds",
641
+ commandDiagnosticSummary.commandErrorIds.join(","),
642
+ );
643
+ }
644
+ if (commandDiagnosticSummary.requiresPrivilegedEnrichment) {
645
+ addUniqueStringProperty(
646
+ retainedProperties,
647
+ "cdx:hbom:analysis:requiresPrivileged",
648
+ true,
649
+ );
650
+ }
651
+
652
+ bomJson.properties = retainedProperties;
653
+ return bomJson;
654
+ }
655
+
656
+ /**
657
+ * Generate an HBOM using the optional cdx-hbom package.
658
+ *
659
+ * @param {object} [options={}] CLI options.
660
+ * @returns {Promise<object>} CycloneDX HBOM document.
661
+ */
662
+ export async function createHbomDocument(options = {}) {
663
+ ensureSupportedHbomSpecVersion(options.specVersion);
664
+ const hbomModule = await importHbomModule(options);
665
+ if (typeof hbomModule.collectHardware !== "function") {
666
+ throw new Error(
667
+ "The installed '@cdxgen/cdx-hbom' package does not expose collectHardware().",
668
+ );
669
+ }
670
+ const normalizedOptions = normalizeHbomOptions(options);
671
+ await enforceSecureModeHbomAllowlists(hbomModule, normalizedOptions);
672
+ if (isDryRun && typeof hbomModule.createCollectorTrace === "function") {
673
+ normalizedOptions.trace = hbomModule.createCollectorTrace();
674
+ } else if (isDryRun) {
675
+ recordActivity({
676
+ kind: "hbom",
677
+ reason:
678
+ "Dry run mode blocks HBOM collection and reports the requested host inventory instead.",
679
+ status: "blocked",
680
+ target: getHbomCollectionTarget(options),
681
+ });
682
+ return createDryRunHbomDocument(options);
683
+ }
684
+ const bomJson = addHbomAnalysisProperties(
685
+ await hbomModule.collectHardware(normalizedOptions),
686
+ );
687
+ if (isDryRun) {
688
+ recordHbomCollectorTrace(
689
+ hbomModule.getCollectorTrace?.(bomJson) ?? normalizedOptions.trace,
690
+ );
691
+ }
692
+ return bomJson;
693
+ }
694
+
695
+ /**
696
+ * Normalize project types to lowercase strings.
697
+ *
698
+ * @param {string|string[]|undefined|null} projectTypes Project types.
699
+ * @returns {string[]} Normalized project types.
700
+ */
701
+ function normalizeProjectTypes(projectTypes) {
702
+ if (!projectTypes) {
703
+ return [];
704
+ }
705
+ const values = Array.isArray(projectTypes) ? projectTypes : [projectTypes];
706
+ return values
707
+ .flatMap((projectType) => `${projectType}`.split(","))
708
+ .map((projectType) => projectType.trim().toLowerCase())
709
+ .filter(Boolean);
710
+ }