@cyclonedx/cdxgen 12.3.3 → 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 (157) hide show
  1. package/README.md +64 -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 +42 -18
  20. package/data/rules/container-risk.yaml +14 -7
  21. package/data/rules/dependency-sources.yaml +11 -0
  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 +14 -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 +506 -88
  38. package/lib/cli/index.poku.js +1352 -212
  39. package/lib/evinser/evinser.js +14 -9
  40. package/lib/helpers/analyzer.js +1406 -29
  41. package/lib/helpers/analyzer.poku.js +342 -0
  42. package/lib/helpers/analyzerScope.js +712 -0
  43. package/lib/helpers/asarutils.js +1556 -0
  44. package/lib/helpers/asarutils.poku.js +443 -0
  45. package/lib/helpers/auditCategories.js +12 -0
  46. package/lib/helpers/auditCategories.poku.js +32 -0
  47. package/lib/helpers/cbomutils.js +271 -1
  48. package/lib/helpers/cbomutils.poku.js +248 -5
  49. package/lib/helpers/display.js +291 -1
  50. package/lib/helpers/display.poku.js +149 -0
  51. package/lib/helpers/evidenceUtils.js +58 -0
  52. package/lib/helpers/evidenceUtils.poku.js +54 -0
  53. package/lib/helpers/exportUtils.js +9 -0
  54. package/lib/helpers/gtfobins.js +142 -8
  55. package/lib/helpers/gtfobins.poku.js +24 -1
  56. package/lib/helpers/hbom.js +710 -0
  57. package/lib/helpers/hbom.poku.js +496 -0
  58. package/lib/helpers/hbomAnalysis.js +268 -0
  59. package/lib/helpers/hbomAnalysis.poku.js +249 -0
  60. package/lib/helpers/hbomLoader.js +35 -0
  61. package/lib/helpers/hostTopology.js +803 -0
  62. package/lib/helpers/hostTopology.poku.js +363 -0
  63. package/lib/helpers/inventoryStats.js +69 -0
  64. package/lib/helpers/inventoryStats.poku.js +86 -0
  65. package/lib/helpers/lolbas.js +19 -1
  66. package/lib/helpers/lolbas.poku.js +23 -0
  67. package/lib/helpers/osqueryTransform.js +47 -0
  68. package/lib/helpers/osqueryTransform.poku.js +47 -0
  69. package/lib/helpers/plugins.js +349 -0
  70. package/lib/helpers/plugins.poku.js +57 -0
  71. package/lib/helpers/protobom.js +156 -45
  72. package/lib/helpers/protobom.poku.js +140 -5
  73. package/lib/helpers/remote/dependency-track.js +36 -3
  74. package/lib/helpers/remote/dependency-track.poku.js +44 -0
  75. package/lib/helpers/source.js +24 -0
  76. package/lib/helpers/source.poku.js +32 -0
  77. package/lib/helpers/utils.js +1438 -93
  78. package/lib/helpers/utils.poku.js +846 -4
  79. package/lib/managers/binary.e2e.poku.js +367 -0
  80. package/lib/managers/binary.js +2293 -353
  81. package/lib/managers/binary.poku.js +1699 -1
  82. package/lib/managers/docker.js +201 -79
  83. package/lib/managers/docker.poku.js +337 -12
  84. package/lib/server/server.js +2 -27
  85. package/lib/stages/postgen/annotator.js +38 -0
  86. package/lib/stages/postgen/annotator.poku.js +107 -1
  87. package/lib/stages/postgen/auditBom.js +121 -18
  88. package/lib/stages/postgen/auditBom.poku.js +1366 -31
  89. package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
  90. package/lib/stages/postgen/postgen.js +192 -1
  91. package/lib/stages/postgen/postgen.poku.js +321 -0
  92. package/lib/stages/postgen/ruleEngine.js +116 -0
  93. package/lib/stages/pregen/envAudit.js +14 -3
  94. package/package.json +23 -21
  95. package/types/bin/hbom.d.ts +3 -0
  96. package/types/bin/hbom.d.ts.map +1 -0
  97. package/types/bin/repl.d.ts.map +1 -1
  98. package/types/lib/audit/index.d.ts +44 -0
  99. package/types/lib/audit/index.d.ts.map +1 -1
  100. package/types/lib/audit/reporters.d.ts +16 -0
  101. package/types/lib/audit/reporters.d.ts.map +1 -1
  102. package/types/lib/audit/targets.d.ts.map +1 -1
  103. package/types/lib/cli/index.d.ts +16 -0
  104. package/types/lib/cli/index.d.ts.map +1 -1
  105. package/types/lib/evinser/evinser.d.ts +4 -0
  106. package/types/lib/evinser/evinser.d.ts.map +1 -1
  107. package/types/lib/helpers/analyzer.d.ts +33 -0
  108. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  109. package/types/lib/helpers/analyzerScope.d.ts +11 -0
  110. package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
  111. package/types/lib/helpers/asarutils.d.ts +34 -0
  112. package/types/lib/helpers/asarutils.d.ts.map +1 -0
  113. package/types/lib/helpers/auditCategories.d.ts +5 -0
  114. package/types/lib/helpers/auditCategories.d.ts.map +1 -1
  115. package/types/lib/helpers/cbomutils.d.ts +3 -2
  116. package/types/lib/helpers/cbomutils.d.ts.map +1 -1
  117. package/types/lib/helpers/display.d.ts.map +1 -1
  118. package/types/lib/helpers/evidenceUtils.d.ts +8 -0
  119. package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
  120. package/types/lib/helpers/exportUtils.d.ts.map +1 -1
  121. package/types/lib/helpers/gtfobins.d.ts +8 -0
  122. package/types/lib/helpers/gtfobins.d.ts.map +1 -1
  123. package/types/lib/helpers/hbom.d.ts +49 -0
  124. package/types/lib/helpers/hbom.d.ts.map +1 -0
  125. package/types/lib/helpers/hbomAnalysis.d.ts +62 -0
  126. package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
  127. package/types/lib/helpers/hbomLoader.d.ts +7 -0
  128. package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
  129. package/types/lib/helpers/hostTopology.d.ts +12 -0
  130. package/types/lib/helpers/hostTopology.d.ts.map +1 -0
  131. package/types/lib/helpers/inventoryStats.d.ts +11 -0
  132. package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
  133. package/types/lib/helpers/lolbas.d.ts.map +1 -1
  134. package/types/lib/helpers/osqueryTransform.d.ts +3 -0
  135. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
  136. package/types/lib/helpers/plugins.d.ts +58 -0
  137. package/types/lib/helpers/plugins.d.ts.map +1 -0
  138. package/types/lib/helpers/protobom.d.ts +3 -4
  139. package/types/lib/helpers/protobom.d.ts.map +1 -1
  140. package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
  141. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  142. package/types/lib/helpers/source.d.ts.map +1 -1
  143. package/types/lib/helpers/utils.d.ts +45 -8
  144. package/types/lib/helpers/utils.d.ts.map +1 -1
  145. package/types/lib/managers/binary.d.ts +5 -0
  146. package/types/lib/managers/binary.d.ts.map +1 -1
  147. package/types/lib/managers/docker.d.ts.map +1 -1
  148. package/types/lib/server/server.d.ts +2 -1
  149. package/types/lib/server/server.d.ts.map +1 -1
  150. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  151. package/types/lib/stages/postgen/auditBom.d.ts +26 -1
  152. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  153. package/types/lib/stages/postgen/postgen.d.ts +2 -1
  154. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  155. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  156. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  157. package/data/spdx-model-v3.0.1.jsonld +0 -15999
@@ -0,0 +1,268 @@
1
+ import { getPropertyValue } from "./inventoryStats.js";
2
+
3
+ function getPropertyValues(propertiesOrObject, propertyName) {
4
+ const properties = Array.isArray(propertiesOrObject)
5
+ ? propertiesOrObject
6
+ : Array.isArray(propertiesOrObject?.properties)
7
+ ? propertiesOrObject.properties
8
+ : [];
9
+ return properties
10
+ .filter((property) => property?.name === propertyName)
11
+ .map((property) => property.value)
12
+ .filter(
13
+ (value) => value !== undefined && value !== null && `${value}` !== "",
14
+ );
15
+ }
16
+
17
+ function safeParseDiagnosticValue(value) {
18
+ if (typeof value !== "string") {
19
+ return undefined;
20
+ }
21
+ try {
22
+ const parsedValue = JSON.parse(value);
23
+ if (!parsedValue || typeof parsedValue !== "object") {
24
+ return undefined;
25
+ }
26
+ return parsedValue;
27
+ } catch {
28
+ return undefined;
29
+ }
30
+ }
31
+
32
+ function uniqueSortedStrings(values = []) {
33
+ return [
34
+ ...new Set(values.map((value) => `${value}`.trim()).filter(Boolean)),
35
+ ].sort((firstValue, secondValue) => firstValue.localeCompare(secondValue));
36
+ }
37
+
38
+ function getDiagnosticIdentifiers(commandDiagnostics = []) {
39
+ return uniqueSortedStrings(
40
+ commandDiagnostics
41
+ .map((entry) => entry.id ?? entry.command)
42
+ .filter((value) => value !== undefined && value !== null),
43
+ );
44
+ }
45
+
46
+ const HBOM_KNOWN_COMMAND_DIAGNOSTIC_ISSUES = new Set([
47
+ "missing-command",
48
+ "permission-denied",
49
+ "partial-support",
50
+ "timeout",
51
+ ]);
52
+
53
+ export function getHbomCommandDiagnostics(bomJson) {
54
+ return getPropertyValues(bomJson, "cdx:hbom:evidence:commandDiagnostic")
55
+ .map((value) => safeParseDiagnosticValue(value))
56
+ .filter(Boolean);
57
+ }
58
+
59
+ export function getHbomCommandDiagnosticSummary(bomJson) {
60
+ const commandDiagnostics = getHbomCommandDiagnostics(bomJson);
61
+ const missingCommandDiagnostics = commandDiagnostics.filter(
62
+ (entry) => entry.issue === "missing-command",
63
+ );
64
+ const permissionDeniedDiagnostics = commandDiagnostics.filter(
65
+ (entry) => entry.issue === "permission-denied",
66
+ );
67
+ const partialSupportDiagnostics = commandDiagnostics.filter(
68
+ (entry) => entry.issue === "partial-support",
69
+ );
70
+ const timeoutDiagnostics = commandDiagnostics.filter(
71
+ (entry) => entry.issue === "timeout",
72
+ );
73
+ const commandErrorDiagnostics = commandDiagnostics.filter((entry) => {
74
+ const issue = `${entry.issue ?? ""}`.trim();
75
+ return !!issue && !HBOM_KNOWN_COMMAND_DIAGNOSTIC_ISSUES.has(issue);
76
+ });
77
+ const installHints = uniqueSortedStrings(
78
+ missingCommandDiagnostics
79
+ .map((entry) => entry.installHint)
80
+ .filter((value) => value !== undefined && value !== null),
81
+ );
82
+ const privilegeHints = uniqueSortedStrings(
83
+ permissionDeniedDiagnostics
84
+ .map((entry) => entry.privilegeHint)
85
+ .filter((value) => value !== undefined && value !== null),
86
+ );
87
+ const missingCommands = uniqueSortedStrings(
88
+ missingCommandDiagnostics
89
+ .map((entry) => entry.command ?? entry.id)
90
+ .filter((value) => value !== undefined && value !== null),
91
+ );
92
+ const permissionDeniedCommands = uniqueSortedStrings(
93
+ permissionDeniedDiagnostics
94
+ .map((entry) => entry.command ?? entry.id)
95
+ .filter((value) => value !== undefined && value !== null),
96
+ );
97
+ const diagnosticIssues = uniqueSortedStrings(
98
+ commandDiagnostics
99
+ .map((entry) => entry.issue)
100
+ .filter((value) => value !== undefined && value !== null),
101
+ );
102
+
103
+ return {
104
+ actionableDiagnosticCount:
105
+ missingCommandDiagnostics.length + permissionDeniedDiagnostics.length,
106
+ commandDiagnosticCount: commandDiagnostics.length,
107
+ commandDiagnostics,
108
+ commandErrorCount: commandErrorDiagnostics.length,
109
+ commandErrorIds: getDiagnosticIdentifiers(commandErrorDiagnostics),
110
+ diagnosticIssues,
111
+ installHintCount: installHints.length,
112
+ installHints,
113
+ missingCommandCount: missingCommandDiagnostics.length,
114
+ missingCommandIds: getDiagnosticIdentifiers(missingCommandDiagnostics),
115
+ missingCommands,
116
+ partialSupportCount: partialSupportDiagnostics.length,
117
+ partialSupportIds: getDiagnosticIdentifiers(partialSupportDiagnostics),
118
+ permissionDeniedCommands,
119
+ permissionDeniedCount: permissionDeniedDiagnostics.length,
120
+ permissionDeniedIds: getDiagnosticIdentifiers(permissionDeniedDiagnostics),
121
+ privilegeHintCount: privilegeHints.length,
122
+ privilegeHints,
123
+ requiresPrivilegedEnrichment:
124
+ permissionDeniedDiagnostics.length > 0 && privilegeHints.length > 0,
125
+ timeoutIds: getDiagnosticIdentifiers(timeoutDiagnostics),
126
+ timeoutCount: timeoutDiagnostics.length,
127
+ };
128
+ }
129
+
130
+ export function isHbomLikeBom(bomJson) {
131
+ if (!bomJson) {
132
+ return false;
133
+ }
134
+ if (
135
+ getPropertyValues(bomJson, "cdx:hbom:collectorProfile").length ||
136
+ getPropertyValues(bomJson, "cdx:hbom:targetPlatform").length ||
137
+ (bomJson?.properties || []).some((property) =>
138
+ `${property?.name || ""}`.startsWith("cdx:hbom:"),
139
+ )
140
+ ) {
141
+ return true;
142
+ }
143
+ if (
144
+ (bomJson?.metadata?.component?.properties || []).some((property) =>
145
+ `${property?.name || ""}`.startsWith("cdx:hbom:"),
146
+ )
147
+ ) {
148
+ return true;
149
+ }
150
+ return (bomJson?.components || []).some((component) =>
151
+ (component?.properties || []).some(
152
+ (property) =>
153
+ property?.name === "cdx:hbom:hardwareClass" ||
154
+ `${property?.name || ""}`.startsWith("cdx:hbom:"),
155
+ ),
156
+ );
157
+ }
158
+
159
+ export function getHbomHardwareClass(component) {
160
+ return getPropertyValue(component, "cdx:hbom:hardwareClass");
161
+ }
162
+
163
+ export function getHbomHardwareClassCounts(components = []) {
164
+ const counts = new Map();
165
+ for (const component of components || []) {
166
+ const hardwareClass = getHbomHardwareClass(component);
167
+ if (!hardwareClass) {
168
+ continue;
169
+ }
170
+ counts.set(hardwareClass, (counts.get(hardwareClass) || 0) + 1);
171
+ }
172
+ return Array.from(counts.entries())
173
+ .map(([hardwareClass, count]) => ({ hardwareClass, count }))
174
+ .sort(
175
+ (firstEntry, secondEntry) =>
176
+ secondEntry.count - firstEntry.count ||
177
+ firstEntry.hardwareClass.localeCompare(secondEntry.hardwareClass),
178
+ );
179
+ }
180
+
181
+ export function formatHbomHardwareClassSummary(hardwareClassCounts = []) {
182
+ return hardwareClassCounts
183
+ .slice(0, 5)
184
+ .map(({ hardwareClass, count }) => `${hardwareClass} (${count})`)
185
+ .join(", ");
186
+ }
187
+
188
+ export function getHbomSummary(bomJson) {
189
+ const metadataComponent = bomJson?.metadata?.component;
190
+ const hardwareClassCounts = getHbomHardwareClassCounts(
191
+ bomJson?.components || [],
192
+ );
193
+ const commandDiagnosticSummary = getHbomCommandDiagnosticSummary(bomJson);
194
+ const evidenceCommands = getPropertyValues(
195
+ bomJson,
196
+ "cdx:hbom:evidence:command",
197
+ );
198
+ const evidenceFiles = getPropertyValues(bomJson, "cdx:hbom:evidence:file");
199
+ const commandCountValue = getPropertyValue(
200
+ bomJson,
201
+ "cdx:hbom:evidence:commandCount",
202
+ );
203
+ const fileCountValue = getPropertyValue(
204
+ bomJson,
205
+ "cdx:hbom:evidence:fileCount",
206
+ );
207
+ const evidenceCommandCount = Number.parseInt(
208
+ `${commandCountValue ?? evidenceCommands.length}`,
209
+ 10,
210
+ );
211
+ const evidenceFileCount = Number.parseInt(
212
+ `${fileCountValue ?? evidenceFiles.length}`,
213
+ 10,
214
+ );
215
+
216
+ return {
217
+ actionableDiagnosticCount:
218
+ commandDiagnosticSummary.actionableDiagnosticCount,
219
+ architecture:
220
+ getPropertyValue(metadataComponent, "cdx:hbom:architecture") ||
221
+ getPropertyValue(bomJson, "cdx:hbom:targetArchitecture") ||
222
+ getPropertyValue(bomJson, "cdx:hbom:architecture"),
223
+ collectorProfile: getPropertyValue(bomJson, "cdx:hbom:collectorProfile"),
224
+ commandDiagnosticCount: commandDiagnosticSummary.commandDiagnosticCount,
225
+ commandDiagnostics: commandDiagnosticSummary.commandDiagnostics,
226
+ commandErrorCount: commandDiagnosticSummary.commandErrorCount,
227
+ commandErrorIds: commandDiagnosticSummary.commandErrorIds,
228
+ componentCount: (bomJson?.components || []).length,
229
+ diagnosticIssues: commandDiagnosticSummary.diagnosticIssues,
230
+ evidenceCommandCount: Number.isNaN(evidenceCommandCount)
231
+ ? evidenceCommands.length
232
+ : evidenceCommandCount,
233
+ evidenceCommands,
234
+ evidenceFileCount: Number.isNaN(evidenceFileCount)
235
+ ? evidenceFiles.length
236
+ : evidenceFileCount,
237
+ evidenceFiles,
238
+ hardwareClassCount: hardwareClassCounts.length,
239
+ hardwareClassCounts,
240
+ identifierPolicy:
241
+ getPropertyValue(metadataComponent, "cdx:hbom:identifierPolicy") ||
242
+ getPropertyValue(bomJson, "cdx:hbom:identifierPolicy"),
243
+ installHintCount: commandDiagnosticSummary.installHintCount,
244
+ installHints: commandDiagnosticSummary.installHints,
245
+ manufacturer: metadataComponent?.manufacturer?.name,
246
+ metadataName: metadataComponent?.name,
247
+ metadataType: metadataComponent?.type,
248
+ missingCommandCount: commandDiagnosticSummary.missingCommandCount,
249
+ missingCommandIds: commandDiagnosticSummary.missingCommandIds,
250
+ missingCommands: commandDiagnosticSummary.missingCommands,
251
+ partialSupportCount: commandDiagnosticSummary.partialSupportCount,
252
+ partialSupportIds: commandDiagnosticSummary.partialSupportIds,
253
+ platform:
254
+ getPropertyValue(metadataComponent, "cdx:hbom:platform") ||
255
+ getPropertyValue(bomJson, "cdx:hbom:targetPlatform") ||
256
+ getPropertyValue(bomJson, "cdx:hbom:platform"),
257
+ permissionDeniedCommands: commandDiagnosticSummary.permissionDeniedCommands,
258
+ permissionDeniedCount: commandDiagnosticSummary.permissionDeniedCount,
259
+ permissionDeniedIds: commandDiagnosticSummary.permissionDeniedIds,
260
+ privilegeHintCount: commandDiagnosticSummary.privilegeHintCount,
261
+ privilegeHints: commandDiagnosticSummary.privilegeHints,
262
+ requiresPrivilegedEnrichment:
263
+ commandDiagnosticSummary.requiresPrivilegedEnrichment,
264
+ timeoutIds: commandDiagnosticSummary.timeoutIds,
265
+ timeoutCount: commandDiagnosticSummary.timeoutCount,
266
+ topHardwareClasses: hardwareClassCounts.slice(0, 5),
267
+ };
268
+ }
@@ -0,0 +1,249 @@
1
+ import { assert, describe, it } from "poku";
2
+
3
+ import {
4
+ formatHbomHardwareClassSummary,
5
+ getHbomCommandDiagnosticSummary,
6
+ getHbomHardwareClassCounts,
7
+ getHbomSummary,
8
+ isHbomLikeBom,
9
+ } from "./hbomAnalysis.js";
10
+
11
+ describe("hbomAnalysis helpers", () => {
12
+ const hbomFixture = {
13
+ metadata: {
14
+ component: {
15
+ name: "demo-host",
16
+ type: "device",
17
+ manufacturer: { name: "Example Corp" },
18
+ properties: [
19
+ { name: "cdx:hbom:platform", value: "linux" },
20
+ { name: "cdx:hbom:architecture", value: "amd64" },
21
+ { name: "cdx:hbom:identifierPolicy", value: "redacted-by-default" },
22
+ ],
23
+ },
24
+ },
25
+ components: [
26
+ {
27
+ name: "eth0",
28
+ properties: [
29
+ { name: "cdx:hbom:hardwareClass", value: "network-interface" },
30
+ ],
31
+ },
32
+ {
33
+ name: "wlan0",
34
+ properties: [
35
+ { name: "cdx:hbom:hardwareClass", value: "network-interface" },
36
+ ],
37
+ },
38
+ {
39
+ name: "nvme0",
40
+ properties: [{ name: "cdx:hbom:hardwareClass", value: "storage" }],
41
+ },
42
+ {
43
+ name: "BAT0",
44
+ properties: [{ name: "cdx:hbom:hardwareClass", value: "power" }],
45
+ },
46
+ ],
47
+ properties: [
48
+ { name: "cdx:hbom:collectorProfile", value: "linux-amd64-v1" },
49
+ { name: "cdx:hbom:targetPlatform", value: "linux" },
50
+ { name: "cdx:hbom:targetArchitecture", value: "amd64" },
51
+ { name: "cdx:hbom:evidence:commandCount", value: "2" },
52
+ {
53
+ name: "cdx:hbom:evidence:command",
54
+ value: "lscpu-json|cpu-memory|/usr/bin/lscpu -J",
55
+ },
56
+ {
57
+ name: "cdx:hbom:evidence:command",
58
+ value: "ip-link-json|network|/usr/sbin/ip -j link",
59
+ },
60
+ { name: "cdx:hbom:evidence:commandDiagnosticCount", value: "2" },
61
+ {
62
+ name: "cdx:hbom:evidence:commandDiagnostic",
63
+ value: JSON.stringify({
64
+ command: "lsusb",
65
+ id: "lsusb",
66
+ installHint:
67
+ "Command not found: install the Linux package providing lsusb (commonly `usbutils`).",
68
+ issue: "missing-command",
69
+ message: "lsusb failed with missing-command",
70
+ }),
71
+ },
72
+ {
73
+ name: "cdx:hbom:evidence:commandDiagnostic",
74
+ value: JSON.stringify({
75
+ command: "drm_info",
76
+ id: "drm-info-json",
77
+ issue: "permission-denied",
78
+ message: "drm_info failed with permission-denied",
79
+ privilegeHint:
80
+ "Retry with --privileged to allow a non-interactive sudo attempt for permission-sensitive Linux commands.",
81
+ }),
82
+ },
83
+ { name: "cdx:hbom:evidence:fileCount", value: "1" },
84
+ { name: "cdx:hbom:evidence:file", value: "/etc/os-release" },
85
+ ],
86
+ };
87
+
88
+ it("detects HBOMs from document and component properties", () => {
89
+ assert.strictEqual(isHbomLikeBom(hbomFixture), true);
90
+ assert.strictEqual(
91
+ isHbomLikeBom({ metadata: { component: { type: "application" } } }),
92
+ false,
93
+ );
94
+ });
95
+
96
+ it("summarizes hardware classes and evidence", () => {
97
+ assert.deepStrictEqual(getHbomHardwareClassCounts(hbomFixture.components), [
98
+ { hardwareClass: "network-interface", count: 2 },
99
+ { hardwareClass: "power", count: 1 },
100
+ { hardwareClass: "storage", count: 1 },
101
+ ]);
102
+ assert.strictEqual(
103
+ formatHbomHardwareClassSummary(
104
+ getHbomHardwareClassCounts(hbomFixture.components),
105
+ ),
106
+ "network-interface (2), power (1), storage (1)",
107
+ );
108
+
109
+ assert.deepStrictEqual(getHbomCommandDiagnosticSummary(hbomFixture), {
110
+ actionableDiagnosticCount: 2,
111
+ commandDiagnosticCount: 2,
112
+ commandDiagnostics: [
113
+ {
114
+ command: "lsusb",
115
+ id: "lsusb",
116
+ installHint:
117
+ "Command not found: install the Linux package providing lsusb (commonly `usbutils`).",
118
+ issue: "missing-command",
119
+ message: "lsusb failed with missing-command",
120
+ },
121
+ {
122
+ command: "drm_info",
123
+ id: "drm-info-json",
124
+ issue: "permission-denied",
125
+ message: "drm_info failed with permission-denied",
126
+ privilegeHint:
127
+ "Retry with --privileged to allow a non-interactive sudo attempt for permission-sensitive Linux commands.",
128
+ },
129
+ ],
130
+ commandErrorCount: 0,
131
+ commandErrorIds: [],
132
+ diagnosticIssues: ["missing-command", "permission-denied"],
133
+ installHintCount: 1,
134
+ installHints: [
135
+ "Command not found: install the Linux package providing lsusb (commonly `usbutils`).",
136
+ ],
137
+ missingCommandCount: 1,
138
+ missingCommandIds: ["lsusb"],
139
+ missingCommands: ["lsusb"],
140
+ partialSupportCount: 0,
141
+ partialSupportIds: [],
142
+ permissionDeniedCommands: ["drm_info"],
143
+ permissionDeniedCount: 1,
144
+ permissionDeniedIds: ["drm-info-json"],
145
+ privilegeHintCount: 1,
146
+ privilegeHints: [
147
+ "Retry with --privileged to allow a non-interactive sudo attempt for permission-sensitive Linux commands.",
148
+ ],
149
+ requiresPrivilegedEnrichment: true,
150
+ timeoutIds: [],
151
+ timeoutCount: 0,
152
+ });
153
+
154
+ assert.deepStrictEqual(getHbomSummary(hbomFixture), {
155
+ actionableDiagnosticCount: 2,
156
+ architecture: "amd64",
157
+ collectorProfile: "linux-amd64-v1",
158
+ commandDiagnosticCount: 2,
159
+ commandDiagnostics: [
160
+ {
161
+ command: "lsusb",
162
+ id: "lsusb",
163
+ installHint:
164
+ "Command not found: install the Linux package providing lsusb (commonly `usbutils`).",
165
+ issue: "missing-command",
166
+ message: "lsusb failed with missing-command",
167
+ },
168
+ {
169
+ command: "drm_info",
170
+ id: "drm-info-json",
171
+ issue: "permission-denied",
172
+ message: "drm_info failed with permission-denied",
173
+ privilegeHint:
174
+ "Retry with --privileged to allow a non-interactive sudo attempt for permission-sensitive Linux commands.",
175
+ },
176
+ ],
177
+ commandErrorCount: 0,
178
+ commandErrorIds: [],
179
+ componentCount: 4,
180
+ diagnosticIssues: ["missing-command", "permission-denied"],
181
+ evidenceCommandCount: 2,
182
+ evidenceCommands: [
183
+ "lscpu-json|cpu-memory|/usr/bin/lscpu -J",
184
+ "ip-link-json|network|/usr/sbin/ip -j link",
185
+ ],
186
+ evidenceFileCount: 1,
187
+ evidenceFiles: ["/etc/os-release"],
188
+ hardwareClassCount: 3,
189
+ hardwareClassCounts: [
190
+ { hardwareClass: "network-interface", count: 2 },
191
+ { hardwareClass: "power", count: 1 },
192
+ { hardwareClass: "storage", count: 1 },
193
+ ],
194
+ identifierPolicy: "redacted-by-default",
195
+ installHintCount: 1,
196
+ installHints: [
197
+ "Command not found: install the Linux package providing lsusb (commonly `usbutils`).",
198
+ ],
199
+ manufacturer: "Example Corp",
200
+ metadataName: "demo-host",
201
+ metadataType: "device",
202
+ missingCommandCount: 1,
203
+ missingCommandIds: ["lsusb"],
204
+ missingCommands: ["lsusb"],
205
+ partialSupportCount: 0,
206
+ partialSupportIds: [],
207
+ platform: "linux",
208
+ permissionDeniedCommands: ["drm_info"],
209
+ permissionDeniedCount: 1,
210
+ permissionDeniedIds: ["drm-info-json"],
211
+ privilegeHintCount: 1,
212
+ privilegeHints: [
213
+ "Retry with --privileged to allow a non-interactive sudo attempt for permission-sensitive Linux commands.",
214
+ ],
215
+ requiresPrivilegedEnrichment: true,
216
+ timeoutIds: [],
217
+ timeoutCount: 0,
218
+ topHardwareClasses: [
219
+ { hardwareClass: "network-interface", count: 2 },
220
+ { hardwareClass: "power", count: 1 },
221
+ { hardwareClass: "storage", count: 1 },
222
+ ],
223
+ });
224
+ });
225
+
226
+ it("counts invalid command argument diagnostics as other command errors", () => {
227
+ const summary = getHbomCommandDiagnosticSummary({
228
+ properties: [
229
+ {
230
+ name: "cdx:hbom:evidence:commandDiagnostic",
231
+ value: JSON.stringify({
232
+ argumentName: "edid-path",
233
+ command: "edid-decode",
234
+ id: "edid-decode:display-1",
235
+ issue: "invalid-command-argument",
236
+ message:
237
+ "edid-decode:display-1 blocked invalid edid-path: expected an absolute /sys/class/drm/<connector>/edid path after normalization",
238
+ }),
239
+ },
240
+ ],
241
+ });
242
+
243
+ assert.deepStrictEqual(summary.commandErrorIds, ["edid-decode:display-1"]);
244
+ assert.strictEqual(summary.commandErrorCount, 1);
245
+ assert.deepStrictEqual(summary.diagnosticIssues, [
246
+ "invalid-command-argument",
247
+ ]);
248
+ });
249
+ });
@@ -0,0 +1,35 @@
1
+ import { fileURLToPath, pathToFileURL } from "node:url";
2
+
3
+ import { safeExistsSync } from "./utils.js";
4
+
5
+ const LOCAL_HBOM_MODULE_URL = new URL(
6
+ "../../cdx-hbom/index.js",
7
+ import.meta.url,
8
+ );
9
+ const LOCAL_HBOM_MODULE_PATH = fileURLToPath(LOCAL_HBOM_MODULE_URL);
10
+
11
+ /**
12
+ * Resolve the optional cdx-hbom module.
13
+ *
14
+ * @returns {Promise<object>} Loaded cdx-hbom module namespace.
15
+ */
16
+ export async function importHbomModule() {
17
+ if (safeExistsSync(LOCAL_HBOM_MODULE_PATH)) {
18
+ return await import(pathToFileURL(LOCAL_HBOM_MODULE_PATH).href);
19
+ }
20
+ let hbomModule;
21
+ try {
22
+ hbomModule = await import("@cdxgen/cdx-hbom");
23
+ } catch (error) {
24
+ if (
25
+ error?.code === "ERR_MODULE_NOT_FOUND" ||
26
+ `${error?.message || ""}`.includes("@cdxgen/cdx-hbom")
27
+ ) {
28
+ throw new Error(
29
+ "HBOM support requires the optional '@cdxgen/cdx-hbom' dependency. Install it or use a build that bundles HBOM support.",
30
+ );
31
+ }
32
+ throw error;
33
+ }
34
+ return hbomModule;
35
+ }