@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.
- package/README.md +69 -25
- package/bin/audit.js +21 -7
- package/bin/cdxgen.js +270 -127
- package/bin/convert.js +34 -15
- package/bin/hbom.js +495 -0
- package/bin/repl.js +592 -37
- package/bin/validate.js +31 -4
- package/bin/verify.js +18 -5
- package/data/README.md +298 -25
- package/data/component-tags.json +6 -0
- package/data/crypto-oid.json +16 -0
- package/data/cyclonedx-2.0-bundled.schema.json +7182 -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 +210 -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 +527 -99
- package/lib/cli/index.poku.js +1469 -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/bomUtils.js +155 -1
- package/lib/helpers/bomUtils.poku.js +79 -1
- 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 +350 -0
- package/lib/helpers/plugins.poku.js +57 -0
- package/lib/helpers/protobom.js +209 -45
- package/lib/helpers/protobom.poku.js +183 -5
- package/lib/helpers/protobomLoader.js +43 -0
- package/lib/helpers/protobomLoader.poku.js +31 -0
- 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 +4 -28
- 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 +406 -8
- package/lib/stages/postgen/postgen.poku.js +484 -0
- package/lib/stages/postgen/ruleEngine.js +116 -0
- package/lib/stages/pregen/envAudit.js +14 -3
- package/lib/validator/bomValidator.js +90 -38
- package/lib/validator/bomValidator.poku.js +90 -0
- package/lib/validator/complianceRules.js +4 -2
- package/lib/validator/index.poku.js +14 -0
- 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 +1 -1
- 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/bomUtils.d.ts +10 -0
- package/types/lib/helpers/bomUtils.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 +76 -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 +5 -4
- package/types/lib/helpers/protobom.d.ts.map +1 -1
- package/types/lib/helpers/protobomLoader.d.ts +17 -0
- package/types/lib/helpers/protobomLoader.d.ts.map +1 -0
- 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/types/lib/third-party/arborist/lib/node.d.ts +23 -0
- package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/data/spdx-model-v3.0.1.jsonld +0 -15999
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
import { assert, describe, it } from "poku";
|
|
5
|
+
|
|
6
|
+
import { evaluateRule, loadRules } from "./ruleEngine.js";
|
|
7
|
+
|
|
8
|
+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
9
|
+
const RULES_DIR = join(__dirname, "..", "..", "..", "data", "rules");
|
|
10
|
+
|
|
11
|
+
function makeProperty(name, value) {
|
|
12
|
+
return { name, value };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function makeHostBom(
|
|
16
|
+
components = [],
|
|
17
|
+
metadataProperties = [],
|
|
18
|
+
bomProperties = [],
|
|
19
|
+
) {
|
|
20
|
+
return {
|
|
21
|
+
bomFormat: "CycloneDX",
|
|
22
|
+
specVersion: "1.7",
|
|
23
|
+
serialNumber: "urn:uuid:test-host-view",
|
|
24
|
+
metadata: {
|
|
25
|
+
tools: {
|
|
26
|
+
components: [
|
|
27
|
+
{
|
|
28
|
+
type: "application",
|
|
29
|
+
name: "cdxgen",
|
|
30
|
+
version: "12.4.0",
|
|
31
|
+
"bom-ref": "pkg:npm/%40cyclonedx/cdxgen@12.4.0",
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
component: {
|
|
36
|
+
name: "test-host",
|
|
37
|
+
type: "device",
|
|
38
|
+
"bom-ref": "urn:uuid:test-host",
|
|
39
|
+
properties: metadataProperties.map(([k, v]) => makeProperty(k, v)),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
components,
|
|
43
|
+
properties: bomProperties.map(([k, v]) => makeProperty(k, v)),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function makeHbomComponent(name, hardwareClass, properties = []) {
|
|
48
|
+
return {
|
|
49
|
+
type: "device",
|
|
50
|
+
name,
|
|
51
|
+
"bom-ref": `urn:uuid:${hardwareClass}:${name}`,
|
|
52
|
+
properties: [
|
|
53
|
+
makeProperty("cdx:hbom:hardwareClass", hardwareClass),
|
|
54
|
+
...properties.map(([k, v]) => makeProperty(k, v)),
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function makeOsqueryComponent(name, queryCategory, properties = []) {
|
|
60
|
+
return {
|
|
61
|
+
type: "data",
|
|
62
|
+
name,
|
|
63
|
+
"bom-ref": `osquery:${queryCategory}:${name}`,
|
|
64
|
+
properties: [
|
|
65
|
+
makeProperty("cdx:osquery:category", queryCategory),
|
|
66
|
+
...properties.map(([k, v]) => makeProperty(k, v)),
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe("host-topology audit rules", () => {
|
|
72
|
+
it("detects weak wireless security when live runtime addresses confirm the link is active (HMX-002)", async () => {
|
|
73
|
+
const rules = await loadRules(RULES_DIR);
|
|
74
|
+
const rule = rules.find((candidate) => candidate.id === "HMX-002");
|
|
75
|
+
assert.ok(rule, "HMX-002 should be present");
|
|
76
|
+
|
|
77
|
+
const bom = makeHostBom(
|
|
78
|
+
[
|
|
79
|
+
makeHbomComponent("wlp2s0", "wireless-adapter", [
|
|
80
|
+
["cdx:hbom:securityMode", "open"],
|
|
81
|
+
["cdx:hostview:interface_addresses:count", "1"],
|
|
82
|
+
["cdx:hostview:linkedRuntimeCategory", "interface_addresses"],
|
|
83
|
+
]),
|
|
84
|
+
makeOsqueryComponent("192.168.1.55", "interface_addresses", [
|
|
85
|
+
["interface", "wlp2s0"],
|
|
86
|
+
["address", "192.168.1.55"],
|
|
87
|
+
]),
|
|
88
|
+
],
|
|
89
|
+
[["cdx:hbom:platform", "linux"]],
|
|
90
|
+
[
|
|
91
|
+
["cdx:hostview:mode", "hbom-obom-merged"],
|
|
92
|
+
["cdx:hostview:topologyLinkCount", "1"],
|
|
93
|
+
],
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const findings = await evaluateRule(rule, bom);
|
|
97
|
+
assert.strictEqual(findings.length, 1);
|
|
98
|
+
assert.strictEqual(findings[0].ruleId, "HMX-002");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("detects merged host inventories that still have zero strict topology links (HMX-003)", async () => {
|
|
102
|
+
const rules = await loadRules(RULES_DIR);
|
|
103
|
+
const rule = rules.find((candidate) => candidate.id === "HMX-003");
|
|
104
|
+
assert.ok(rule, "HMX-003 should be present");
|
|
105
|
+
|
|
106
|
+
const bom = makeHostBom(
|
|
107
|
+
[makeHbomComponent("enp1s0", "network-interface")],
|
|
108
|
+
[
|
|
109
|
+
["cdx:hbom:platform", "linux"],
|
|
110
|
+
["cdx:hostview:mode", "hbom-obom-merged"],
|
|
111
|
+
["cdx:hostview:hardwareComponentCount", "1"],
|
|
112
|
+
["cdx:hostview:runtimeComponentCount", "2"],
|
|
113
|
+
["cdx:hostview:topologyLinkCount", "0"],
|
|
114
|
+
],
|
|
115
|
+
[
|
|
116
|
+
["cdx:hostview:mode", "hbom-obom-merged"],
|
|
117
|
+
["cdx:hostview:hardwareComponentCount", "1"],
|
|
118
|
+
["cdx:hostview:runtimeComponentCount", "2"],
|
|
119
|
+
["cdx:hostview:topologyLinkCount", "0"],
|
|
120
|
+
],
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const findings = await evaluateRule(rule, bom);
|
|
124
|
+
assert.strictEqual(findings.length, 1);
|
|
125
|
+
assert.strictEqual(findings[0].ruleId, "HMX-003");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("detects degraded storage when explicit runtime mount evidence shows the device is in use (HMX-004)", async () => {
|
|
129
|
+
const rules = await loadRules(RULES_DIR);
|
|
130
|
+
const rule = rules.find((candidate) => candidate.id === "HMX-004");
|
|
131
|
+
assert.ok(rule, "HMX-004 should be present");
|
|
132
|
+
|
|
133
|
+
const bom = makeHostBom(
|
|
134
|
+
[
|
|
135
|
+
makeHbomComponent("nvme0", "storage", [
|
|
136
|
+
["cdx:hbom:smartStatus", "Failing"],
|
|
137
|
+
["cdx:hbom:wearPercentageUsed", "92"],
|
|
138
|
+
["cdx:hostview:mount_hardening:count", "1"],
|
|
139
|
+
["cdx:hostview:runtime-storage:count", "1"],
|
|
140
|
+
]),
|
|
141
|
+
makeOsqueryComponent("/home", "mount_hardening", [
|
|
142
|
+
["device", "/dev/nvme0n1"],
|
|
143
|
+
["path", "/home"],
|
|
144
|
+
]),
|
|
145
|
+
],
|
|
146
|
+
[["cdx:hbom:platform", "linux"]],
|
|
147
|
+
[
|
|
148
|
+
["cdx:hostview:mode", "hbom-obom-merged"],
|
|
149
|
+
["cdx:hostview:topologyLinkCount", "2"],
|
|
150
|
+
],
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const findings = await evaluateRule(rule, bom);
|
|
154
|
+
assert.strictEqual(findings.length, 1);
|
|
155
|
+
assert.strictEqual(findings[0].ruleId, "HMX-004");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("detects revoked secure boot trust anchors only after an explicit HBOM trust link exists (HMX-005)", async () => {
|
|
159
|
+
const rules = await loadRules(RULES_DIR);
|
|
160
|
+
const rule = rules.find((candidate) => candidate.id === "HMX-005");
|
|
161
|
+
assert.ok(rule, "HMX-005 should be present");
|
|
162
|
+
|
|
163
|
+
const bom = makeHostBom(
|
|
164
|
+
[
|
|
165
|
+
makeOsqueryComponent("dbx-entry", "secureboot_certificates", [
|
|
166
|
+
["revoked", "1"],
|
|
167
|
+
["sha1", "db-cert-1"],
|
|
168
|
+
["subject", "CN=Legacy Bootloader"],
|
|
169
|
+
]),
|
|
170
|
+
],
|
|
171
|
+
[
|
|
172
|
+
["cdx:hbom:platform", "linux"],
|
|
173
|
+
["cdx:hostview:secureboot_certificates:count", "1"],
|
|
174
|
+
],
|
|
175
|
+
[
|
|
176
|
+
["cdx:hostview:mode", "hbom-obom-merged"],
|
|
177
|
+
["cdx:hostview:secureboot_certificates:count", "1"],
|
|
178
|
+
["cdx:hostview:topologyLinkCount", "1"],
|
|
179
|
+
],
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const findings = await evaluateRule(rule, bom);
|
|
183
|
+
assert.strictEqual(findings.length, 1);
|
|
184
|
+
assert.strictEqual(findings[0].ruleId, "HMX-005");
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -9,8 +9,15 @@ import {
|
|
|
9
9
|
matchesAiInventoryExcludeType,
|
|
10
10
|
optionIncludesAiInventoryProjectType,
|
|
11
11
|
} from "../../helpers/aiInventory.js";
|
|
12
|
+
import {
|
|
13
|
+
isCycloneDx20SpecVersion,
|
|
14
|
+
normalizeCycloneDxSpecVersion,
|
|
15
|
+
setCycloneDxFormat,
|
|
16
|
+
toCycloneDxSpecVersionString,
|
|
17
|
+
} from "../../helpers/bomUtils.js";
|
|
12
18
|
import { mergeDependencies, mergeServices } from "../../helpers/depsUtils.js";
|
|
13
19
|
import { addFormulationSection } from "../../helpers/formulationParsers.js";
|
|
20
|
+
import { getContainerFileInventoryStats } from "../../helpers/inventoryStats.js";
|
|
14
21
|
import { thoughtLog } from "../../helpers/logger.js";
|
|
15
22
|
import { buildReleaseNotesFromGit } from "../../helpers/source.js";
|
|
16
23
|
import {
|
|
@@ -78,6 +85,8 @@ function applyFormulation(bomJson, options, filePath, formulationList) {
|
|
|
78
85
|
}
|
|
79
86
|
const context = formulationList?.length ? { formulationList } : {};
|
|
80
87
|
setActivityContext({
|
|
88
|
+
bomMutation: "formulation",
|
|
89
|
+
capability: "bom-mutation",
|
|
81
90
|
projectType: "Formulation",
|
|
82
91
|
sourcePath: filePath || options.filePath || process.cwd(),
|
|
83
92
|
});
|
|
@@ -141,10 +150,24 @@ const SIGNED_URL_PARAM_NAMES = new Set([
|
|
|
141
150
|
"x-amz-signature",
|
|
142
151
|
"x-goog-signature",
|
|
143
152
|
]);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
153
|
+
const COMPONENT_1_6_ONLY_FIELDS = new Set([
|
|
154
|
+
"authors",
|
|
155
|
+
"manufacturer",
|
|
156
|
+
"omniborId",
|
|
157
|
+
"swhid",
|
|
158
|
+
"tags",
|
|
159
|
+
]);
|
|
160
|
+
const COMPONENT_1_7_ONLY_FIELDS = new Set([
|
|
161
|
+
"isExternal",
|
|
162
|
+
"patentAssertions",
|
|
163
|
+
"versionRange",
|
|
164
|
+
]);
|
|
165
|
+
const SERVICE_1_6_ONLY_FIELDS = new Set(["tags"]);
|
|
166
|
+
const SERVICE_1_7_ONLY_FIELDS = new Set(["patentAssertions"]);
|
|
167
|
+
const METADATA_1_6_ONLY_FIELDS = new Set(["manufacturer"]);
|
|
168
|
+
const METADATA_1_7_ONLY_FIELDS = new Set(["distributionConstraints"]);
|
|
169
|
+
const METADATA_2_0_REMOVED_FIELDS = new Set(["manufacture"]);
|
|
170
|
+
const COMPONENT_2_0_REMOVED_FIELDS = new Set(["author", "modified"]);
|
|
148
171
|
|
|
149
172
|
function normalizeTlpClassification(tlpClassification) {
|
|
150
173
|
return String(tlpClassification || "")
|
|
@@ -255,9 +278,10 @@ function collectSensitivePropertyViolations(
|
|
|
255
278
|
}
|
|
256
279
|
|
|
257
280
|
function validateTlpClassification(bomJson, options) {
|
|
258
|
-
const specVersion =
|
|
259
|
-
|
|
260
|
-
|
|
281
|
+
const specVersion =
|
|
282
|
+
normalizeCycloneDxSpecVersion(
|
|
283
|
+
bomJson?.specVersion || options?.specVersion,
|
|
284
|
+
) || 0;
|
|
261
285
|
if (specVersion < 1.7) {
|
|
262
286
|
return bomJson;
|
|
263
287
|
}
|
|
@@ -292,11 +316,373 @@ function validateTlpClassification(bomJson, options) {
|
|
|
292
316
|
return bomJson;
|
|
293
317
|
}
|
|
294
318
|
|
|
319
|
+
function applyContainerInventoryMetadata(bomJson) {
|
|
320
|
+
if (!bomJson?.metadata) {
|
|
321
|
+
return bomJson;
|
|
322
|
+
}
|
|
323
|
+
const { unpackagedExecutableCount, unpackagedSharedLibraryCount } =
|
|
324
|
+
getContainerFileInventoryStats(bomJson.components);
|
|
325
|
+
const metadataProperties = Array.isArray(bomJson.metadata.properties)
|
|
326
|
+
? [...bomJson.metadata.properties]
|
|
327
|
+
: [];
|
|
328
|
+
const propertyNamesToReplace = new Set([
|
|
329
|
+
"cdx:container:unpackagedExecutableCount",
|
|
330
|
+
"cdx:container:unpackagedSharedLibraryCount",
|
|
331
|
+
]);
|
|
332
|
+
const retainedProperties = metadataProperties.filter(
|
|
333
|
+
(property) => !propertyNamesToReplace.has(property?.name),
|
|
334
|
+
);
|
|
335
|
+
if (
|
|
336
|
+
unpackagedExecutableCount ||
|
|
337
|
+
metadataProperties.some(
|
|
338
|
+
(property) =>
|
|
339
|
+
property?.name === "cdx:container:unpackagedExecutableCount",
|
|
340
|
+
)
|
|
341
|
+
) {
|
|
342
|
+
retainedProperties.push({
|
|
343
|
+
name: "cdx:container:unpackagedExecutableCount",
|
|
344
|
+
value: String(unpackagedExecutableCount),
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
if (
|
|
348
|
+
unpackagedSharedLibraryCount ||
|
|
349
|
+
metadataProperties.some(
|
|
350
|
+
(property) =>
|
|
351
|
+
property?.name === "cdx:container:unpackagedSharedLibraryCount",
|
|
352
|
+
)
|
|
353
|
+
) {
|
|
354
|
+
retainedProperties.push({
|
|
355
|
+
name: "cdx:container:unpackagedSharedLibraryCount",
|
|
356
|
+
value: String(unpackagedSharedLibraryCount),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
if (retainedProperties.length) {
|
|
360
|
+
bomJson.metadata.properties = retainedProperties;
|
|
361
|
+
}
|
|
362
|
+
return bomJson;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function deleteFields(subject, fields) {
|
|
366
|
+
if (!subject || typeof subject !== "object") {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
for (const fieldName of fields) {
|
|
370
|
+
delete subject[fieldName];
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function isObjectRecord(value) {
|
|
375
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function normalizeComponentForSpecVersion(subject, specVersion) {
|
|
379
|
+
if (specVersion < 1.6) {
|
|
380
|
+
deleteFields(subject, COMPONENT_1_6_ONLY_FIELDS);
|
|
381
|
+
}
|
|
382
|
+
if (specVersion < 1.7) {
|
|
383
|
+
deleteFields(subject, COMPONENT_1_7_ONLY_FIELDS);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function normalizeServiceForSpecVersion(subject, specVersion) {
|
|
388
|
+
if (specVersion < 1.6) {
|
|
389
|
+
deleteFields(subject, SERVICE_1_6_ONLY_FIELDS);
|
|
390
|
+
}
|
|
391
|
+
if (specVersion < 1.7) {
|
|
392
|
+
deleteFields(subject, SERVICE_1_7_ONLY_FIELDS);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function normalizeMetadataForSpecVersion(subject, specVersion) {
|
|
397
|
+
if (specVersion < 1.6) {
|
|
398
|
+
deleteFields(subject, METADATA_1_6_ONLY_FIELDS);
|
|
399
|
+
}
|
|
400
|
+
if (specVersion < 1.7) {
|
|
401
|
+
deleteFields(subject, METADATA_1_7_ONLY_FIELDS);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function authorStringToAuthors(authorValue) {
|
|
406
|
+
if (typeof authorValue !== "string") {
|
|
407
|
+
return undefined;
|
|
408
|
+
}
|
|
409
|
+
const authors = authorValue
|
|
410
|
+
.split(",")
|
|
411
|
+
.map((author) => author.trim())
|
|
412
|
+
.filter(Boolean)
|
|
413
|
+
.map((name) => ({ name }));
|
|
414
|
+
return authors.length ? authors : undefined;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function normalizeLegacyToolComponent(tool) {
|
|
418
|
+
if (!isObjectRecord(tool)) {
|
|
419
|
+
return tool;
|
|
420
|
+
}
|
|
421
|
+
if (!tool.type) {
|
|
422
|
+
tool.type = "application";
|
|
423
|
+
}
|
|
424
|
+
if (tool.vendor && !tool.publisher) {
|
|
425
|
+
tool.publisher = tool.vendor;
|
|
426
|
+
}
|
|
427
|
+
delete tool.vendor;
|
|
428
|
+
if (!tool.authors && tool.author) {
|
|
429
|
+
tool.authors = authorStringToAuthors(tool.author);
|
|
430
|
+
}
|
|
431
|
+
deleteFields(tool, COMPONENT_2_0_REMOVED_FIELDS);
|
|
432
|
+
normalizeComponentsForSpecVersion(tool.components);
|
|
433
|
+
return tool;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function hasExplicitSpecVersion(specVersion) {
|
|
437
|
+
return (
|
|
438
|
+
specVersion !== undefined &&
|
|
439
|
+
specVersion !== null &&
|
|
440
|
+
`${specVersion}`.trim() !== ""
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function resolveSpecVersionForCompatibility(bomJson, options) {
|
|
445
|
+
if (hasExplicitSpecVersion(options?.specVersion)) {
|
|
446
|
+
return options.specVersion;
|
|
447
|
+
}
|
|
448
|
+
if (hasExplicitSpecVersion(bomJson?.specVersion)) {
|
|
449
|
+
return bomJson.specVersion;
|
|
450
|
+
}
|
|
451
|
+
return "1.7";
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function normalizeLegacyToolService(service) {
|
|
455
|
+
if (!isObjectRecord(service)) {
|
|
456
|
+
return service;
|
|
457
|
+
}
|
|
458
|
+
if (service.vendor && !service.provider) {
|
|
459
|
+
service.provider =
|
|
460
|
+
typeof service.vendor === "string"
|
|
461
|
+
? { name: service.vendor }
|
|
462
|
+
: service.vendor;
|
|
463
|
+
}
|
|
464
|
+
delete service.vendor;
|
|
465
|
+
deleteFields(service, COMPONENT_2_0_REMOVED_FIELDS);
|
|
466
|
+
normalizeServicesForSpecVersion(service.services);
|
|
467
|
+
return service;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function normalizeComponentsForSpecVersion(components) {
|
|
471
|
+
if (!Array.isArray(components)) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
for (const component of components) {
|
|
475
|
+
normalizeComponentForSpecVersion20(component);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function normalizeComponentForSpecVersion20(component) {
|
|
480
|
+
if (!isObjectRecord(component)) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (!component.authors && component.author) {
|
|
484
|
+
component.authors = authorStringToAuthors(component.author);
|
|
485
|
+
}
|
|
486
|
+
deleteFields(component, COMPONENT_2_0_REMOVED_FIELDS);
|
|
487
|
+
normalizeComponentsForSpecVersion(component.components);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function normalizeServicesForSpecVersion(services) {
|
|
491
|
+
if (!Array.isArray(services)) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
for (const service of services) {
|
|
495
|
+
normalizeLegacyToolService(service);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function normalizeFormulationForSpecVersion(formulation) {
|
|
500
|
+
if (!Array.isArray(formulation)) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
for (const formula of formulation) {
|
|
504
|
+
if (!isObjectRecord(formula)) {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
normalizeComponentsForSpecVersion(formula.components);
|
|
508
|
+
normalizeServicesForSpecVersion(formula.services);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function normalizeVulnerabilitiesForSpecVersion(vulnerabilities) {
|
|
513
|
+
if (!Array.isArray(vulnerabilities)) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
for (const vulnerability of vulnerabilities) {
|
|
517
|
+
if (!isObjectRecord(vulnerability?.tools)) {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
normalizeComponentsForSpecVersion(vulnerability.tools.components);
|
|
521
|
+
normalizeServicesForSpecVersion(vulnerability.tools.services);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function normalizeDefinitionsForSpecVersion(definitions) {
|
|
526
|
+
if (!isObjectRecord(definitions)) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
normalizeComponentsForSpecVersion(definitions.components);
|
|
530
|
+
normalizeServicesForSpecVersion(definitions.services);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function migrateLegacyManufactureForSpecVersion(metadata) {
|
|
534
|
+
if (!metadata.manufacture) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (isObjectRecord(metadata.component) && !metadata.component.manufacturer) {
|
|
538
|
+
metadata.component.manufacturer = metadata.manufacture;
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (!metadata.manufacturer) {
|
|
542
|
+
metadata.manufacturer = metadata.manufacture;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function normalizeToolsForSpecVersion(subject, specVersion) {
|
|
547
|
+
if (!subject || !isCycloneDx20SpecVersion(specVersion)) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (Array.isArray(subject.tools)) {
|
|
551
|
+
subject.tools = {
|
|
552
|
+
components: subject.tools.map((tool) =>
|
|
553
|
+
normalizeLegacyToolComponent(isObjectRecord(tool) ? { ...tool } : tool),
|
|
554
|
+
),
|
|
555
|
+
};
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
if (!isObjectRecord(subject.tools)) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (Array.isArray(subject.tools.components)) {
|
|
562
|
+
subject.tools.components = subject.tools.components.map((tool) =>
|
|
563
|
+
normalizeLegacyToolComponent(tool),
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
if (Array.isArray(subject.tools.services)) {
|
|
567
|
+
subject.tools.services = subject.tools.services.map((service) =>
|
|
568
|
+
normalizeLegacyToolService(service),
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function upgradeSubjectForSpecVersion(subject, specVersion) {
|
|
574
|
+
if (!isObjectRecord(subject) || !isCycloneDx20SpecVersion(specVersion)) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (isObjectRecord(subject.metadata)) {
|
|
578
|
+
migrateLegacyManufactureForSpecVersion(subject.metadata);
|
|
579
|
+
deleteFields(subject.metadata, METADATA_2_0_REMOVED_FIELDS);
|
|
580
|
+
normalizeToolsForSpecVersion(subject.metadata, specVersion);
|
|
581
|
+
normalizeComponentForSpecVersion20(subject.metadata.component);
|
|
582
|
+
}
|
|
583
|
+
normalizeComponentsForSpecVersion(subject.components);
|
|
584
|
+
normalizeFormulationForSpecVersion(subject.formulation);
|
|
585
|
+
normalizeDefinitionsForSpecVersion(subject.definitions);
|
|
586
|
+
normalizeVulnerabilitiesForSpecVersion(subject.vulnerabilities);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function downgradeSubjectForSpecVersion(subject, specVersion, parentKey) {
|
|
590
|
+
if (!subject || typeof subject !== "object") {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (Array.isArray(subject)) {
|
|
594
|
+
subject.forEach((entry) => {
|
|
595
|
+
downgradeSubjectForSpecVersion(entry, specVersion, parentKey);
|
|
596
|
+
});
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (parentKey === "metadata") {
|
|
600
|
+
normalizeMetadataForSpecVersion(subject, specVersion);
|
|
601
|
+
}
|
|
602
|
+
if (parentKey === "component" || parentKey === "components") {
|
|
603
|
+
normalizeComponentForSpecVersion(subject, specVersion);
|
|
604
|
+
}
|
|
605
|
+
if (parentKey === "service" || parentKey === "services") {
|
|
606
|
+
normalizeServiceForSpecVersion(subject, specVersion);
|
|
607
|
+
}
|
|
608
|
+
if (specVersion < 1.6) {
|
|
609
|
+
if (subject.cryptoProperties) {
|
|
610
|
+
delete subject.cryptoProperties;
|
|
611
|
+
}
|
|
612
|
+
if (
|
|
613
|
+
subject?.evidence?.occurrences &&
|
|
614
|
+
Array.isArray(subject.evidence.occurrences)
|
|
615
|
+
) {
|
|
616
|
+
subject.evidence.occurrences.forEach((occurrence) => {
|
|
617
|
+
delete occurrence.line;
|
|
618
|
+
delete occurrence.offset;
|
|
619
|
+
delete occurrence.symbol;
|
|
620
|
+
delete occurrence.additionalContext;
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
if (
|
|
624
|
+
subject?.evidence?.identity &&
|
|
625
|
+
Array.isArray(subject.evidence.identity)
|
|
626
|
+
) {
|
|
627
|
+
subject.evidence.identity = subject.evidence.identity[0];
|
|
628
|
+
if (subject.evidence.identity?.concludedValue) {
|
|
629
|
+
delete subject.evidence.identity.concludedValue;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
} else if (
|
|
633
|
+
specVersion < 1.7 &&
|
|
634
|
+
subject.cryptoProperties?.assetType === "certificate" &&
|
|
635
|
+
subject.cryptoProperties.certificateProperties
|
|
636
|
+
) {
|
|
637
|
+
const certificateProperties =
|
|
638
|
+
subject.cryptoProperties.certificateProperties;
|
|
639
|
+
if (
|
|
640
|
+
!certificateProperties.certificateExtension &&
|
|
641
|
+
certificateProperties.certificateFileExtension
|
|
642
|
+
) {
|
|
643
|
+
certificateProperties.certificateExtension =
|
|
644
|
+
certificateProperties.certificateFileExtension;
|
|
645
|
+
}
|
|
646
|
+
delete certificateProperties.serialNumber;
|
|
647
|
+
delete certificateProperties.certificateFileExtension;
|
|
648
|
+
delete certificateProperties.fingerprint;
|
|
649
|
+
}
|
|
650
|
+
Object.entries(subject).forEach(([key, value]) => {
|
|
651
|
+
downgradeSubjectForSpecVersion(value, specVersion, key);
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function applySpecVersionCompatibility(bomJson, options) {
|
|
656
|
+
const requestedSpecVersion = resolveSpecVersionForCompatibility(
|
|
657
|
+
bomJson,
|
|
658
|
+
options,
|
|
659
|
+
);
|
|
660
|
+
const normalizedSpecVersion =
|
|
661
|
+
toCycloneDxSpecVersionString(requestedSpecVersion);
|
|
662
|
+
if (!normalizedSpecVersion) {
|
|
663
|
+
thoughtLog(
|
|
664
|
+
"Skipping CycloneDX specVersion compatibility updates for malformed explicit specVersion.",
|
|
665
|
+
{
|
|
666
|
+
specVersion: requestedSpecVersion,
|
|
667
|
+
},
|
|
668
|
+
);
|
|
669
|
+
return bomJson;
|
|
670
|
+
}
|
|
671
|
+
const specVersion = normalizeCycloneDxSpecVersion(normalizedSpecVersion);
|
|
672
|
+
if (specVersion < 1.7) {
|
|
673
|
+
downgradeSubjectForSpecVersion(bomJson, specVersion);
|
|
674
|
+
} else if (isCycloneDx20SpecVersion(specVersion)) {
|
|
675
|
+
upgradeSubjectForSpecVersion(bomJson, specVersion);
|
|
676
|
+
}
|
|
677
|
+
return setCycloneDxFormat(bomJson, normalizedSpecVersion);
|
|
678
|
+
}
|
|
679
|
+
|
|
295
680
|
/**
|
|
296
681
|
* Filter and enhance BOM post generation.
|
|
297
682
|
*
|
|
298
683
|
* @param {Object} bomNSData BOM with namespaces object
|
|
299
684
|
* @param {Object} options CLI options
|
|
685
|
+
* @param {string} [filePath] Source path used for formulation and metadata context
|
|
300
686
|
*
|
|
301
687
|
* @returns {Object} Modified bomNSData
|
|
302
688
|
*/
|
|
@@ -312,6 +698,7 @@ export function postProcess(bomNSData, options, filePath) {
|
|
|
312
698
|
bomNSData.bomJson = filterBom(jsonPayload, options);
|
|
313
699
|
bomNSData.bomJson = applyStandards(bomNSData.bomJson, options);
|
|
314
700
|
bomNSData.bomJson = applyMetadata(bomNSData.bomJson, options);
|
|
701
|
+
bomNSData.bomJson = applyContainerInventoryMetadata(bomNSData.bomJson);
|
|
315
702
|
bomNSData.bomJson = applyFormulation(
|
|
316
703
|
bomNSData.bomJson,
|
|
317
704
|
options,
|
|
@@ -319,10 +706,21 @@ export function postProcess(bomNSData, options, filePath) {
|
|
|
319
706
|
bomNSData.formulationList,
|
|
320
707
|
);
|
|
321
708
|
bomNSData.bomJson = applyReleaseNotes(bomNSData.bomJson, options, filePath);
|
|
709
|
+
bomNSData.bomJson = applySpecVersionCompatibility(bomNSData.bomJson, options);
|
|
322
710
|
bomNSData.bomJson = validateTlpClassification(bomNSData.bomJson, options);
|
|
323
711
|
// Support for automatic annotations
|
|
324
712
|
if (options.specVersion >= 1.6) {
|
|
325
|
-
|
|
713
|
+
setActivityContext({
|
|
714
|
+
bomMutation: "annotations",
|
|
715
|
+
capability: "bom-mutation",
|
|
716
|
+
projectType: "Annotations",
|
|
717
|
+
sourcePath: filePath || options.filePath || process.cwd(),
|
|
718
|
+
});
|
|
719
|
+
try {
|
|
720
|
+
bomNSData.bomJson = annotate(bomNSData.bomJson, options);
|
|
721
|
+
} finally {
|
|
722
|
+
resetActivityContext();
|
|
723
|
+
}
|
|
326
724
|
}
|
|
327
725
|
cleanupEnv(options);
|
|
328
726
|
cleanupTmpDir();
|