@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
@@ -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
  });
@@ -9,6 +9,10 @@ import {
9
9
  providerNamesForText,
10
10
  sanitizeMcpRefToken,
11
11
  } from "./mcpDiscovery.js";
12
+ import {
13
+ sanitizeBomPropertyValue,
14
+ sanitizeBomUrl,
15
+ } from "./propertySanitizer.js";
12
16
  import { scanTextForHiddenUnicode } from "./unicodeScan.js";
13
17
 
14
18
  const MCP_CONFIG_PATTERNS = [
@@ -40,13 +44,22 @@ const SECRET_FIELD_NAME_PATTERN =
40
44
  const ENV_REFERENCE_PATTERN = /(?:\$\{?[A-Z0-9_]+\}?|%[A-Z0-9_]+%)/u;
41
45
 
42
46
  function addUniqueProperty(properties, name, value) {
43
- if (value === undefined || value === null || value === "") {
47
+ const sanitizedValue = sanitizeBomPropertyValue(name, value);
48
+ if (
49
+ sanitizedValue === undefined ||
50
+ sanitizedValue === null ||
51
+ sanitizedValue === ""
52
+ ) {
44
53
  return;
45
54
  }
46
- if (properties.some((prop) => prop.name === name && prop.value === value)) {
55
+ if (
56
+ properties.some(
57
+ (prop) => prop.name === name && prop.value === String(sanitizedValue),
58
+ )
59
+ ) {
47
60
  return;
48
61
  }
49
- properties.push({ name, value: String(value) });
62
+ properties.push({ name, value: String(sanitizedValue) });
50
63
  }
51
64
 
52
65
  function normalizeFilePath(filePath) {
@@ -380,6 +393,9 @@ function createServiceFromConfig(
380
393
  const command = normalized.command;
381
394
  const args = normalized.args;
382
395
  const endpoints = extractEndpoints(serverConfig);
396
+ const sanitizedEndpoints = endpoints.map((endpoint) =>
397
+ sanitizeBomUrl(endpoint),
398
+ );
383
399
  const transport = inferTransport(serverConfig, endpoints);
384
400
  const authHints = authHintsFromValue(serverConfig);
385
401
  const authPosture = authPostureForConfig(serverConfig, endpoints, authHints);
@@ -396,7 +412,7 @@ function createServiceFromConfig(
396
412
  JSON.stringify({
397
413
  args,
398
414
  command,
399
- endpoints,
415
+ endpoints: sanitizedEndpoints,
400
416
  env: serverConfig?.env || serverConfig?.environment || {},
401
417
  }),
402
418
  );
@@ -534,7 +550,7 @@ function createServiceFromConfig(
534
550
  return {
535
551
  "bom-ref": `urn:service:mcp:${sanitizeMcpRefToken(serviceName)}:${sanitizeMcpRefToken(version)}`,
536
552
  authenticated,
537
- endpoints,
553
+ endpoints: sanitizedEndpoints,
538
554
  group: "mcp",
539
555
  name: serviceName,
540
556
  properties,