@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
@@ -2,6 +2,14 @@ import { readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import process from "node:process";
4
4
 
5
+ import { formatOccurrenceEvidence } from "./evidenceUtils.js";
6
+ import {
7
+ formatHbomHardwareClassSummary,
8
+ getHbomSummary,
9
+ isHbomLikeBom,
10
+ } from "./hbomAnalysis.js";
11
+ import { getHostViewSummary, isMergedHostViewBom } from "./hostTopology.js";
12
+ import { getPropertyValue } from "./inventoryStats.js";
5
13
  import {
6
14
  hasComponentRegistryProvenance,
7
15
  REGISTRY_PROVENANCE_ICON,
@@ -65,12 +73,314 @@ const formatComponentName = (component, highlight) => {
65
73
  return displayName;
66
74
  };
67
75
 
68
- const printProvenanceLegend = () => {
69
- console.log(
70
- `Legend: ${REGISTRY_PROVENANCE_ICON} = registry provenance or trusted publishing evidence`,
71
- );
76
+ /**
77
+ * Builds the summary and provenance lines printed after the component table.
78
+ *
79
+ * @param {Object} bomJson CycloneDX BOM JSON object
80
+ * @param {string[]|undefined} filterTypes Optional list of component types to include
81
+ * @param {string|undefined} summaryText Optional summary message to print after the table
82
+ * @param {number} displayedProvenanceCount Number of displayed components with registry provenance
83
+ * @returns {string[]} Summary lines to print
84
+ */
85
+ export const buildTableSummaryLines = (
86
+ bomJson,
87
+ filterTypes,
88
+ summaryText,
89
+ displayedProvenanceCount = 0,
90
+ ) => {
91
+ const summaryLines = [];
92
+ if (summaryText) {
93
+ summaryLines.push(summaryText);
94
+ } else if (!filterTypes && isHbomLikeBom(bomJson)) {
95
+ const hbomSummary = getHbomSummary(bomJson);
96
+ summaryLines.push(
97
+ `HBOM includes ${hbomSummary.componentCount} hardware component(s) across ${hbomSummary.hardwareClassCount} hardware class(es)`,
98
+ );
99
+ if (hbomSummary.hardwareClassCounts.length) {
100
+ summaryLines.push(
101
+ `Top hardware classes: ${formatHbomHardwareClassSummary(hbomSummary.hardwareClassCounts)}`,
102
+ );
103
+ }
104
+ if (hbomSummary.collectorProfile) {
105
+ summaryLines.push(
106
+ `Collector profile: ${hbomSummary.collectorProfile}; command evidence: ${hbomSummary.evidenceCommandCount}; observed files: ${hbomSummary.evidenceFileCount}`,
107
+ );
108
+ }
109
+ if (hbomSummary.commandDiagnosticCount) {
110
+ const diagnosticDetails = [];
111
+ if (hbomSummary.missingCommandCount) {
112
+ diagnosticDetails.push(
113
+ `missing commands: ${hbomSummary.missingCommandCount}`,
114
+ );
115
+ }
116
+ if (hbomSummary.permissionDeniedCount) {
117
+ diagnosticDetails.push(
118
+ `permission denied: ${hbomSummary.permissionDeniedCount}`,
119
+ );
120
+ }
121
+ if (hbomSummary.partialSupportCount) {
122
+ diagnosticDetails.push(
123
+ `partial support: ${hbomSummary.partialSupportCount}`,
124
+ );
125
+ }
126
+ if (hbomSummary.timeoutCount) {
127
+ diagnosticDetails.push(`timeouts: ${hbomSummary.timeoutCount}`);
128
+ }
129
+ if (hbomSummary.commandErrorCount) {
130
+ diagnosticDetails.push(
131
+ `other command errors: ${hbomSummary.commandErrorCount}`,
132
+ );
133
+ }
134
+ summaryLines.push(
135
+ `Collector diagnostics: ${hbomSummary.commandDiagnosticCount} issue(s)${diagnosticDetails.length ? `; ${diagnosticDetails.join(", ")}` : ""}`,
136
+ );
137
+ if (hbomSummary.requiresPrivilegedEnrichment) {
138
+ summaryLines.push(
139
+ "Permission-sensitive enrichments were skipped or blocked. Re-run with --privileged where policy allows.",
140
+ );
141
+ }
142
+ }
143
+ if (isMergedHostViewBom(bomJson)) {
144
+ const hostViewSummary = getHostViewSummary(bomJson);
145
+ summaryLines.push(
146
+ `Host topology view: ${hostViewSummary.runtimeComponentCount} runtime component(s), ${hostViewSummary.topologyLinkCount} strict host/runtime topology link(s), ${hostViewSummary.linkedHardwareComponentCount} linked hardware component(s)`,
147
+ );
148
+ if (hostViewSummary.linkedRuntimeCategories.length) {
149
+ summaryLines.push(
150
+ `Linked runtime categories: ${hostViewSummary.linkedRuntimeCategories.join(", ")}`,
151
+ );
152
+ }
153
+ }
154
+ } else if (!filterTypes) {
155
+ summaryLines.push(
156
+ `BOM includes ${bomJson?.components?.length || 0} components and ${
157
+ bomJson?.dependencies?.length || 0
158
+ } dependencies`,
159
+ );
160
+ } else {
161
+ summaryLines.push(
162
+ `Components filtered based on type: ${filterTypes.join(", ")}`,
163
+ );
164
+ }
165
+ if (displayedProvenanceCount > 0) {
166
+ summaryLines.push(
167
+ `Legend: ${REGISTRY_PROVENANCE_ICON} = registry provenance or trusted publishing evidence`,
168
+ );
169
+ summaryLines.push(
170
+ `${REGISTRY_PROVENANCE_ICON} ${displayedProvenanceCount} component(s) include registry provenance or trusted publishing metadata.`,
171
+ );
172
+ }
173
+ return summaryLines;
72
174
  };
73
175
 
176
+ const HBOM_COLUMN_PRIORITY = Object.freeze([
177
+ ["cdx:hbom:status", "status"],
178
+ ["cdx:hbom:connected", "connected"],
179
+ ["cdx:hbom:connectionState", "connectionState"],
180
+ ["cdx:hbom:securityMode", "securityMode"],
181
+ ["cdx:hbom:health", "health"],
182
+ ["cdx:hbom:smartStatus", "smartStatus"],
183
+ ["cdx:hbom:powerSource", "powerSource"],
184
+ ["cdx:hbom:maximumCapacity", "maximumCapacity"],
185
+ ["cdx:hbom:chargePercent", "chargePercent"],
186
+ ["cdx:hbom:capacity", "capacity"],
187
+ ["cdx:hbom:size", "size"],
188
+ ["cdx:hbom:sizeBytes", "sizeBytes"],
189
+ ["cdx:hbom:linkRateMbps", "linkRateMbps"],
190
+ ["cdx:hbom:speedMbps", "speedMbps"],
191
+ ["cdx:hbom:resolution", "resolution"],
192
+ ["cdx:hbom:transport", "transport"],
193
+ ["cdx:hbom:connectionType", "connectionType"],
194
+ ["cdx:hbom:firmwareVersion", "firmwareVersion"],
195
+ ["cdx:hbom:driver", "driver"],
196
+ ["cdx:hbom:channel", "channel"],
197
+ ["cdx:hbom:phyMode", "phyMode"],
198
+ ["cdx:hbom:temperatureCelsius", "temperatureCelsius"],
199
+ ["cdx:hostview:runtimeAddressCount", "runtimeAddrs"],
200
+ ["cdx:hostview:kernel_modules:count", "kernelMods"],
201
+ ["cdx:hostview:mount_hardening:count", "runtimeMounts"],
202
+ ["cdx:hostview:runtime-storage:count", "runtimeStorage"],
203
+ ["cdx:hostview:linkedRuntimeCategoryCount", "runtimeLinks"],
204
+ ]);
205
+
206
+ const HBOM_CLASS_PROPERTY_PRIORITY = Object.freeze({
207
+ "audio-device": Object.freeze([
208
+ ["cdx:hbom:transport", "transport"],
209
+ ["cdx:hbom:defaultOutput", "defaultOutput"],
210
+ ["cdx:hbom:defaultInput", "defaultInput"],
211
+ ["cdx:hbom:sampleRate", "sampleRate"],
212
+ ]),
213
+ bus: Object.freeze([
214
+ ["cdx:hbom:speed", "speed"],
215
+ ["cdx:hbom:linkStatus", "linkStatus"],
216
+ ["cdx:hbom:receptacleStatus", "receptacleStatus"],
217
+ ]),
218
+ camera: Object.freeze([
219
+ ["cdx:hbom:isVirtual", "virtual"],
220
+ ["cdx:hbom:cameraModelId", "modelId"],
221
+ ]),
222
+ "bluetooth-controller": Object.freeze([
223
+ ["cdx:hbom:state", "state"],
224
+ ["cdx:hbom:transport", "transport"],
225
+ ["cdx:hbom:firmwareVersion", "firmware"],
226
+ ]),
227
+ "bluetooth-device": Object.freeze([
228
+ ["cdx:hbom:connectionState", "connection"],
229
+ ["cdx:hbom:rssi", "rssi"],
230
+ ["cdx:hbom:firmwareVersion", "firmware"],
231
+ ["cdx:hbom:minorType", "minorType"],
232
+ ]),
233
+ display: Object.freeze([
234
+ ["cdx:hbom:resolution", "resolution"],
235
+ ["cdx:hbom:connectionType", "connection"],
236
+ ["cdx:hbom:vendorId", "vendorId"],
237
+ ["cdx:hbom:productId", "productId"],
238
+ ]),
239
+ memory: Object.freeze([
240
+ ["cdx:hbom:size", "size"],
241
+ ["cdx:hbom:sizeBytes", "sizeBytes"],
242
+ ["cdx:hbom:memoryOnlineSize", "onlineSize"],
243
+ ["cdx:hbom:addressSizes", "addressSizes"],
244
+ ]),
245
+ "network-interface": Object.freeze([
246
+ ["cdx:hbom:status", "status"],
247
+ ["cdx:hbom:speedMbps", "speedMbps"],
248
+ ["cdx:hbom:duplex", "duplex"],
249
+ ["cdx:hbom:driver", "driver"],
250
+ ["cdx:hostview:runtimeAddressCount", "runtimeAddrs"],
251
+ ["cdx:hostview:kernel_modules:count", "kernelMods"],
252
+ ]),
253
+ power: Object.freeze([
254
+ ["cdx:hbom:health", "health"],
255
+ ["cdx:hbom:chargePercent", "charge%"],
256
+ ["cdx:hbom:maximumCapacity", "maxCapacity"],
257
+ ["cdx:hbom:cycleCount", "cycles"],
258
+ ["cdx:hbom:powerSource", "source"],
259
+ ]),
260
+ "power-adapter": Object.freeze([
261
+ ["cdx:hbom:connected", "connected"],
262
+ ["cdx:hbom:watts", "watts"],
263
+ ["cdx:hbom:isCharging", "charging"],
264
+ ]),
265
+ processor: Object.freeze([
266
+ ["cdx:hbom:coreCount", "cores"],
267
+ ["cdx:hbom:logicalCpuCount", "logical"],
268
+ ["cdx:hbom:physicalCpuCount", "physical"],
269
+ ]),
270
+ storage: Object.freeze([
271
+ ["cdx:hbom:capacity", "capacity"],
272
+ ["cdx:hbom:smartStatus", "smart"],
273
+ ["cdx:hbom:wearPercentageUsed", "wearUsed"],
274
+ ["cdx:hbom:transport", "transport"],
275
+ ["cdx:hbom:firmwareVersion", "firmware"],
276
+ ["cdx:hostview:mount_hardening:count", "runtimeMounts"],
277
+ ["cdx:hostview:runtime-storage:count", "runtimeStorage"],
278
+ ]),
279
+ "storage-volume": Object.freeze([
280
+ ["cdx:hbom:size", "size"],
281
+ ["cdx:hbom:capacity", "capacity"],
282
+ ["cdx:hbom:fileVault", "fileVault"],
283
+ ["cdx:hbom:isEncrypted", "encrypted"],
284
+ ["cdx:hbom:isRemovable", "removable"],
285
+ ["cdx:hostview:mount_hardening:count", "runtimeMounts"],
286
+ ["cdx:hostview:runtime-storage:count", "runtimeStorage"],
287
+ ]),
288
+ sensor: Object.freeze([
289
+ ["cdx:hbom:temperatureCelsius", "tempC"],
290
+ ["cdx:hbom:fanCount", "fanCount"],
291
+ ]),
292
+ "thermal-zone": Object.freeze([
293
+ ["cdx:hbom:temperatureCelsius", "tempC"],
294
+ ["cdx:hbom:fanCount", "fanCount"],
295
+ ]),
296
+ "wireless-adapter": Object.freeze([
297
+ ["cdx:hbom:connected", "connected"],
298
+ ["cdx:hbom:securityMode", "security"],
299
+ ["cdx:hbom:linkRateMbps", "linkMbps"],
300
+ ["cdx:hbom:channel", "channel"],
301
+ ["cdx:hbom:phyMode", "phy"],
302
+ ["cdx:hostview:runtimeAddressCount", "runtimeAddrs"],
303
+ ]),
304
+ });
305
+
306
+ function formatHbomKeyProperties(component) {
307
+ const hardwareClass = getPropertyValue(component, "cdx:hbom:hardwareClass");
308
+ const classSpecificPriority =
309
+ HBOM_CLASS_PROPERTY_PRIORITY[hardwareClass] || [];
310
+ const details = [];
311
+ const seenPropertyNames = new Set();
312
+ for (const [propertyName, label] of [
313
+ ...classSpecificPriority,
314
+ ...HBOM_COLUMN_PRIORITY,
315
+ ]) {
316
+ if (seenPropertyNames.has(propertyName)) {
317
+ continue;
318
+ }
319
+ seenPropertyNames.add(propertyName);
320
+ const value = getPropertyValue(component, propertyName);
321
+ if (value === undefined || value === null || value === "") {
322
+ continue;
323
+ }
324
+ details.push(`${label}=${value}`);
325
+ if (details.length >= 3) {
326
+ break;
327
+ }
328
+ }
329
+ return details.join(", ");
330
+ }
331
+
332
+ function printHBOMTable(bomJson, filterTypes, highlight, summaryText) {
333
+ const config = {
334
+ columnDefault: {
335
+ width: 28,
336
+ },
337
+ columnCount: 5,
338
+ columns: [
339
+ { width: 22 },
340
+ { width: 32 },
341
+ { width: 24 },
342
+ { width: 52 },
343
+ { width: 24 },
344
+ ],
345
+ };
346
+ const stream = createStream(config);
347
+ stream.write([
348
+ "Hardware Class",
349
+ "Name",
350
+ "Manufacturer / Version",
351
+ "Key Properties",
352
+ "Tags",
353
+ ]);
354
+ for (const comp of bomJson.components) {
355
+ if (filterTypes && !filterTypes.includes(comp.type)) {
356
+ continue;
357
+ }
358
+ const manufacturerOrVersion = [comp.manufacturer?.name, comp.version]
359
+ .filter(Boolean)
360
+ .join(" / ");
361
+ stream.write([
362
+ highlightStr(
363
+ getPropertyValue(comp, "cdx:hbom:hardwareClass") || comp.type || "",
364
+ highlight,
365
+ ),
366
+ formatComponentName(comp, highlight),
367
+ highlightStr(manufacturerOrVersion, highlight),
368
+ highlightStr(formatHbomKeyProperties(comp), highlight),
369
+ (comp.tags || []).join(", "),
370
+ ]);
371
+ }
372
+ stream.end();
373
+ console.log();
374
+ for (const line of buildTableSummaryLines(
375
+ bomJson,
376
+ filterTypes,
377
+ summaryText,
378
+ 0,
379
+ )) {
380
+ console.log(line);
381
+ }
382
+ }
383
+
74
384
  /**
75
385
  * Builds legend lines for dependency tree marker icons.
76
386
  *
@@ -196,6 +506,9 @@ export function printTable(
196
506
  ) {
197
507
  return printOSTable(bomJson);
198
508
  }
509
+ if (isHbomLikeBom(bomJson) && !filterTypes?.includes("cryptographic-asset")) {
510
+ return printHBOMTable(bomJson, filterTypes, highlight, summaryText);
511
+ }
199
512
  const config = {
200
513
  columnDefault: {
201
514
  width: 30,
@@ -247,24 +560,13 @@ export function printTable(
247
560
  }
248
561
  stream.end();
249
562
  console.log();
250
- if (summaryText) {
251
- console.log(summaryText);
252
- } else if (!filterTypes) {
253
- console.log(
254
- "BOM includes",
255
- bomJson?.components?.length || 0,
256
- "components and",
257
- bomJson?.dependencies?.length || 0,
258
- "dependencies",
259
- );
260
- } else {
261
- console.log(`Components filtered based on type: ${filterTypes.join(", ")}`);
262
- }
263
- if (displayedProvenanceCount > 0) {
264
- printProvenanceLegend();
265
- console.log(
266
- `${REGISTRY_PROVENANCE_ICON} ${displayedProvenanceCount} component(s) include registry provenance or trusted publishing metadata.`,
267
- );
563
+ for (const line of buildTableSummaryLines(
564
+ bomJson,
565
+ filterTypes,
566
+ summaryText,
567
+ displayedProvenanceCount,
568
+ )) {
569
+ console.log(line);
268
570
  }
269
571
  }
270
572
  const formatProps = (props) => {
@@ -371,6 +673,17 @@ const locationComparator = (a, b) => {
371
673
  }
372
674
  }
373
675
  }
676
+ if (a && b) {
677
+ const tmpA = a.match(/^(.*):(\d+)(?::(\d+))?$/);
678
+ const tmpB = b.match(/^(.*):(\d+)(?::(\d+))?$/);
679
+ if (tmpA && tmpB && tmpA[1] === tmpB[1]) {
680
+ const lineComparison = Number(tmpA[2]) - Number(tmpB[2]);
681
+ if (lineComparison !== 0) {
682
+ return lineComparison;
683
+ }
684
+ return Number(tmpA[3] || 0) - Number(tmpB[3] || 0);
685
+ }
686
+ }
374
687
  return a.localeCompare(b);
375
688
  };
376
689
 
@@ -410,7 +723,7 @@ export function printOccurrences(bomJson) {
410
723
  comp.name,
411
724
  comp.version || "",
412
725
  comp.evidence.occurrences
413
- .map((l) => l.location)
726
+ .map((occurrence) => formatOccurrenceEvidence(occurrence))
414
727
  .sort(locationComparator)
415
728
  .join("\n"),
416
729
  ];
@@ -8,6 +8,7 @@ import {
8
8
  buildActivitySummaryPayload,
9
9
  buildDependencyTreeLegendLines,
10
10
  buildDependencyTreeLines,
11
+ buildTableSummaryLines,
11
12
  printDependencyTree,
12
13
  serializeActivitySummary,
13
14
  } from "./display.js";
@@ -21,9 +22,156 @@ it("print tree test", () => {
21
22
  });
22
23
 
23
24
  it("prints a provenance icon for registry-backed components", async () => {
25
+ const rows = [];
26
+ const bomJson = {
27
+ components: [
28
+ {
29
+ group: "",
30
+ name: "left-pad",
31
+ properties: [
32
+ {
33
+ name: "cdx:npm:provenanceUrl",
34
+ value: "https://registry.npmjs.org/-/npm/v1/attestations/left-pad",
35
+ },
36
+ ],
37
+ type: "library",
38
+ version: "1.3.0",
39
+ },
40
+ {
41
+ group: "",
42
+ name: "lodash",
43
+ properties: [],
44
+ type: "library",
45
+ version: "4.17.21",
46
+ },
47
+ ],
48
+ dependencies: [],
49
+ };
50
+ const { printTable } = await esmock("./display.js", {
51
+ "./table.js": {
52
+ createStream: () => ({
53
+ end() {
54
+ // intentional no-op for stream stub
55
+ },
56
+ write(row) {
57
+ rows.push(row);
58
+ },
59
+ }),
60
+ table: sinon.stub().returns(""),
61
+ },
62
+ "./utils.js": {
63
+ isSecureMode: false,
64
+ safeExistsSync: sinon.stub(),
65
+ toCamel: sinon.stub(),
66
+ },
67
+ });
68
+
69
+ printTable(bomJson, undefined, undefined, "Found 1 trusted component.");
70
+
71
+ assert.strictEqual(rows[1][1], `${REGISTRY_PROVENANCE_ICON} left-pad`);
72
+ assert.strictEqual(rows[2][1], "lodash");
73
+ assert.deepStrictEqual(
74
+ buildTableSummaryLines(bomJson, undefined, "Found 1 trusted component.", 1),
75
+ [
76
+ "Found 1 trusted component.",
77
+ `Legend: ${REGISTRY_PROVENANCE_ICON} = registry provenance or trusted publishing evidence`,
78
+ `${REGISTRY_PROVENANCE_ICON} 1 component(s) include registry provenance or trusted publishing metadata.`,
79
+ ],
80
+ );
81
+ });
82
+
83
+ it("renders HBOM tables with hardware-centric columns and summary lines", async () => {
24
84
  const rows = [];
25
85
  const consoleLogStub = sinon.stub(console, "log");
26
86
  try {
87
+ const bomJson = {
88
+ metadata: {
89
+ component: {
90
+ name: "demo-host",
91
+ type: "device",
92
+ manufacturer: { name: "Example Corp" },
93
+ properties: [
94
+ { name: "cdx:hbom:platform", value: "linux" },
95
+ { name: "cdx:hbom:architecture", value: "amd64" },
96
+ {
97
+ name: "cdx:hbom:identifierPolicy",
98
+ value: "redacted-by-default",
99
+ },
100
+ ],
101
+ },
102
+ },
103
+ components: [
104
+ {
105
+ type: "device",
106
+ name: "eth0",
107
+ manufacturer: { name: "Intel" },
108
+ version: "en0",
109
+ properties: [
110
+ { name: "cdx:hbom:hardwareClass", value: "network-interface" },
111
+ { name: "cdx:hbom:status", value: "active" },
112
+ { name: "cdx:hbom:speedMbps", value: "2500" },
113
+ { name: "cdx:hbom:driver", value: "igc" },
114
+ ],
115
+ },
116
+ {
117
+ type: "device",
118
+ name: "wifi0",
119
+ properties: [
120
+ { name: "cdx:hbom:hardwareClass", value: "wireless-adapter" },
121
+ { name: "cdx:hbom:connected", value: "true" },
122
+ { name: "cdx:hbom:securityMode", value: "wpa3-personal" },
123
+ { name: "cdx:hbom:linkRateMbps", value: "1200" },
124
+ { name: "cdx:hbom:channel", value: "36" },
125
+ ],
126
+ },
127
+ {
128
+ type: "device",
129
+ name: "nvme0",
130
+ manufacturer: { name: "Samsung" },
131
+ properties: [
132
+ { name: "cdx:hbom:hardwareClass", value: "storage" },
133
+ { name: "cdx:hbom:capacity", value: "1 TB" },
134
+ { name: "cdx:hbom:smartStatus", value: "Verified" },
135
+ { name: "cdx:hbom:wearPercentageUsed", value: "2" },
136
+ { name: "cdx:hbom:transport", value: "nvme" },
137
+ ],
138
+ },
139
+ {
140
+ type: "device",
141
+ name: "Internal Battery",
142
+ properties: [
143
+ { name: "cdx:hbom:hardwareClass", value: "power" },
144
+ { name: "cdx:hbom:health", value: "Good" },
145
+ { name: "cdx:hbom:chargePercent", value: "80" },
146
+ { name: "cdx:hbom:maximumCapacity", value: "91%" },
147
+ { name: "cdx:hbom:cycleCount", value: "120" },
148
+ ],
149
+ },
150
+ ],
151
+ properties: [
152
+ { name: "cdx:hbom:collectorProfile", value: "linux-amd64-v1" },
153
+ { name: "cdx:hbom:evidence:commandCount", value: "2" },
154
+ { name: "cdx:hbom:evidence:commandDiagnosticCount", value: "2" },
155
+ {
156
+ name: "cdx:hbom:evidence:commandDiagnostic",
157
+ value: JSON.stringify({
158
+ command: "lsusb",
159
+ installHint:
160
+ "Command not found: install the Linux package providing lsusb (commonly `usbutils`).",
161
+ issue: "missing-command",
162
+ }),
163
+ },
164
+ {
165
+ name: "cdx:hbom:evidence:commandDiagnostic",
166
+ value: JSON.stringify({
167
+ command: "drm_info",
168
+ issue: "permission-denied",
169
+ privilegeHint:
170
+ "Retry with --privileged to allow a non-interactive sudo attempt for permission-sensitive Linux commands.",
171
+ }),
172
+ },
173
+ ],
174
+ };
27
175
  const { printTable } = await esmock("./display.js", {
28
176
  "./table.js": {
29
177
  createStream: () => ({
@@ -37,57 +185,45 @@ it("prints a provenance icon for registry-backed components", async () => {
37
185
  table: sinon.stub().returns(""),
38
186
  },
39
187
  "./utils.js": {
188
+ getRecordedActivities: sinon.stub().returns([]),
189
+ isDryRun: false,
40
190
  isSecureMode: false,
41
191
  safeExistsSync: sinon.stub(),
42
192
  toCamel: sinon.stub(),
43
193
  },
44
194
  });
45
195
 
46
- printTable(
47
- {
48
- components: [
49
- {
50
- group: "",
51
- name: "left-pad",
52
- properties: [
53
- {
54
- name: "cdx:npm:provenanceUrl",
55
- value:
56
- "https://registry.npmjs.org/-/npm/v1/attestations/left-pad",
57
- },
58
- ],
59
- type: "library",
60
- version: "1.3.0",
61
- },
62
- {
63
- group: "",
64
- name: "lodash",
65
- properties: [],
66
- type: "library",
67
- version: "4.17.21",
68
- },
69
- ],
70
- dependencies: [],
71
- },
72
- undefined,
73
- undefined,
74
- "Found 1 trusted component.",
75
- );
196
+ printTable(bomJson);
76
197
 
77
- assert.strictEqual(rows[1][1], `${REGISTRY_PROVENANCE_ICON} left-pad`);
78
- assert.strictEqual(rows[2][1], "lodash");
79
- sinon.assert.calledWithExactly(
80
- consoleLogStub,
81
- "Found 1 trusted component.",
82
- );
83
- sinon.assert.calledWithExactly(
84
- consoleLogStub,
85
- `Legend: ${REGISTRY_PROVENANCE_ICON} = registry provenance or trusted publishing evidence`,
86
- );
87
- sinon.assert.calledWithExactly(
88
- consoleLogStub,
89
- `${REGISTRY_PROVENANCE_ICON} 1 component(s) include registry provenance or trusted publishing metadata.`,
198
+ assert.deepStrictEqual(rows[0], [
199
+ "Hardware Class",
200
+ "Name",
201
+ "Manufacturer / Version",
202
+ "Key Properties",
203
+ "Tags",
204
+ ]);
205
+ assert.strictEqual(rows[1][0], "network-interface");
206
+ assert.strictEqual(rows[1][1], "eth0");
207
+ assert.strictEqual(rows[1][2], "Intel / en0");
208
+ assert.match(rows[1][3], /status=active/u);
209
+ assert.match(rows[1][3], /speedMbps=2500/u);
210
+ assert.match(rows[1][3], /driver=igc/u);
211
+ assert.strictEqual(rows[2][0], "wireless-adapter");
212
+ assert.match(
213
+ rows[2][3],
214
+ /^connected=true, security=wpa3-personal, linkMbps=1200$/u,
90
215
  );
216
+ assert.strictEqual(rows[3][0], "storage");
217
+ assert.match(rows[3][3], /^capacity=1 TB, smart=Verified, wearUsed=2$/u);
218
+ assert.strictEqual(rows[4][0], "power");
219
+ assert.match(rows[4][3], /^health=Good, charge%=80, maxCapacity=91%$/u);
220
+ assert.deepStrictEqual(buildTableSummaryLines(bomJson), [
221
+ "HBOM includes 4 hardware component(s) across 4 hardware class(es)",
222
+ "Top hardware classes: network-interface (1), power (1), storage (1), wireless-adapter (1)",
223
+ "Collector profile: linux-amd64-v1; command evidence: 2; observed files: 0",
224
+ "Collector diagnostics: 2 issue(s); missing commands: 1, permission denied: 1",
225
+ "Permission-sensitive enrichments were skipped or blocked. Re-run with --privileged where policy allows.",
226
+ ]);
91
227
  } finally {
92
228
  consoleLogStub.restore();
93
229
  }