@cyclonedx/cdxgen 12.3.3 → 12.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/README.md +69 -25
  2. package/bin/audit.js +21 -7
  3. package/bin/cdxgen.js +270 -127
  4. package/bin/convert.js +34 -15
  5. package/bin/hbom.js +495 -0
  6. package/bin/repl.js +592 -37
  7. package/bin/validate.js +31 -4
  8. package/bin/verify.js +18 -5
  9. package/data/README.md +298 -25
  10. package/data/component-tags.json +6 -0
  11. package/data/crypto-oid.json +16 -0
  12. package/data/cyclonedx-2.0-bundled.schema.json +7182 -0
  13. package/data/predictive-audit-allowlist.json +11 -0
  14. package/data/queries-darwin.json +12 -1
  15. package/data/queries-win.json +7 -1
  16. package/data/queries.json +39 -2
  17. package/data/rules/ai-agent-governance.yaml +16 -0
  18. package/data/rules/asar-archives.yaml +150 -0
  19. package/data/rules/chrome-extensions.yaml +8 -0
  20. package/data/rules/ci-permissions.yaml +42 -18
  21. package/data/rules/container-risk.yaml +14 -7
  22. package/data/rules/dependency-sources.yaml +11 -0
  23. package/data/rules/hbom-compliance.yaml +325 -0
  24. package/data/rules/hbom-performance.yaml +307 -0
  25. package/data/rules/hbom-security.yaml +248 -0
  26. package/data/rules/host-topology.yaml +165 -0
  27. package/data/rules/mcp-servers.yaml +18 -3
  28. package/data/rules/obom-runtime.yaml +907 -22
  29. package/data/rules/package-integrity.yaml +14 -0
  30. package/data/rules/rootfs-hardening.yaml +179 -0
  31. package/data/rules/vscode-extensions.yaml +9 -0
  32. package/lib/audit/index.js +210 -8
  33. package/lib/audit/index.poku.js +332 -0
  34. package/lib/audit/reporters.js +222 -0
  35. package/lib/audit/targets.js +146 -1
  36. package/lib/audit/targets.poku.js +186 -0
  37. package/lib/cli/asar.poku.js +328 -0
  38. package/lib/cli/index.js +527 -99
  39. package/lib/cli/index.poku.js +1469 -212
  40. package/lib/evinser/evinser.js +14 -9
  41. package/lib/helpers/analyzer.js +1406 -29
  42. package/lib/helpers/analyzer.poku.js +342 -0
  43. package/lib/helpers/analyzerScope.js +712 -0
  44. package/lib/helpers/asarutils.js +1556 -0
  45. package/lib/helpers/asarutils.poku.js +443 -0
  46. package/lib/helpers/auditCategories.js +12 -0
  47. package/lib/helpers/auditCategories.poku.js +32 -0
  48. package/lib/helpers/bomUtils.js +155 -1
  49. package/lib/helpers/bomUtils.poku.js +79 -1
  50. package/lib/helpers/cbomutils.js +271 -1
  51. package/lib/helpers/cbomutils.poku.js +248 -5
  52. package/lib/helpers/display.js +291 -1
  53. package/lib/helpers/display.poku.js +149 -0
  54. package/lib/helpers/evidenceUtils.js +58 -0
  55. package/lib/helpers/evidenceUtils.poku.js +54 -0
  56. package/lib/helpers/exportUtils.js +9 -0
  57. package/lib/helpers/gtfobins.js +142 -8
  58. package/lib/helpers/gtfobins.poku.js +24 -1
  59. package/lib/helpers/hbom.js +710 -0
  60. package/lib/helpers/hbom.poku.js +496 -0
  61. package/lib/helpers/hbomAnalysis.js +268 -0
  62. package/lib/helpers/hbomAnalysis.poku.js +249 -0
  63. package/lib/helpers/hbomLoader.js +35 -0
  64. package/lib/helpers/hostTopology.js +803 -0
  65. package/lib/helpers/hostTopology.poku.js +363 -0
  66. package/lib/helpers/inventoryStats.js +69 -0
  67. package/lib/helpers/inventoryStats.poku.js +86 -0
  68. package/lib/helpers/lolbas.js +19 -1
  69. package/lib/helpers/lolbas.poku.js +23 -0
  70. package/lib/helpers/osqueryTransform.js +47 -0
  71. package/lib/helpers/osqueryTransform.poku.js +47 -0
  72. package/lib/helpers/plugins.js +350 -0
  73. package/lib/helpers/plugins.poku.js +57 -0
  74. package/lib/helpers/protobom.js +209 -45
  75. package/lib/helpers/protobom.poku.js +183 -5
  76. package/lib/helpers/protobomLoader.js +43 -0
  77. package/lib/helpers/protobomLoader.poku.js +31 -0
  78. package/lib/helpers/remote/dependency-track.js +36 -3
  79. package/lib/helpers/remote/dependency-track.poku.js +44 -0
  80. package/lib/helpers/source.js +24 -0
  81. package/lib/helpers/source.poku.js +32 -0
  82. package/lib/helpers/utils.js +1438 -93
  83. package/lib/helpers/utils.poku.js +846 -4
  84. package/lib/managers/binary.e2e.poku.js +367 -0
  85. package/lib/managers/binary.js +2293 -353
  86. package/lib/managers/binary.poku.js +1699 -1
  87. package/lib/managers/docker.js +201 -79
  88. package/lib/managers/docker.poku.js +337 -12
  89. package/lib/server/server.js +4 -28
  90. package/lib/stages/postgen/annotator.js +38 -0
  91. package/lib/stages/postgen/annotator.poku.js +107 -1
  92. package/lib/stages/postgen/auditBom.js +121 -18
  93. package/lib/stages/postgen/auditBom.poku.js +1366 -31
  94. package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
  95. package/lib/stages/postgen/postgen.js +406 -8
  96. package/lib/stages/postgen/postgen.poku.js +484 -0
  97. package/lib/stages/postgen/ruleEngine.js +116 -0
  98. package/lib/stages/pregen/envAudit.js +14 -3
  99. package/lib/validator/bomValidator.js +90 -38
  100. package/lib/validator/bomValidator.poku.js +90 -0
  101. package/lib/validator/complianceRules.js +4 -2
  102. package/lib/validator/index.poku.js +14 -0
  103. package/package.json +23 -21
  104. package/types/bin/hbom.d.ts +3 -0
  105. package/types/bin/hbom.d.ts.map +1 -0
  106. package/types/bin/repl.d.ts +1 -1
  107. package/types/bin/repl.d.ts.map +1 -1
  108. package/types/lib/audit/index.d.ts +44 -0
  109. package/types/lib/audit/index.d.ts.map +1 -1
  110. package/types/lib/audit/reporters.d.ts +16 -0
  111. package/types/lib/audit/reporters.d.ts.map +1 -1
  112. package/types/lib/audit/targets.d.ts.map +1 -1
  113. package/types/lib/cli/index.d.ts +16 -0
  114. package/types/lib/cli/index.d.ts.map +1 -1
  115. package/types/lib/evinser/evinser.d.ts +4 -0
  116. package/types/lib/evinser/evinser.d.ts.map +1 -1
  117. package/types/lib/helpers/analyzer.d.ts +33 -0
  118. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  119. package/types/lib/helpers/analyzerScope.d.ts +11 -0
  120. package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
  121. package/types/lib/helpers/asarutils.d.ts +34 -0
  122. package/types/lib/helpers/asarutils.d.ts.map +1 -0
  123. package/types/lib/helpers/auditCategories.d.ts +5 -0
  124. package/types/lib/helpers/auditCategories.d.ts.map +1 -1
  125. package/types/lib/helpers/bomUtils.d.ts +10 -0
  126. package/types/lib/helpers/bomUtils.d.ts.map +1 -1
  127. package/types/lib/helpers/cbomutils.d.ts +3 -2
  128. package/types/lib/helpers/cbomutils.d.ts.map +1 -1
  129. package/types/lib/helpers/display.d.ts.map +1 -1
  130. package/types/lib/helpers/evidenceUtils.d.ts +8 -0
  131. package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
  132. package/types/lib/helpers/exportUtils.d.ts.map +1 -1
  133. package/types/lib/helpers/gtfobins.d.ts +8 -0
  134. package/types/lib/helpers/gtfobins.d.ts.map +1 -1
  135. package/types/lib/helpers/hbom.d.ts +49 -0
  136. package/types/lib/helpers/hbom.d.ts.map +1 -0
  137. package/types/lib/helpers/hbomAnalysis.d.ts +76 -0
  138. package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
  139. package/types/lib/helpers/hbomLoader.d.ts +7 -0
  140. package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
  141. package/types/lib/helpers/hostTopology.d.ts +12 -0
  142. package/types/lib/helpers/hostTopology.d.ts.map +1 -0
  143. package/types/lib/helpers/inventoryStats.d.ts +11 -0
  144. package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
  145. package/types/lib/helpers/lolbas.d.ts.map +1 -1
  146. package/types/lib/helpers/osqueryTransform.d.ts +3 -0
  147. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
  148. package/types/lib/helpers/plugins.d.ts +58 -0
  149. package/types/lib/helpers/plugins.d.ts.map +1 -0
  150. package/types/lib/helpers/protobom.d.ts +5 -4
  151. package/types/lib/helpers/protobom.d.ts.map +1 -1
  152. package/types/lib/helpers/protobomLoader.d.ts +17 -0
  153. package/types/lib/helpers/protobomLoader.d.ts.map +1 -0
  154. package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
  155. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  156. package/types/lib/helpers/source.d.ts.map +1 -1
  157. package/types/lib/helpers/utils.d.ts +45 -8
  158. package/types/lib/helpers/utils.d.ts.map +1 -1
  159. package/types/lib/managers/binary.d.ts +5 -0
  160. package/types/lib/managers/binary.d.ts.map +1 -1
  161. package/types/lib/managers/docker.d.ts.map +1 -1
  162. package/types/lib/server/server.d.ts +2 -1
  163. package/types/lib/server/server.d.ts.map +1 -1
  164. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  165. package/types/lib/stages/postgen/auditBom.d.ts +26 -1
  166. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  167. package/types/lib/stages/postgen/postgen.d.ts +2 -1
  168. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  169. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  170. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  171. package/types/lib/third-party/arborist/lib/node.d.ts +23 -0
  172. package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -1
  173. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  174. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  175. package/data/spdx-model-v3.0.1.jsonld +0 -15999
@@ -0,0 +1,363 @@
1
+ import { assert, describe, it } from "poku";
2
+
3
+ import {
4
+ applyHostInventoryTopology,
5
+ getHostViewSummary,
6
+ isMergedHostViewBom,
7
+ mergeHostInventoryBoms,
8
+ } from "./hostTopology.js";
9
+
10
+ function makeProperty(name, value) {
11
+ return { name, value };
12
+ }
13
+
14
+ function makeHbomComponent(name, hardwareClass, properties = []) {
15
+ return {
16
+ type: "device",
17
+ name,
18
+ properties: [
19
+ makeProperty("cdx:hbom:hardwareClass", hardwareClass),
20
+ ...properties,
21
+ ],
22
+ };
23
+ }
24
+
25
+ function makeOsqueryComponent(
26
+ name,
27
+ queryCategory,
28
+ properties = [],
29
+ extra = {},
30
+ ) {
31
+ return {
32
+ type: extra.type || "data",
33
+ name,
34
+ version: extra.version || "",
35
+ "bom-ref": extra.bomRef || `osquery:${queryCategory}:${name}`,
36
+ properties: [
37
+ makeProperty("cdx:osquery:category", queryCategory),
38
+ ...properties,
39
+ ],
40
+ };
41
+ }
42
+
43
+ describe("host topology helpers", () => {
44
+ it("adds strict HBOM topology dependencies for standalone hardware BOMs", () => {
45
+ const bomJson = applyHostInventoryTopology({
46
+ bomFormat: "CycloneDX",
47
+ specVersion: "1.7",
48
+ metadata: {
49
+ component: {
50
+ name: "host-a",
51
+ type: "device",
52
+ properties: [
53
+ makeProperty("cdx:hbom:platform", "linux"),
54
+ makeProperty("cdx:hbom:architecture", "amd64"),
55
+ ],
56
+ },
57
+ },
58
+ components: [
59
+ makeHbomComponent("wlp2s0", "network-interface", [
60
+ makeProperty("cdx:hbom:driver", "iwlwifi"),
61
+ ]),
62
+ makeHbomComponent("CT1000P3PSSD8", "storage"),
63
+ ],
64
+ dependencies: [],
65
+ });
66
+
67
+ assert.ok(bomJson.metadata.component["bom-ref"]);
68
+ assert.ok(bomJson.components.every((component) => component["bom-ref"]));
69
+ assert.strictEqual(getHostViewSummary(bomJson).mode, "hbom-topology");
70
+ assert.strictEqual(getHostViewSummary(bomJson).runtimeAnchorCount, 0);
71
+ assert.strictEqual(
72
+ bomJson.dependencies.find(
73
+ (dependency) =>
74
+ dependency.ref === bomJson.metadata.component["bom-ref"],
75
+ )?.dependsOn?.length,
76
+ 2,
77
+ );
78
+ });
79
+
80
+ it("merges OBOM runtime data and links hardware to runtime components without guesswork", () => {
81
+ const hbomJson = {
82
+ bomFormat: "CycloneDX",
83
+ specVersion: "1.7",
84
+ metadata: {
85
+ component: {
86
+ name: "host-b",
87
+ type: "device",
88
+ properties: [
89
+ makeProperty("cdx:hbom:platform", "linux"),
90
+ makeProperty("cdx:hbom:architecture", "amd64"),
91
+ makeProperty("cdx:hbom:secureBootDbSha1", "db-cert-1"),
92
+ ],
93
+ },
94
+ },
95
+ components: [
96
+ makeHbomComponent("enp1s0", "network-interface", [
97
+ makeProperty("cdx:hbom:driver", "r8169"),
98
+ makeProperty("cdx:hbom:speedMbps", "100"),
99
+ ]),
100
+ makeHbomComponent("CT1000P3PSSD8", "storage", [
101
+ makeProperty("cdx:hbom:deviceNode", "/dev/nvme0n1"),
102
+ makeProperty("cdx:hbom:mountPoint", "/home"),
103
+ ]),
104
+ ],
105
+ dependencies: [],
106
+ properties: [],
107
+ };
108
+ const obomData = {
109
+ parentComponent: {
110
+ type: "operating-system",
111
+ name: "Ubuntu",
112
+ version: "24.04",
113
+ "bom-ref": "pkg:generic/ubuntu@24.04",
114
+ },
115
+ bomJson: {
116
+ bomFormat: "CycloneDX",
117
+ specVersion: "1.7",
118
+ metadata: {
119
+ tools: {
120
+ components: [
121
+ {
122
+ type: "application",
123
+ name: "osquery",
124
+ version: "5.12.0",
125
+ "bom-ref": "pkg:generic/osquery@5.12.0",
126
+ },
127
+ ],
128
+ },
129
+ },
130
+ components: [
131
+ makeOsqueryComponent("192.168.1.23", "interface_addresses", [
132
+ makeProperty("interface", "enp1s0"),
133
+ makeProperty("address", "192.168.1.23"),
134
+ ]),
135
+ makeOsqueryComponent("r8169", "kernel_modules", [], {
136
+ type: "data",
137
+ }),
138
+ makeOsqueryComponent("iwlwifi", "kernel_modules", [], {
139
+ type: "data",
140
+ }),
141
+ makeOsqueryComponent("/home", "mount_hardening", [
142
+ makeProperty("device", "/dev/nvme0n1"),
143
+ makeProperty("path", "/home"),
144
+ makeProperty("flags", "rw,nosuid,nodev"),
145
+ ]),
146
+ makeOsqueryComponent("nvme-home", "logical_drives", [
147
+ makeProperty("device_id", "/dev/nvme0n1"),
148
+ makeProperty("description", "home"),
149
+ ]),
150
+ makeOsqueryComponent("db-cert", "secureboot_certificates", [
151
+ makeProperty("sha1", "db-cert-1"),
152
+ makeProperty("subject", "CN=Platform DB"),
153
+ ]),
154
+ ],
155
+ dependencies: [
156
+ {
157
+ ref: "pkg:generic/ubuntu@24.04",
158
+ dependsOn: [
159
+ "osquery:interface_addresses:192.168.1.23",
160
+ "osquery:kernel_modules:r8169",
161
+ ],
162
+ },
163
+ ],
164
+ },
165
+ };
166
+
167
+ const mergedBomJson = mergeHostInventoryBoms(hbomJson, obomData);
168
+ const networkComponent = mergedBomJson.components.find(
169
+ (component) => component.name === "enp1s0",
170
+ );
171
+ const networkDependency = mergedBomJson.dependencies.find(
172
+ (dependency) => dependency.ref === networkComponent["bom-ref"],
173
+ );
174
+ const storageComponent = mergedBomJson.components.find(
175
+ (component) => component.name === "CT1000P3PSSD8",
176
+ );
177
+ const storageDependency = mergedBomJson.dependencies.find(
178
+ (dependency) => dependency.ref === storageComponent["bom-ref"],
179
+ );
180
+ const hostDependency = mergedBomJson.dependencies.find(
181
+ (dependency) =>
182
+ dependency.ref === mergedBomJson.metadata.component["bom-ref"],
183
+ );
184
+ const summary = getHostViewSummary(mergedBomJson);
185
+
186
+ assert.strictEqual(isMergedHostViewBom(mergedBomJson), true);
187
+ assert.ok(networkComponent["bom-ref"]);
188
+ assert.ok(networkDependency);
189
+ assert.deepStrictEqual(networkDependency.dependsOn, [
190
+ "osquery:interface_addresses:192.168.1.23",
191
+ "osquery:kernel_modules:r8169",
192
+ ]);
193
+ assert.deepStrictEqual(storageDependency.dependsOn, [
194
+ "osquery:logical_drives:nvme-home",
195
+ "osquery:mount_hardening:/home",
196
+ ]);
197
+ assert.ok(
198
+ hostDependency.dependsOn.includes(
199
+ "osquery:secureboot_certificates:db-cert",
200
+ ),
201
+ );
202
+ assert.strictEqual(
203
+ networkComponent.properties.find(
204
+ (property) =>
205
+ property.name === "cdx:hostview:interface_addresses:count",
206
+ )?.value,
207
+ "1",
208
+ );
209
+ assert.strictEqual(
210
+ networkComponent.properties.find(
211
+ (property) => property.name === "cdx:hostview:kernel_modules:count",
212
+ )?.value,
213
+ "1",
214
+ );
215
+ assert.strictEqual(
216
+ storageComponent.properties.find(
217
+ (property) => property.name === "cdx:hostview:mount_hardening:count",
218
+ )?.value,
219
+ "1",
220
+ );
221
+ assert.strictEqual(
222
+ storageComponent.properties.find(
223
+ (property) => property.name === "cdx:hostview:runtime-storage:count",
224
+ )?.value,
225
+ "1",
226
+ );
227
+ assert.strictEqual(
228
+ mergedBomJson.metadata.component.properties.find(
229
+ (property) =>
230
+ property.name === "cdx:hostview:secureboot_certificates:count",
231
+ )?.value,
232
+ "1",
233
+ );
234
+ assert.strictEqual(summary.mode, "hbom-obom-merged");
235
+ assert.strictEqual(summary.runtimeAnchorCount, 1);
236
+ assert.strictEqual(summary.runtimeComponentCount, 6);
237
+ assert.strictEqual(summary.topologyLinkCount, 5);
238
+ assert.deepStrictEqual(summary.linkedRuntimeCategories, [
239
+ "interface_addresses",
240
+ "kernel_modules",
241
+ "mount_hardening",
242
+ "runtime-storage",
243
+ "secureboot_certificates",
244
+ ]);
245
+ });
246
+
247
+ it("links host trust metadata to secure boot certificates only when exact identifiers match", () => {
248
+ const mergedBomJson = mergeHostInventoryBoms(
249
+ {
250
+ bomFormat: "CycloneDX",
251
+ specVersion: "1.7",
252
+ metadata: {
253
+ component: {
254
+ name: "host-trust",
255
+ type: "device",
256
+ properties: [
257
+ makeProperty("cdx:hbom:platform", "linux"),
258
+ makeProperty(
259
+ "cdx:hbom:secureBootSubjectKeyId",
260
+ "trust-anchor-42",
261
+ ),
262
+ ],
263
+ },
264
+ },
265
+ components: [],
266
+ dependencies: [],
267
+ properties: [],
268
+ },
269
+ {
270
+ bomJson: {
271
+ bomFormat: "CycloneDX",
272
+ specVersion: "1.7",
273
+ metadata: {},
274
+ components: [
275
+ makeOsqueryComponent("db-anchor", "secureboot_certificates", [
276
+ makeProperty("subject_key_id", "trust-anchor-42"),
277
+ makeProperty("issuer", "CN=Firmware CA"),
278
+ ]),
279
+ ],
280
+ dependencies: [],
281
+ properties: [],
282
+ },
283
+ },
284
+ );
285
+
286
+ const hostDependency = mergedBomJson.dependencies.find(
287
+ (dependency) =>
288
+ dependency.ref === mergedBomJson.metadata.component["bom-ref"],
289
+ );
290
+ const summary = getHostViewSummary(mergedBomJson);
291
+
292
+ assert.ok(hostDependency);
293
+ assert.deepStrictEqual(hostDependency.dependsOn, [
294
+ "osquery:secureboot_certificates:db-anchor",
295
+ ]);
296
+ assert.strictEqual(
297
+ mergedBomJson.metadata.component.properties.find(
298
+ (property) =>
299
+ property.name === "cdx:hostview:secureboot_certificates:count",
300
+ )?.value,
301
+ "1",
302
+ );
303
+ assert.ok(
304
+ summary.linkedRuntimeCategories.includes("secureboot_certificates"),
305
+ );
306
+ assert.strictEqual(summary.topologyLinkCount, 1);
307
+ });
308
+
309
+ it("does not create runtime links when there is no exact identifier match", () => {
310
+ const hbomJson = {
311
+ bomFormat: "CycloneDX",
312
+ specVersion: "1.7",
313
+ metadata: {
314
+ component: {
315
+ name: "host-c",
316
+ type: "device",
317
+ properties: [makeProperty("cdx:hbom:platform", "linux")],
318
+ },
319
+ },
320
+ components: [
321
+ makeHbomComponent("enp1s0", "network-interface", [
322
+ makeProperty("cdx:hbom:driver", "r8169"),
323
+ ]),
324
+ ],
325
+ dependencies: [],
326
+ properties: [],
327
+ };
328
+ const obomData = {
329
+ bomJson: {
330
+ metadata: {},
331
+ components: [
332
+ makeOsqueryComponent("192.168.1.24", "interface_addresses", [
333
+ makeProperty("interface", "eth9"),
334
+ ]),
335
+ makeOsqueryComponent("iwlwifi", "kernel_modules"),
336
+ ],
337
+ dependencies: [],
338
+ properties: [],
339
+ },
340
+ };
341
+
342
+ const mergedBomJson = mergeHostInventoryBoms(hbomJson, obomData);
343
+ const networkComponent = mergedBomJson.components.find(
344
+ (component) => component.name === "enp1s0",
345
+ );
346
+ const summary = getHostViewSummary(mergedBomJson);
347
+
348
+ assert.strictEqual(
349
+ mergedBomJson.dependencies.some(
350
+ (dependency) => dependency.ref === networkComponent["bom-ref"],
351
+ ),
352
+ false,
353
+ );
354
+ assert.strictEqual(
355
+ networkComponent.properties.some((property) =>
356
+ property.name.startsWith("cdx:hostview:"),
357
+ ),
358
+ false,
359
+ );
360
+ assert.strictEqual(summary.topologyLinkCount, 0);
361
+ assert.strictEqual(summary.linkedHardwareComponentCount, 0);
362
+ });
363
+ });
@@ -0,0 +1,69 @@
1
+ function toProperties(propertiesOrObject) {
2
+ if (Array.isArray(propertiesOrObject)) {
3
+ return propertiesOrObject;
4
+ }
5
+ if (Array.isArray(propertiesOrObject?.properties)) {
6
+ return propertiesOrObject.properties;
7
+ }
8
+ return [];
9
+ }
10
+
11
+ export function getPropertyValue(propertiesOrObject, propertyName) {
12
+ return toProperties(propertiesOrObject).find(
13
+ (property) => property?.name === propertyName,
14
+ )?.value;
15
+ }
16
+
17
+ function hasPropertyValue(propertiesOrObject, propertyName, valuePredicate) {
18
+ const propertyValue = getPropertyValue(propertiesOrObject, propertyName);
19
+ if (typeof valuePredicate === "function") {
20
+ return valuePredicate(propertyValue);
21
+ }
22
+ return propertyValue === valuePredicate;
23
+ }
24
+
25
+ function isFileComponent(component) {
26
+ return component?.type === "file";
27
+ }
28
+
29
+ function isCryptographicAssetComponent(component) {
30
+ return component?.type === "cryptographic-asset";
31
+ }
32
+
33
+ export function getUnpackagedExecutableComponents(components = []) {
34
+ return (components || []).filter(
35
+ (component) =>
36
+ isFileComponent(component) &&
37
+ hasPropertyValue(component, "internal:is_executable", "true"),
38
+ );
39
+ }
40
+
41
+ export function getUnpackagedSharedLibraryComponents(components = []) {
42
+ return (components || []).filter(
43
+ (component) =>
44
+ isFileComponent(component) &&
45
+ hasPropertyValue(component, "internal:is_shared_library", "true"),
46
+ );
47
+ }
48
+
49
+ export function getSourceDerivedCryptoComponents(components = []) {
50
+ return (components || []).filter(
51
+ (component) =>
52
+ isCryptographicAssetComponent(component) &&
53
+ hasPropertyValue(component, "cdx:crypto:sourceType", (propertyValue) =>
54
+ propertyValue?.startsWith("js-ast:"),
55
+ ),
56
+ );
57
+ }
58
+
59
+ export function getContainerFileInventoryStats(components = []) {
60
+ const unpackagedExecutables = getUnpackagedExecutableComponents(components);
61
+ const unpackagedSharedLibraries =
62
+ getUnpackagedSharedLibraryComponents(components);
63
+ return {
64
+ unpackagedExecutables,
65
+ unpackagedSharedLibraries,
66
+ unpackagedExecutableCount: unpackagedExecutables.length,
67
+ unpackagedSharedLibraryCount: unpackagedSharedLibraries.length,
68
+ };
69
+ }
@@ -0,0 +1,86 @@
1
+ import { assert, describe, it } from "poku";
2
+
3
+ import {
4
+ getContainerFileInventoryStats,
5
+ getPropertyValue,
6
+ getSourceDerivedCryptoComponents,
7
+ getUnpackagedExecutableComponents,
8
+ getUnpackagedSharedLibraryComponents,
9
+ } from "./inventoryStats.js";
10
+
11
+ describe("inventoryStats helpers", () => {
12
+ const components = [
13
+ {
14
+ type: "file",
15
+ name: "demo",
16
+ properties: [{ name: "internal:is_executable", value: "true" }],
17
+ },
18
+ {
19
+ type: "file",
20
+ name: "libdemo.so",
21
+ properties: [{ name: "internal:is_shared_library", value: "true" }],
22
+ },
23
+ {
24
+ type: "file",
25
+ name: "README.md",
26
+ properties: [{ name: "internal:is_executable", value: "false" }],
27
+ },
28
+ {
29
+ type: "cryptographic-asset",
30
+ name: "sha-512",
31
+ properties: [
32
+ { name: "cdx:crypto:sourceType", value: "js-ast:node:crypto" },
33
+ ],
34
+ },
35
+ {
36
+ type: "cryptographic-asset",
37
+ name: "cert.pem",
38
+ properties: [{ name: "cdx:crypto:sourceType", value: "certificate" }],
39
+ },
40
+ ];
41
+
42
+ it("getPropertyValue() reads properties from arrays and component objects", () => {
43
+ assert.strictEqual(
44
+ getPropertyValue(components[0], "internal:is_executable"),
45
+ "true",
46
+ );
47
+ assert.strictEqual(
48
+ getPropertyValue(components[0].properties, "internal:is_executable"),
49
+ "true",
50
+ );
51
+ assert.strictEqual(getPropertyValue({}, "missing"), undefined);
52
+ });
53
+
54
+ it("filters unpackaged executable and shared-library file components", () => {
55
+ assert.deepStrictEqual(
56
+ getUnpackagedExecutableComponents(components).map(
57
+ (component) => component.name,
58
+ ),
59
+ ["demo"],
60
+ );
61
+ assert.deepStrictEqual(
62
+ getUnpackagedSharedLibraryComponents(components).map(
63
+ (component) => component.name,
64
+ ),
65
+ ["libdemo.so"],
66
+ );
67
+ });
68
+
69
+ it("filters source-derived crypto components", () => {
70
+ assert.deepStrictEqual(
71
+ getSourceDerivedCryptoComponents(components).map(
72
+ (component) => component.name,
73
+ ),
74
+ ["sha-512"],
75
+ );
76
+ });
77
+
78
+ it("summarizes unpackaged container file inventory counts", () => {
79
+ assert.deepStrictEqual(getContainerFileInventoryStats(components), {
80
+ unpackagedExecutables: [components[0]],
81
+ unpackagedSharedLibraries: [components[1]],
82
+ unpackagedExecutableCount: 1,
83
+ unpackagedSharedLibraryCount: 1,
84
+ });
85
+ });
86
+ });
@@ -42,6 +42,22 @@ const MATCH_FIELDS = [
42
42
  "program",
43
43
  "source",
44
44
  ];
45
+ const CATEGORY_MATCH_FIELDS = {
46
+ appcompat_shims: ["executable", "path", "sdb_path"],
47
+ listening_ports: ["cmdline", "name", "path"],
48
+ process_open_handles_snapshot: ["cmdline", "name", "path"],
49
+ processes: ["cmdline", "name", "path"],
50
+ scheduled_tasks: ["action"],
51
+ services_snapshot: ["module_path", "path"],
52
+ startup_items: ["path"],
53
+ windows_run_keys: ["description", "name"],
54
+ wmi_cli_event_consumers: ["command_line_template", "command_line", "name"],
55
+ wmi_cli_event_consumers_snapshot: [
56
+ "command_line_template",
57
+ "command_line",
58
+ "name",
59
+ ],
60
+ };
45
61
  const STANDALONE_COMMAND_PATTERN =
46
62
  /\b(bitsadmin|certutil|cmd|cmdkey|cmstp|cscript|ftp|installutil|msbuild|mshta|msiexec|odbcconf|powershell|pwsh|regsvr32|rundll32|wmic|wscript)\b/gi;
47
63
  const WINDOWS_EXECUTABLE_PATTERN =
@@ -173,7 +189,9 @@ export function getLolbasMetadata(candidate) {
173
189
  */
174
190
  export function createLolbasProperties(queryCategory, row) {
175
191
  const matches = new Map();
176
- for (const field of MATCH_FIELDS) {
192
+ const matchFieldsForCategory =
193
+ CATEGORY_MATCH_FIELDS[queryCategory] || MATCH_FIELDS;
194
+ for (const field of matchFieldsForCategory) {
177
195
  const fieldValue = row?.[field];
178
196
  if (!fieldValue) {
179
197
  continue;
@@ -36,4 +36,27 @@ describe("lolbas helpers", () => {
36
36
  assert.ok(propertyMap["cdx:lolbas:attackTechniques"].includes("T1059.001"));
37
37
  assert.ok(propertyMap["cdx:lolbas:matchFields"].includes("description"));
38
38
  });
39
+
40
+ it("ignores benign Windows service descriptions that only mention LOLBAS tools", () => {
41
+ const properties = createLolbasProperties("services_snapshot", {
42
+ description:
43
+ "This service can be queried via Powershell and configured with winrm.cmd.",
44
+ display_name: "Windows Remote Management",
45
+ module_path: "C:\\Windows\\System32\\WsmSvc.dll",
46
+ path: "C:\\Windows\\System32\\svchost.exe -k NetworkService -p",
47
+ });
48
+ assert.deepStrictEqual(properties, []);
49
+ });
50
+
51
+ it("keeps Windows service path matches when the executable field itself is LOLBAS", () => {
52
+ const properties = createLolbasProperties("services_snapshot", {
53
+ path: "C:\\Users\\Public\\evil\\powershell.exe -enc AAAA",
54
+ });
55
+ const propertyMap = Object.fromEntries(
56
+ properties.map((property) => [property.name, property.value]),
57
+ );
58
+ assert.strictEqual(propertyMap["cdx:lolbas:matched"], "true");
59
+ assert.ok(propertyMap["cdx:lolbas:names"].includes("powershell.exe"));
60
+ assert.ok(propertyMap["cdx:lolbas:matchFields"].includes("path"));
61
+ });
39
62
  });
@@ -65,6 +65,53 @@ export function sanitizeOsQueryIdentity(value) {
65
65
  .replace(/[}]$/g, "");
66
66
  }
67
67
 
68
+ export function sanitizeOsQueryBomRefValue(value, fallback = "unknown") {
69
+ const normalizedValue = String(value || "")
70
+ .replace(/[\r\n\t]+/g, " ")
71
+ .replace(/\s+/g, " ")
72
+ .trim();
73
+ if (!normalizedValue || normalizedValue === "null") {
74
+ return fallback;
75
+ }
76
+ return normalizedValue.replace(/[:@#\[\]=]/g, "-");
77
+ }
78
+
79
+ export function createOsQueryFallbackBomRef(
80
+ queryCategory,
81
+ componentType,
82
+ name,
83
+ version,
84
+ identityField,
85
+ identityValue,
86
+ ) {
87
+ const categoryRef = sanitizeOsQueryBomRefValue(queryCategory, "component");
88
+ const componentTypeRef = sanitizeOsQueryBomRefValue(
89
+ componentType,
90
+ "component",
91
+ );
92
+ const nameRef = sanitizeOsQueryBomRefValue(
93
+ name || queryCategory,
94
+ "component",
95
+ );
96
+ const versionRef = sanitizeOsQueryBomRefValue(version, "unknown");
97
+ const baseBomRef = `osquery:${categoryRef}:${componentTypeRef}:${nameRef}@${versionRef}`;
98
+ if (!identityField || !identityValue) {
99
+ return baseBomRef;
100
+ }
101
+ const identityFieldRef = sanitizeOsQueryBomRefValue(
102
+ identityField,
103
+ "identity",
104
+ );
105
+ const identityValueRef = sanitizeOsQueryBomRefValue(identityValue, "unknown");
106
+ return `${baseBomRef}[${identityFieldRef}=${identityValueRef}]`;
107
+ }
108
+
109
+ export function shouldCreateOsQueryPurl(componentType) {
110
+ return !["cryptographic-asset", "data", "device", "information"].includes(
111
+ componentType || "",
112
+ );
113
+ }
114
+
68
115
  export function createOsQueryPurl(
69
116
  purlType,
70
117
  group,
@@ -1,12 +1,15 @@
1
1
  import { assert, describe, it } from "poku";
2
2
 
3
3
  import {
4
+ createOsQueryFallbackBomRef,
4
5
  createOsQueryPurl,
5
6
  deriveOsQueryDescription,
6
7
  deriveOsQueryName,
7
8
  deriveOsQueryPublisher,
8
9
  deriveOsQueryVersion,
10
+ sanitizeOsQueryBomRefValue,
9
11
  sanitizeOsQueryIdentity,
12
+ shouldCreateOsQueryPurl,
10
13
  } from "./osqueryTransform.js";
11
14
 
12
15
  describe("osqueryTransform helpers", () => {
@@ -46,4 +49,48 @@ describe("osqueryTransform helpers", () => {
46
49
  assert.ok(purl.startsWith("pkg:swid/microsoft/"));
47
50
  assert.ok(purl.includes("@22H2"));
48
51
  });
52
+
53
+ it("creates readable fallback bom-ref strings for non-package osquery rows", () => {
54
+ const bomRef = createOsQueryFallbackBomRef(
55
+ "authorized_keys_snapshot",
56
+ "data",
57
+ "root",
58
+ "ssh-ed25519",
59
+ "key_file",
60
+ "/root/.ssh/authorized_keys",
61
+ );
62
+ assert.strictEqual(
63
+ bomRef,
64
+ "osquery:authorized_keys_snapshot:data:root@ssh-ed25519[key_file=/root/.ssh/authorized_keys]",
65
+ );
66
+ });
67
+
68
+ it("omits the bracketed suffix when no extra identity field exists", () => {
69
+ const bomRef = createOsQueryFallbackBomRef(
70
+ "windows_run_keys",
71
+ "data",
72
+ "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\Updater",
73
+ undefined,
74
+ );
75
+ assert.strictEqual(
76
+ bomRef,
77
+ "osquery:windows_run_keys:data:HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\Updater@unknown",
78
+ );
79
+ });
80
+
81
+ it("sanitizes fallback bom-ref fragments without percent-encoding them", () => {
82
+ assert.strictEqual(
83
+ sanitizeOsQueryBomRefValue(" launchd: updater@[user]=#1\n"),
84
+ "launchd- updater--user---1",
85
+ );
86
+ });
87
+
88
+ it("limits purl generation to package-like osquery component types", () => {
89
+ assert.strictEqual(shouldCreateOsQueryPurl(undefined), true);
90
+ assert.strictEqual(shouldCreateOsQueryPurl("application"), true);
91
+ assert.strictEqual(shouldCreateOsQueryPurl("operating-system"), true);
92
+ assert.strictEqual(shouldCreateOsQueryPurl("data"), false);
93
+ assert.strictEqual(shouldCreateOsQueryPurl("device"), false);
94
+ assert.strictEqual(shouldCreateOsQueryPurl("cryptographic-asset"), false);
95
+ });
49
96
  });