@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.
- package/README.md +64 -22
- package/bin/audit.js +21 -7
- package/bin/cdxgen.js +238 -116
- package/bin/convert.js +28 -13
- package/bin/hbom.js +490 -0
- package/bin/repl.js +580 -29
- package/bin/validate.js +34 -4
- package/bin/verify.js +40 -5
- package/data/README.md +298 -25
- package/data/component-tags.json +6 -0
- package/data/crypto-oid.json +16 -0
- package/data/predictive-audit-allowlist.json +11 -0
- package/data/queries-darwin.json +12 -1
- package/data/queries-win.json +7 -1
- package/data/queries.json +39 -2
- package/data/rules/ai-agent-governance.yaml +16 -0
- package/data/rules/asar-archives.yaml +150 -0
- package/data/rules/chrome-extensions.yaml +8 -0
- package/data/rules/ci-permissions.yaml +42 -18
- package/data/rules/container-risk.yaml +14 -7
- package/data/rules/dependency-sources.yaml +11 -0
- package/data/rules/hbom-compliance.yaml +325 -0
- package/data/rules/hbom-performance.yaml +307 -0
- package/data/rules/hbom-security.yaml +248 -0
- package/data/rules/host-topology.yaml +165 -0
- package/data/rules/mcp-servers.yaml +18 -3
- package/data/rules/obom-runtime.yaml +907 -22
- package/data/rules/package-integrity.yaml +14 -0
- package/data/rules/rootfs-hardening.yaml +179 -0
- package/data/rules/vscode-extensions.yaml +9 -0
- package/lib/audit/index.js +209 -8
- package/lib/audit/index.poku.js +332 -0
- package/lib/audit/reporters.js +222 -0
- package/lib/audit/targets.js +146 -1
- package/lib/audit/targets.poku.js +186 -0
- package/lib/cli/asar.poku.js +328 -0
- package/lib/cli/index.js +506 -88
- package/lib/cli/index.poku.js +1352 -212
- package/lib/evinser/evinser.js +14 -9
- package/lib/helpers/analyzer.js +1406 -29
- package/lib/helpers/analyzer.poku.js +342 -0
- package/lib/helpers/analyzerScope.js +712 -0
- package/lib/helpers/asarutils.js +1556 -0
- package/lib/helpers/asarutils.poku.js +443 -0
- package/lib/helpers/auditCategories.js +12 -0
- package/lib/helpers/auditCategories.poku.js +32 -0
- package/lib/helpers/cbomutils.js +271 -1
- package/lib/helpers/cbomutils.poku.js +248 -5
- package/lib/helpers/display.js +291 -1
- package/lib/helpers/display.poku.js +149 -0
- package/lib/helpers/evidenceUtils.js +58 -0
- package/lib/helpers/evidenceUtils.poku.js +54 -0
- package/lib/helpers/exportUtils.js +9 -0
- package/lib/helpers/gtfobins.js +142 -8
- package/lib/helpers/gtfobins.poku.js +24 -1
- package/lib/helpers/hbom.js +710 -0
- package/lib/helpers/hbom.poku.js +496 -0
- package/lib/helpers/hbomAnalysis.js +268 -0
- package/lib/helpers/hbomAnalysis.poku.js +249 -0
- package/lib/helpers/hbomLoader.js +35 -0
- package/lib/helpers/hostTopology.js +803 -0
- package/lib/helpers/hostTopology.poku.js +363 -0
- package/lib/helpers/inventoryStats.js +69 -0
- package/lib/helpers/inventoryStats.poku.js +86 -0
- package/lib/helpers/lolbas.js +19 -1
- package/lib/helpers/lolbas.poku.js +23 -0
- package/lib/helpers/osqueryTransform.js +47 -0
- package/lib/helpers/osqueryTransform.poku.js +47 -0
- package/lib/helpers/plugins.js +349 -0
- package/lib/helpers/plugins.poku.js +57 -0
- package/lib/helpers/protobom.js +156 -45
- package/lib/helpers/protobom.poku.js +140 -5
- package/lib/helpers/remote/dependency-track.js +36 -3
- package/lib/helpers/remote/dependency-track.poku.js +44 -0
- package/lib/helpers/source.js +24 -0
- package/lib/helpers/source.poku.js +32 -0
- package/lib/helpers/utils.js +1438 -93
- package/lib/helpers/utils.poku.js +846 -4
- package/lib/managers/binary.e2e.poku.js +367 -0
- package/lib/managers/binary.js +2293 -353
- package/lib/managers/binary.poku.js +1699 -1
- package/lib/managers/docker.js +201 -79
- package/lib/managers/docker.poku.js +337 -12
- package/lib/server/server.js +2 -27
- package/lib/stages/postgen/annotator.js +38 -0
- package/lib/stages/postgen/annotator.poku.js +107 -1
- package/lib/stages/postgen/auditBom.js +121 -18
- package/lib/stages/postgen/auditBom.poku.js +1366 -31
- package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
- package/lib/stages/postgen/postgen.js +192 -1
- package/lib/stages/postgen/postgen.poku.js +321 -0
- package/lib/stages/postgen/ruleEngine.js +116 -0
- package/lib/stages/pregen/envAudit.js +14 -3
- package/package.json +23 -21
- package/types/bin/hbom.d.ts +3 -0
- package/types/bin/hbom.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +44 -0
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/reporters.d.ts +16 -0
- package/types/lib/audit/reporters.d.ts.map +1 -1
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +16 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +4 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +33 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/analyzerScope.d.ts +11 -0
- package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
- package/types/lib/helpers/asarutils.d.ts +34 -0
- package/types/lib/helpers/asarutils.d.ts.map +1 -0
- package/types/lib/helpers/auditCategories.d.ts +5 -0
- package/types/lib/helpers/auditCategories.d.ts.map +1 -1
- package/types/lib/helpers/cbomutils.d.ts +3 -2
- package/types/lib/helpers/cbomutils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/evidenceUtils.d.ts +8 -0
- package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +8 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -1
- package/types/lib/helpers/hbom.d.ts +49 -0
- package/types/lib/helpers/hbom.d.ts.map +1 -0
- package/types/lib/helpers/hbomAnalysis.d.ts +62 -0
- package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
- package/types/lib/helpers/hbomLoader.d.ts +7 -0
- package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
- package/types/lib/helpers/hostTopology.d.ts +12 -0
- package/types/lib/helpers/hostTopology.d.ts.map +1 -0
- package/types/lib/helpers/inventoryStats.d.ts +11 -0
- package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -1
- package/types/lib/helpers/osqueryTransform.d.ts +3 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
- package/types/lib/helpers/plugins.d.ts +58 -0
- package/types/lib/helpers/plugins.d.ts.map +1 -0
- package/types/lib/helpers/protobom.d.ts +3 -4
- package/types/lib/helpers/protobom.d.ts.map +1 -1
- package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/helpers/source.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +45 -8
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +5 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +2 -1
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +26 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +2 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/data/spdx-model-v3.0.1.jsonld +0 -15999
package/lib/helpers/display.js
CHANGED
|
@@ -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,
|
|
@@ -83,6 +91,66 @@ export const buildTableSummaryLines = (
|
|
|
83
91
|
const summaryLines = [];
|
|
84
92
|
if (summaryText) {
|
|
85
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
|
+
}
|
|
86
154
|
} else if (!filterTypes) {
|
|
87
155
|
summaryLines.push(
|
|
88
156
|
`BOM includes ${bomJson?.components?.length || 0} components and ${
|
|
@@ -105,6 +173,214 @@ export const buildTableSummaryLines = (
|
|
|
105
173
|
return summaryLines;
|
|
106
174
|
};
|
|
107
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
|
+
|
|
108
384
|
/**
|
|
109
385
|
* Builds legend lines for dependency tree marker icons.
|
|
110
386
|
*
|
|
@@ -230,6 +506,9 @@ export function printTable(
|
|
|
230
506
|
) {
|
|
231
507
|
return printOSTable(bomJson);
|
|
232
508
|
}
|
|
509
|
+
if (isHbomLikeBom(bomJson) && !filterTypes?.includes("cryptographic-asset")) {
|
|
510
|
+
return printHBOMTable(bomJson, filterTypes, highlight, summaryText);
|
|
511
|
+
}
|
|
233
512
|
const config = {
|
|
234
513
|
columnDefault: {
|
|
235
514
|
width: 30,
|
|
@@ -394,6 +673,17 @@ const locationComparator = (a, b) => {
|
|
|
394
673
|
}
|
|
395
674
|
}
|
|
396
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
|
+
}
|
|
397
687
|
return a.localeCompare(b);
|
|
398
688
|
};
|
|
399
689
|
|
|
@@ -433,7 +723,7 @@ export function printOccurrences(bomJson) {
|
|
|
433
723
|
comp.name,
|
|
434
724
|
comp.version || "",
|
|
435
725
|
comp.evidence.occurrences
|
|
436
|
-
.map((
|
|
726
|
+
.map((occurrence) => formatOccurrenceEvidence(occurrence))
|
|
437
727
|
.sort(locationComparator)
|
|
438
728
|
.join("\n"),
|
|
439
729
|
];
|
|
@@ -80,6 +80,155 @@ it("prints a provenance icon for registry-backed components", async () => {
|
|
|
80
80
|
);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
+
it("renders HBOM tables with hardware-centric columns and summary lines", async () => {
|
|
84
|
+
const rows = [];
|
|
85
|
+
const consoleLogStub = sinon.stub(console, "log");
|
|
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
|
+
};
|
|
175
|
+
const { printTable } = await esmock("./display.js", {
|
|
176
|
+
"./table.js": {
|
|
177
|
+
createStream: () => ({
|
|
178
|
+
end() {
|
|
179
|
+
// intentional no-op for stream stub
|
|
180
|
+
},
|
|
181
|
+
write(row) {
|
|
182
|
+
rows.push(row);
|
|
183
|
+
},
|
|
184
|
+
}),
|
|
185
|
+
table: sinon.stub().returns(""),
|
|
186
|
+
},
|
|
187
|
+
"./utils.js": {
|
|
188
|
+
getRecordedActivities: sinon.stub().returns([]),
|
|
189
|
+
isDryRun: false,
|
|
190
|
+
isSecureMode: false,
|
|
191
|
+
safeExistsSync: sinon.stub(),
|
|
192
|
+
toCamel: sinon.stub(),
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
printTable(bomJson);
|
|
197
|
+
|
|
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,
|
|
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
|
+
]);
|
|
227
|
+
} finally {
|
|
228
|
+
consoleLogStub.restore();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
83
232
|
it("displaySelfThreatModel does not assume a default TLP classification", async () => {
|
|
84
233
|
const tableStub = sinon.stub().returns("table-output");
|
|
85
234
|
try {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function createOccurrenceEvidence(location, details = {}) {
|
|
2
|
+
const normalizedLocation = String(location || "").trim();
|
|
3
|
+
if (!normalizedLocation) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
const occurrence = {
|
|
7
|
+
location: normalizedLocation,
|
|
8
|
+
};
|
|
9
|
+
for (const [key, value] of Object.entries(details || {})) {
|
|
10
|
+
if (value !== undefined && value !== null && value !== "") {
|
|
11
|
+
occurrence[key] = value;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return occurrence;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function parseOccurrenceEvidenceLocation(location, details = {}) {
|
|
18
|
+
const normalizedLocation = String(location || "").trim();
|
|
19
|
+
if (!normalizedLocation) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const hashMatch = normalizedLocation.match(/^(.*)#(\d+)$/);
|
|
23
|
+
if (hashMatch) {
|
|
24
|
+
return createOccurrenceEvidence(hashMatch[1], {
|
|
25
|
+
...details,
|
|
26
|
+
line: Number(hashMatch[2]),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const lineOffsetMatch = normalizedLocation.match(/^(.*):(\d+):(\d+)$/);
|
|
30
|
+
if (lineOffsetMatch) {
|
|
31
|
+
return createOccurrenceEvidence(lineOffsetMatch[1], {
|
|
32
|
+
...details,
|
|
33
|
+
line: Number(lineOffsetMatch[2]),
|
|
34
|
+
offset: Number(lineOffsetMatch[3]),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const lineMatch = normalizedLocation.match(/^(.*):(\d+)$/);
|
|
38
|
+
if (lineMatch) {
|
|
39
|
+
return createOccurrenceEvidence(lineMatch[1], {
|
|
40
|
+
...details,
|
|
41
|
+
line: Number(lineMatch[2]),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return createOccurrenceEvidence(normalizedLocation, details);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function formatOccurrenceEvidence(occurrence) {
|
|
48
|
+
if (!occurrence?.location) {
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
if (typeof occurrence.line === "number") {
|
|
52
|
+
if (typeof occurrence.offset === "number") {
|
|
53
|
+
return `${occurrence.location}:${occurrence.line}:${occurrence.offset}`;
|
|
54
|
+
}
|
|
55
|
+
return `${occurrence.location}#${occurrence.line}`;
|
|
56
|
+
}
|
|
57
|
+
return occurrence.location;
|
|
58
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { assert, describe, it } from "poku";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createOccurrenceEvidence,
|
|
5
|
+
formatOccurrenceEvidence,
|
|
6
|
+
parseOccurrenceEvidenceLocation,
|
|
7
|
+
} from "./evidenceUtils.js";
|
|
8
|
+
|
|
9
|
+
describe("evidence utils", () => {
|
|
10
|
+
it("creates occurrence evidence with structured line details", () => {
|
|
11
|
+
assert.deepStrictEqual(
|
|
12
|
+
createOccurrenceEvidence("src/index.js", {
|
|
13
|
+
line: 14,
|
|
14
|
+
offset: 3,
|
|
15
|
+
symbol: "node:crypto.createHash",
|
|
16
|
+
}),
|
|
17
|
+
{
|
|
18
|
+
location: "src/index.js",
|
|
19
|
+
line: 14,
|
|
20
|
+
offset: 3,
|
|
21
|
+
symbol: "node:crypto.createHash",
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("parses hash-style line locations", () => {
|
|
27
|
+
assert.deepStrictEqual(parseOccurrenceEvidenceLocation("src/index.js#27"), {
|
|
28
|
+
location: "src/index.js",
|
|
29
|
+
line: 27,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("parses colon-style line and offset locations", () => {
|
|
34
|
+
assert.deepStrictEqual(
|
|
35
|
+
parseOccurrenceEvidenceLocation("src/index.js:29:7"),
|
|
36
|
+
{
|
|
37
|
+
location: "src/index.js",
|
|
38
|
+
line: 29,
|
|
39
|
+
offset: 7,
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("formats structured occurrence evidence for display", () => {
|
|
45
|
+
assert.strictEqual(
|
|
46
|
+
formatOccurrenceEvidence({
|
|
47
|
+
location: "src/index.js",
|
|
48
|
+
line: 12,
|
|
49
|
+
offset: 1,
|
|
50
|
+
}),
|
|
51
|
+
"src/index.js:12:1",
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -49,9 +49,18 @@ export function deriveSpdxOutputPath(outputPath) {
|
|
|
49
49
|
if (outputPath.endsWith(".spdx.json")) {
|
|
50
50
|
return outputPath;
|
|
51
51
|
}
|
|
52
|
+
if (outputPath.endsWith(".cdx.bin")) {
|
|
53
|
+
return outputPath.replace(/\.cdx\.bin$/u, ".spdx.json");
|
|
54
|
+
}
|
|
52
55
|
if (outputPath.endsWith(".cdx.json")) {
|
|
53
56
|
return outputPath.replace(/\.cdx\.json$/u, ".spdx.json");
|
|
54
57
|
}
|
|
58
|
+
if (outputPath.endsWith(".cdx")) {
|
|
59
|
+
return outputPath.replace(/\.cdx$/u, ".spdx.json");
|
|
60
|
+
}
|
|
61
|
+
if (outputPath.endsWith(".proto")) {
|
|
62
|
+
return outputPath.replace(/\.proto$/u, ".spdx.json");
|
|
63
|
+
}
|
|
55
64
|
if (outputPath.endsWith(".json")) {
|
|
56
65
|
return outputPath.replace(/\.json$/u, ".spdx.json");
|
|
57
66
|
}
|