@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.
- package/README.md +70 -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 +171 -15
- package/data/rules/container-risk.yaml +14 -7
- package/data/rules/dependency-sources.yaml +76 -5
- 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 +36 -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 +647 -127
- package/lib/cli/index.poku.js +1905 -187
- package/lib/evinser/evinser.js +14 -9
- package/lib/helpers/agentFormulationParser.js +6 -2
- package/lib/helpers/agentFormulationParser.poku.js +42 -0
- package/lib/helpers/analyzer.js +1444 -38
- package/lib/helpers/analyzer.poku.js +409 -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/chromextutils.js +25 -3
- package/lib/helpers/chromextutils.poku.js +68 -0
- package/lib/helpers/ciParsers/githubActions.js +79 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +103 -0
- package/lib/helpers/communityAiConfigParser.js +15 -5
- package/lib/helpers/communityAiConfigParser.poku.js +71 -0
- package/lib/helpers/depsUtils.js +5 -0
- package/lib/helpers/depsUtils.poku.js +55 -0
- package/lib/helpers/display.js +336 -23
- package/lib/helpers/display.poku.js +179 -43
- 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/mcpConfigParser.js +21 -5
- package/lib/helpers/mcpConfigParser.poku.js +39 -2
- 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/propertySanitizer.js +121 -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 +2454 -198
- package/lib/helpers/utils.poku.js +1798 -74
- package/lib/managers/binary.e2e.poku.js +367 -0
- package/lib/managers/binary.js +2306 -350
- package/lib/managers/binary.poku.js +1700 -1
- package/lib/managers/docker.js +441 -95
- package/lib/managers/docker.poku.js +1479 -14
- package/lib/server/server.js +2 -24
- package/lib/server/server.poku.js +36 -1
- 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 +2967 -990
- 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 +24 -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/agentFormulationParser.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/chromextutils.d.ts.map +1 -1
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +1 -0
- 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/mcpConfigParser.d.ts +1 -1
- package/types/lib/helpers/mcpConfigParser.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/propertySanitizer.d.ts +3 -0
- package/types/lib/helpers/propertySanitizer.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 +74 -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 +3 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +2 -0
- 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/server/server.js
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
} from "../helpers/source.js";
|
|
28
28
|
import {
|
|
29
29
|
CDXGEN_VERSION,
|
|
30
|
-
|
|
30
|
+
isAllowedHttpHost,
|
|
31
31
|
isSecureMode,
|
|
32
32
|
isWin,
|
|
33
33
|
} from "../helpers/utils.js";
|
|
@@ -76,29 +76,7 @@ const ALLOWED_PARAMS = [
|
|
|
76
76
|
|
|
77
77
|
const app = connect();
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
if (!process.env.CDXGEN_ALLOWED_HOSTS) {
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
if (!hostname || hasDangerousUnicode(hostname)) {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
const allowHosts = process.env.CDXGEN_ALLOWED_HOSTS.split(",")
|
|
87
|
-
.map((host) => host.trim())
|
|
88
|
-
.filter(Boolean);
|
|
89
|
-
for (const allowedHost of allowHosts) {
|
|
90
|
-
if (hostname === allowedHost) {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
if (
|
|
94
|
-
allowedHost.startsWith("*.") &&
|
|
95
|
-
hostname.endsWith(allowedHost.slice(1))
|
|
96
|
-
) {
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
79
|
+
export { isAllowedHttpHost };
|
|
102
80
|
|
|
103
81
|
app.use(
|
|
104
82
|
bodyParser.json({
|
|
@@ -12,7 +12,12 @@ import {
|
|
|
12
12
|
validateAndRejectGitSource,
|
|
13
13
|
} from "../helpers/source.js";
|
|
14
14
|
import { isWin } from "../helpers/utils.js";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
getQueryParams,
|
|
17
|
+
isAllowedHttpHost,
|
|
18
|
+
parseQueryString,
|
|
19
|
+
parseValue,
|
|
20
|
+
} from "./server.js";
|
|
16
21
|
|
|
17
22
|
function nullProtoObj(obj) {
|
|
18
23
|
if (obj === null || typeof obj !== "object") {
|
|
@@ -171,6 +176,36 @@ describe("isAllowedHost()", () => {
|
|
|
171
176
|
});
|
|
172
177
|
});
|
|
173
178
|
|
|
179
|
+
describe("isAllowedHttpHost()", () => {
|
|
180
|
+
let originalAllowedHosts;
|
|
181
|
+
|
|
182
|
+
beforeEach(() => {
|
|
183
|
+
originalAllowedHosts = process.env.CDXGEN_ALLOWED_HOSTS;
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
afterEach(() => {
|
|
187
|
+
if (originalAllowedHosts === undefined) {
|
|
188
|
+
delete process.env.CDXGEN_ALLOWED_HOSTS;
|
|
189
|
+
} else {
|
|
190
|
+
process.env.CDXGEN_ALLOWED_HOSTS = originalAllowedHosts;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("allows exact host matches", () => {
|
|
195
|
+
process.env.CDXGEN_ALLOWED_HOSTS = "dependencytrack.example.com";
|
|
196
|
+
assert.strictEqual(isAllowedHttpHost("dependencytrack.example.com"), true);
|
|
197
|
+
assert.strictEqual(isAllowedHttpHost("other.example.com"), false);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("allows only real subdomains for wildcard entries", () => {
|
|
201
|
+
process.env.CDXGEN_ALLOWED_HOSTS = "*.example.com";
|
|
202
|
+
assert.strictEqual(isAllowedHttpHost("api.example.com"), true);
|
|
203
|
+
assert.strictEqual(isAllowedHttpHost("deep.api.example.com"), true);
|
|
204
|
+
assert.strictEqual(isAllowedHttpHost("example.com"), false);
|
|
205
|
+
assert.strictEqual(isAllowedHttpHost("evil-example.com"), false);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
174
209
|
describe("isAllowedPath()", () => {
|
|
175
210
|
let originalPaths;
|
|
176
211
|
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
|
+
import {
|
|
5
|
+
formatHbomHardwareClassSummary,
|
|
6
|
+
getHbomSummary,
|
|
7
|
+
isHbomLikeBom,
|
|
8
|
+
} from "../../helpers/hbomAnalysis.js";
|
|
9
|
+
import { getContainerFileInventoryStats } from "../../helpers/inventoryStats.js";
|
|
4
10
|
import { thoughtLog } from "../../helpers/logger.js";
|
|
5
11
|
import { getTrustedPublishingComponentCounts } from "../../helpers/provenanceUtils.js";
|
|
6
12
|
import { dirNameStr } from "../../helpers/utils.js";
|
|
@@ -247,6 +253,11 @@ export function findBomType(bomJson) {
|
|
|
247
253
|
bomType = "SBOM";
|
|
248
254
|
description = "Software Bill-of-Materials (SBOM) including GitHub Actions";
|
|
249
255
|
}
|
|
256
|
+
// Is this an HBOM?
|
|
257
|
+
else if (isHbomLikeBom(bomJson)) {
|
|
258
|
+
bomType = "HBOM";
|
|
259
|
+
description = "Hardware Bill-of-Materials (HBOM)";
|
|
260
|
+
}
|
|
250
261
|
// Is this an OBOM?
|
|
251
262
|
else if (lifecycles.filter((l) => l.phase === "operations").length > 0) {
|
|
252
263
|
bomType = "OBOM";
|
|
@@ -296,7 +307,10 @@ export function textualMetadata(bomJson) {
|
|
|
296
307
|
const swidCount = bomJson?.components?.filter((c) =>
|
|
297
308
|
c?.purl?.startsWith("pkg:swid"),
|
|
298
309
|
).length;
|
|
310
|
+
const { unpackagedExecutableCount, unpackagedSharedLibraryCount } =
|
|
311
|
+
getContainerFileInventoryStats(bomJson?.components);
|
|
299
312
|
const githubStats = getGitHubWorkflowStats(bomJson?.components);
|
|
313
|
+
const hbomSummary = bomType === "HBOM" ? getHbomSummary(bomJson) : undefined;
|
|
300
314
|
const trustedPublishingCounts = getTrustedPublishingComponentCounts(
|
|
301
315
|
bomJson?.components,
|
|
302
316
|
);
|
|
@@ -511,6 +525,9 @@ export function textualMetadata(bomJson) {
|
|
|
511
525
|
if (bundledSdks.length) {
|
|
512
526
|
text = `${text} Furthermore, the container image bundles the following SDKs: ${bundledSdks.join(", ")}.`;
|
|
513
527
|
}
|
|
528
|
+
if (unpackagedExecutableCount || unpackagedSharedLibraryCount) {
|
|
529
|
+
text = `${text} The container or rootfs inventory includes ${unpackagedExecutableCount} executable file component(s) and ${unpackagedSharedLibraryCount} shared library component(s) that were not traced to OS package ownership.`;
|
|
530
|
+
}
|
|
514
531
|
if (bomPkgTypes.length && bomPkgNamespaces.length) {
|
|
515
532
|
if (bomPkgTypes.length === 1) {
|
|
516
533
|
if (bomPkgNamespaces.length === 1) {
|
|
@@ -537,6 +554,27 @@ export function textualMetadata(bomJson) {
|
|
|
537
554
|
text = `${text} In addition, there are ${swidCount} applications installed on the system.`;
|
|
538
555
|
}
|
|
539
556
|
}
|
|
557
|
+
if (bomType === "HBOM" && hbomSummary) {
|
|
558
|
+
if (hbomSummary.hardwareClassCount > 0) {
|
|
559
|
+
text = `${text} The hardware inventory spans ${hbomSummary.hardwareClassCount} hardware classes.`;
|
|
560
|
+
const hardwareClassSummary = formatHbomHardwareClassSummary(
|
|
561
|
+
hbomSummary.hardwareClassCounts,
|
|
562
|
+
);
|
|
563
|
+
if (hardwareClassSummary) {
|
|
564
|
+
text = `${text} The most represented hardware classes are ${hardwareClassSummary}.`;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (hbomSummary.collectorProfile) {
|
|
568
|
+
text = `${text} Collector profile '${hbomSummary.collectorProfile}' recorded ${hbomSummary.evidenceCommandCount} command evidence entr${hbomSummary.evidenceCommandCount === 1 ? "y" : "ies"}`;
|
|
569
|
+
if (hbomSummary.evidenceFileCount > 0) {
|
|
570
|
+
text = `${text} and ${hbomSummary.evidenceFileCount} observed file entr${hbomSummary.evidenceFileCount === 1 ? "y" : "ies"}`;
|
|
571
|
+
}
|
|
572
|
+
text = `${text}.`;
|
|
573
|
+
}
|
|
574
|
+
if (hbomSummary.identifierPolicy) {
|
|
575
|
+
text = `${text} Identifier policy is '${hbomSummary.identifierPolicy}'.`;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
540
578
|
if (bomType === "SaaSBOM") {
|
|
541
579
|
text = `${text} ${bomJson.services.length} are described in this ${bomType} under services.`;
|
|
542
580
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assert, it } from "poku";
|
|
2
2
|
|
|
3
|
-
import { extractTags, textualMetadata } from "./annotator.js";
|
|
3
|
+
import { extractTags, findBomType, textualMetadata } from "./annotator.js";
|
|
4
4
|
|
|
5
5
|
it("textualMetadata tests", () => {
|
|
6
6
|
assert.deepStrictEqual(textualMetadata({}), undefined);
|
|
@@ -303,6 +303,36 @@ it("textualMetadata tests", () => {
|
|
|
303
303
|
"Trusted publishing metadata is present for 1 npm component(s) and 1 PyPI component(s).",
|
|
304
304
|
),
|
|
305
305
|
);
|
|
306
|
+
|
|
307
|
+
assert.ok(
|
|
308
|
+
textualMetadata({
|
|
309
|
+
metadata: {
|
|
310
|
+
component: {
|
|
311
|
+
name: "demo-image",
|
|
312
|
+
type: "container",
|
|
313
|
+
version: "1.0.0",
|
|
314
|
+
},
|
|
315
|
+
timestamp: "2026-01-01T00:00:00Z",
|
|
316
|
+
tools: {
|
|
317
|
+
components: [{ name: "cdxgen" }],
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
components: [
|
|
321
|
+
{
|
|
322
|
+
type: "file",
|
|
323
|
+
name: "demo",
|
|
324
|
+
properties: [{ name: "internal:is_executable", value: "true" }],
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
type: "file",
|
|
328
|
+
name: "libdemo.so",
|
|
329
|
+
properties: [{ name: "internal:is_shared_library", value: "true" }],
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
}).includes(
|
|
333
|
+
"The container or rootfs inventory includes 1 executable file component(s) and 1 shared library component(s) that were not traced to OS package ownership.",
|
|
334
|
+
),
|
|
335
|
+
);
|
|
306
336
|
});
|
|
307
337
|
|
|
308
338
|
it("extractTags tests", () => {
|
|
@@ -326,3 +356,79 @@ it("textualMetadata includes the CycloneDX 1.7 TLP classification from distribut
|
|
|
326
356
|
/TLP\) classification for this document is 'AMBER_AND_STRICT'/,
|
|
327
357
|
);
|
|
328
358
|
});
|
|
359
|
+
|
|
360
|
+
it("recognizes HBOMs and summarizes hardware-specific metadata", () => {
|
|
361
|
+
const hbom = {
|
|
362
|
+
bomFormat: "CycloneDX",
|
|
363
|
+
specVersion: "1.7",
|
|
364
|
+
metadata: {
|
|
365
|
+
timestamp: "2026-01-01T00:00:00Z",
|
|
366
|
+
tools: {
|
|
367
|
+
components: [{ name: "cdxgen" }],
|
|
368
|
+
},
|
|
369
|
+
component: {
|
|
370
|
+
name: "demo-host",
|
|
371
|
+
type: "device",
|
|
372
|
+
manufacturer: { name: "Example Corp" },
|
|
373
|
+
properties: [
|
|
374
|
+
{ name: "cdx:hbom:platform", value: "linux" },
|
|
375
|
+
{ name: "cdx:hbom:architecture", value: "amd64" },
|
|
376
|
+
{
|
|
377
|
+
name: "cdx:hbom:identifierPolicy",
|
|
378
|
+
value: "redacted-by-default",
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
components: [
|
|
384
|
+
{
|
|
385
|
+
name: "eth0",
|
|
386
|
+
type: "device",
|
|
387
|
+
properties: [
|
|
388
|
+
{ name: "cdx:hbom:hardwareClass", value: "network-interface" },
|
|
389
|
+
],
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
name: "wlan0",
|
|
393
|
+
type: "device",
|
|
394
|
+
properties: [
|
|
395
|
+
{ name: "cdx:hbom:hardwareClass", value: "network-interface" },
|
|
396
|
+
],
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
name: "nvme0",
|
|
400
|
+
type: "device",
|
|
401
|
+
properties: [{ name: "cdx:hbom:hardwareClass", value: "storage" }],
|
|
402
|
+
},
|
|
403
|
+
],
|
|
404
|
+
properties: [
|
|
405
|
+
{ name: "cdx:hbom:collectorProfile", value: "linux-amd64-v1" },
|
|
406
|
+
{ name: "cdx:hbom:evidence:commandCount", value: "2" },
|
|
407
|
+
{
|
|
408
|
+
name: "cdx:hbom:evidence:command",
|
|
409
|
+
value: "lscpu-json|cpu-memory|/usr/bin/lscpu -J",
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: "cdx:hbom:evidence:command",
|
|
413
|
+
value: "ip-link-json|network|/usr/sbin/ip -j link",
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
assert.deepStrictEqual(findBomType(hbom), {
|
|
419
|
+
bomType: "HBOM",
|
|
420
|
+
bomTypeDescription: "Hardware Bill-of-Materials (HBOM)",
|
|
421
|
+
});
|
|
422
|
+
const summary = textualMetadata(hbom);
|
|
423
|
+
assert.match(summary, /Hardware Bill-of-Materials \(HBOM\)/u);
|
|
424
|
+
assert.match(summary, /The hardware inventory spans 2 hardware classes\./u);
|
|
425
|
+
assert.match(
|
|
426
|
+
summary,
|
|
427
|
+
/The most represented hardware classes are network-interface \(2\), storage \(1\)\./u,
|
|
428
|
+
);
|
|
429
|
+
assert.match(
|
|
430
|
+
summary,
|
|
431
|
+
/Collector profile 'linux-amd64-v1' recorded 2 command evidence entries\./u,
|
|
432
|
+
);
|
|
433
|
+
assert.match(summary, /Identifier policy is 'redacted-by-default'\./u);
|
|
434
|
+
});
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
expandBomAuditCategories,
|
|
11
11
|
validateBomAuditCategories,
|
|
12
12
|
} from "../../helpers/auditCategories.js";
|
|
13
|
+
import { isHbomLikeBom as isHbomLikeBomDocument } from "../../helpers/hbomAnalysis.js";
|
|
13
14
|
import { table } from "../../helpers/table.js";
|
|
14
15
|
import {
|
|
15
16
|
DEBUG_MODE,
|
|
@@ -21,17 +22,7 @@ import { evaluateRules, loadRules } from "./ruleEngine.js";
|
|
|
21
22
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
22
23
|
const BUILTIN_RULES_DIR = join(__dirname, "..", "..", "..", "data", "rules");
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
* Audit BOM formulation section using JSONata-powered rule engine
|
|
26
|
-
* @param {Object} bomJson - Generated CycloneDX BOM
|
|
27
|
-
* @param {Object} options - CLI options
|
|
28
|
-
* @returns {Promise<Array>} Array of audit findings
|
|
29
|
-
*/
|
|
30
|
-
export async function auditBom(bomJson, options) {
|
|
31
|
-
if (!bomJson) {
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
34
|
-
const findings = [];
|
|
25
|
+
async function loadConfiguredBomAuditRules(options = {}) {
|
|
35
26
|
const rules = await loadRules(BUILTIN_RULES_DIR);
|
|
36
27
|
if (options.bomAuditRulesDir && safeExistsSync(options.bomAuditRulesDir)) {
|
|
37
28
|
const userRulesDir = resolve(options.bomAuditRulesDir);
|
|
@@ -41,11 +32,11 @@ export async function auditBom(bomJson, options) {
|
|
|
41
32
|
}
|
|
42
33
|
rules.push(...userRules);
|
|
43
34
|
}
|
|
44
|
-
if (rules.length
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
if (!rules.length) {
|
|
36
|
+
return {
|
|
37
|
+
activeRules: [],
|
|
38
|
+
rules,
|
|
39
|
+
};
|
|
49
40
|
}
|
|
50
41
|
let activeRules = rules;
|
|
51
42
|
if (options.bomAuditCategories) {
|
|
@@ -64,6 +55,107 @@ export async function auditBom(bomJson, options) {
|
|
|
64
55
|
}
|
|
65
56
|
}
|
|
66
57
|
}
|
|
58
|
+
return {
|
|
59
|
+
activeRules,
|
|
60
|
+
rules,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Detect whether a BOM looks like an HBOM inventory.
|
|
66
|
+
*
|
|
67
|
+
* @param {object} bomJson CycloneDX BOM
|
|
68
|
+
* @returns {boolean} True when the BOM appears to represent hardware inventory
|
|
69
|
+
*/
|
|
70
|
+
export function isHbomLikeBom(bomJson) {
|
|
71
|
+
return isHbomLikeBomDocument(bomJson);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Detect whether a BOM looks like an OBOM/runtime inventory.
|
|
76
|
+
*
|
|
77
|
+
* @param {object} bomJson CycloneDX BOM
|
|
78
|
+
* @returns {boolean} True when the BOM appears to represent operations/runtime data
|
|
79
|
+
*/
|
|
80
|
+
export function isObomLikeBom(bomJson) {
|
|
81
|
+
if (!bomJson) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
if (isHbomLikeBom(bomJson)) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
if (
|
|
88
|
+
bomJson?.metadata?.component?.type === "operating-system" ||
|
|
89
|
+
bomJson?.metadata?.component?.type === "device"
|
|
90
|
+
) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
if (
|
|
94
|
+
Array.isArray(bomJson?.metadata?.lifecycles) &&
|
|
95
|
+
bomJson.metadata.lifecycles.some(
|
|
96
|
+
(lifecycle) => lifecycle?.phase === "operations",
|
|
97
|
+
)
|
|
98
|
+
) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return (bomJson?.components || []).some((component) =>
|
|
102
|
+
(component?.properties || []).some(
|
|
103
|
+
(property) => property?.name === "cdx:osquery:category",
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function summarizeDryRunSupport(activeRules = []) {
|
|
109
|
+
const summary = {
|
|
110
|
+
fullCount: 0,
|
|
111
|
+
noCount: 0,
|
|
112
|
+
partialCount: 0,
|
|
113
|
+
totalRules: activeRules.length,
|
|
114
|
+
};
|
|
115
|
+
for (const rule of activeRules) {
|
|
116
|
+
if (rule?.dryRunSupport === "no") {
|
|
117
|
+
summary.noCount += 1;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (rule?.dryRunSupport === "full") {
|
|
121
|
+
summary.fullCount += 1;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
summary.partialCount += 1;
|
|
125
|
+
}
|
|
126
|
+
return summary;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function getBomAuditDryRunSupportSummary(options = {}) {
|
|
130
|
+
const { activeRules } = await loadConfiguredBomAuditRules(options);
|
|
131
|
+
return summarizeDryRunSupport(activeRules);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function formatDryRunSupportSummary(summary) {
|
|
135
|
+
if (!summary) {
|
|
136
|
+
return "";
|
|
137
|
+
}
|
|
138
|
+
return `BOM audit dry-run summary: ${summary.noCount} rule(s) do not support dry-run, ${summary.partialCount} rule(s) have partial dry-run support, ${summary.totalRules} active rule(s) total.`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Audit BOM formulation section using JSONata-powered rule engine
|
|
143
|
+
* @param {Object} bomJson - Generated CycloneDX BOM
|
|
144
|
+
* @param {Object} options - CLI options
|
|
145
|
+
* @returns {Promise<Array>} Array of audit findings
|
|
146
|
+
*/
|
|
147
|
+
export async function auditBom(bomJson, options) {
|
|
148
|
+
if (!bomJson) {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
const findings = [];
|
|
152
|
+
const { activeRules, rules } = await loadConfiguredBomAuditRules(options);
|
|
153
|
+
if (rules.length === 0) {
|
|
154
|
+
if (DEBUG_MODE) {
|
|
155
|
+
console.log("No audit rules loaded; formulation audit skipped");
|
|
156
|
+
}
|
|
157
|
+
return findings;
|
|
158
|
+
}
|
|
67
159
|
const allFindings = await evaluateRules(activeRules, bomJson);
|
|
68
160
|
if (options.bomAuditMinSeverity) {
|
|
69
161
|
const minSeverity = options.bomAuditMinSeverity.toLowerCase();
|
|
@@ -87,7 +179,7 @@ export async function auditBom(bomJson, options) {
|
|
|
87
179
|
/**
|
|
88
180
|
* Format findings for console output with color-coded severity
|
|
89
181
|
*/
|
|
90
|
-
export function
|
|
182
|
+
export function renderBomAuditConsoleReport(findings) {
|
|
91
183
|
if (!findings?.length) {
|
|
92
184
|
return "";
|
|
93
185
|
}
|
|
@@ -119,7 +211,18 @@ export function formatConsoleOutput(findings) {
|
|
|
119
211
|
line.push(f.location?.file || "");
|
|
120
212
|
data.push(line);
|
|
121
213
|
}
|
|
122
|
-
|
|
214
|
+
return table(data, config);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Format findings for console output with color-coded severity
|
|
219
|
+
*/
|
|
220
|
+
export function formatConsoleOutput(findings) {
|
|
221
|
+
const output = renderBomAuditConsoleReport(findings);
|
|
222
|
+
if (output) {
|
|
223
|
+
console.log(output);
|
|
224
|
+
}
|
|
225
|
+
return output;
|
|
123
226
|
}
|
|
124
227
|
|
|
125
228
|
/**
|