@cdxgen/cdxgen 13.6.0 → 13.7.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 +15 -8
- package/bin/audit.js +7 -4
- package/bin/cdxgen.js +256 -37
- package/bin/convert.js +146 -22
- package/bin/evinse.js +25 -3
- package/bin/repl.js +47 -3
- package/bin/sign.js +211 -34
- package/bin/tracebom.js +18 -6
- package/bin/validate.js +18 -3
- package/bin/verify.js +113 -5
- package/data/behavior-taxonomy.schema.json +1491 -0
- package/data/cyclonedx-2.0-bundled.schema.json +18606 -5528
- package/data/cyclonedx-2.0-cryptography-defs.schema.json +857 -0
- package/data/pqc-migration.json +239 -0
- package/data/remediations.json +52 -0
- package/data/rules/cbom-security.yaml +4 -3
- package/data/rules/package-integrity.yaml +28 -0
- package/lib/audit/index.js +25 -3
- package/lib/cli/bomAssembly.js +80 -1
- package/lib/cli/cliOptions.js +121 -17
- package/lib/cli/index.js +609 -898
- package/lib/cli/jsBom.js +3 -0
- package/lib/cli/jvmBom.js +365 -40
- package/lib/cli/managedBom.js +19 -3
- package/lib/cli/nativeBom.js +10 -0
- package/lib/core/env.js +26 -0
- package/lib/core/fs.js +126 -3
- package/lib/core/globPatterns.js +218 -0
- package/lib/core/processTree.js +99 -12
- package/lib/core/projectConfig.js +332 -25
- package/lib/core/toolRegistry.js +15 -1
- package/lib/ecosystems/denoutils.js +2 -2
- package/lib/ecosystems/golem.js +3 -8
- package/lib/ecosystems/gradleutils.js +1082 -6
- package/lib/ecosystems/jsEvidence.js +90 -38
- package/lib/ecosystems/kosi.js +3 -5
- package/lib/ecosystems/npmVersionResolution.js +376 -0
- package/lib/ecosystems/parsers-python.js +89 -28
- package/lib/ecosystems/parsers-rust.js +117 -15
- package/lib/ecosystems/remote/huggingface.js +22 -0
- package/lib/ecosystems/rusi.js +2 -4
- package/lib/ecosystems/scalasem.js +2 -1
- package/lib/evinser/evinser.js +196 -34
- package/lib/evinser/scalasem.js +5 -1
- package/lib/helpers/agenticToolutils.js +112 -1
- package/lib/helpers/bomSigner.js +43 -9
- package/lib/helpers/jssSigner.js +1276 -0
- package/lib/inventory/agentBlueprint.js +193 -0
- package/lib/inventory/agentToolNames.js +145 -0
- package/lib/inventory/aiCollector.js +153 -7
- package/lib/inventory/aiInventory.js +44 -8
- package/lib/inventory/aiProviders.js +324 -0
- package/lib/inventory/bomUtils.js +8 -1
- package/lib/inventory/ciParsers/githubActionsLock.js +821 -0
- package/lib/inventory/citations.js +31 -2
- package/lib/inventory/communityAiConfigParser.js +978 -80
- package/lib/inventory/cyclonedx20.js +152 -0
- package/lib/inventory/cyclonedx20Common.js +286 -0
- package/lib/inventory/cyclonedx20Derived.js +159 -0
- package/lib/inventory/cyclonedx20Parties.js +552 -0
- package/lib/inventory/cyclonedx20Read.js +768 -0
- package/lib/inventory/cyclonedx20ReadCommon.js +374 -0
- package/lib/inventory/cyclonedx20ReadComponents.js +692 -0
- package/lib/inventory/cyclonedx20ReadContext.js +380 -0
- package/lib/inventory/cyclonedx20ReadEvidence.js +466 -0
- package/lib/inventory/cyclonedx20ReadSections.js +599 -0
- package/lib/inventory/cyclonedx20Render.js +766 -0
- package/lib/inventory/cyclonedx20RenderComponents.js +716 -0
- package/lib/inventory/cyclonedx20RenderDefinitions.js +405 -0
- package/lib/inventory/cyclonedx20RenderEvidence.js +726 -0
- package/lib/inventory/cyclonedx20RenderHardware.js +203 -0
- package/lib/inventory/cyclonedx20RenderPatents.js +174 -0
- package/lib/inventory/cyclonedx20RenderRefIds.js +314 -0
- package/lib/inventory/cyclonedx20RenderSections.js +499 -0
- package/lib/inventory/cyclonedx20Tables.js +594 -0
- package/lib/inventory/deps.js +106 -13
- package/lib/inventory/dosai.js +236 -1
- package/lib/inventory/dosaiParsers.js +3 -6
- package/lib/inventory/dotnetRuntime.js +164 -14
- package/lib/inventory/ecosystemFormulationParsers.js +979 -0
- package/lib/inventory/envcontext.js +132 -38
- package/lib/inventory/evidenceUtils.js +2 -1
- package/lib/inventory/formulationParsers.js +95 -13
- package/lib/inventory/hbom.js +45 -6
- package/lib/inventory/hbomLoader.js +166 -0
- package/lib/inventory/mcpConfigParser.js +5 -0
- package/lib/inventory/occurrenceKey.js +90 -0
- package/lib/inventory/pqcMigration.js +426 -0
- package/lib/inventory/rustFormulationParser.js +21 -10
- package/lib/inventory/source.js +65 -6
- package/lib/parsers/huggingfaceManifest.js +9 -1
- package/lib/parsers/huggingfaceUtils.js +18 -0
- package/lib/parsers/kiroPermissions.js +166 -0
- package/lib/server/server.js +36 -34
- package/lib/stages/postgen/postgen.js +41 -1
- package/lib/stages/postgen/specVersionCompat.js +76 -0
- package/lib/validator/bomValidator.js +8 -1
- package/lib/validator/complianceEngine.js +30 -3
- package/lib/validator/complianceRules.js +1416 -4
- package/lib/validator/generated/validate-2.0.mjs +57554 -13851
- package/lib/validator/index.js +48 -7
- package/lib/validator/reporters/json.js +6 -0
- package/package.json +9 -9
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +6 -2
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +16 -0
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +6 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +2 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +15 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/boundedFile.d.ts +30 -0
- package/types/lib/core/boundedFile.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +10 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +4 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/globPatterns.d.ts +70 -0
- package/types/lib/core/globPatterns.d.ts.map +1 -0
- package/types/lib/core/paths.d.ts +9 -0
- package/types/lib/core/paths.d.ts.map +1 -1
- package/types/lib/core/processTree.d.ts +16 -5
- package/types/lib/core/processTree.d.ts.map +1 -1
- package/types/lib/core/projectConfig.d.ts +47 -8
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/toolRegistry.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +248 -8
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +14 -1
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmVersionResolution.d.ts +119 -0
- package/types/lib/ecosystems/npmVersionResolution.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts +58 -16
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +61 -9
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/scalasem.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +53 -19
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +20 -3
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/helpers/jssSigner.d.ts +288 -0
- package/types/lib/helpers/jssSigner.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +303 -19
- package/types/lib/helpers/vsixutils.d.ts.map +1 -1
- package/types/lib/inventory/agentBlueprint.d.ts +70 -0
- package/types/lib/inventory/agentBlueprint.d.ts.map +1 -0
- package/types/lib/inventory/agentToolNames.d.ts +51 -0
- package/types/lib/inventory/agentToolNames.d.ts.map +1 -0
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/aiProviders.d.ts +85 -0
- package/types/lib/inventory/aiProviders.d.ts.map +1 -0
- package/types/lib/inventory/bomUtils.d.ts.map +1 -1
- package/types/lib/inventory/ciParsers/githubActionsLock.d.ts +101 -0
- package/types/lib/inventory/ciParsers/githubActionsLock.d.ts.map +1 -0
- package/types/lib/inventory/citations.d.ts +5 -0
- package/types/lib/inventory/citations.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/cyclonedx20.d.ts +99 -0
- package/types/lib/inventory/cyclonedx20.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Common.d.ts +102 -0
- package/types/lib/inventory/cyclonedx20Common.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Derived.d.ts +74 -0
- package/types/lib/inventory/cyclonedx20Derived.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Parties.d.ts +91 -0
- package/types/lib/inventory/cyclonedx20Parties.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Read.d.ts +51 -0
- package/types/lib/inventory/cyclonedx20Read.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadCommon.d.ts +69 -0
- package/types/lib/inventory/cyclonedx20ReadCommon.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadComponents.d.ts +77 -0
- package/types/lib/inventory/cyclonedx20ReadComponents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadContext.d.ts +118 -0
- package/types/lib/inventory/cyclonedx20ReadContext.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts +40 -0
- package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadSections.d.ts +45 -0
- package/types/lib/inventory/cyclonedx20ReadSections.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Render.d.ts +53 -0
- package/types/lib/inventory/cyclonedx20Render.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderComponents.d.ts +73 -0
- package/types/lib/inventory/cyclonedx20RenderComponents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts +29 -0
- package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts +110 -0
- package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderHardware.d.ts +42 -0
- package/types/lib/inventory/cyclonedx20RenderHardware.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderPatents.d.ts +26 -0
- package/types/lib/inventory/cyclonedx20RenderPatents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts +55 -0
- package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderSections.d.ts +45 -0
- package/types/lib/inventory/cyclonedx20RenderSections.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Tables.d.ts +134 -0
- package/types/lib/inventory/cyclonedx20Tables.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +35 -2
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +19 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dotnetRuntime.d.ts +51 -4
- package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -1
- package/types/lib/inventory/ecosystemFormulationParsers.d.ts +73 -0
- package/types/lib/inventory/ecosystemFormulationParsers.d.ts.map +1 -0
- package/types/lib/inventory/envcontext.d.ts +22 -6
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/hbom.d.ts +11 -0
- package/types/lib/inventory/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomLoader.d.ts +54 -0
- package/types/lib/inventory/hbomLoader.d.ts.map +1 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts +5 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -1
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/occurrenceKey.d.ts +52 -0
- package/types/lib/inventory/occurrenceKey.d.ts.map +1 -0
- package/types/lib/inventory/pqcMigration.d.ts +84 -0
- package/types/lib/inventory/pqcMigration.d.ts.map +1 -0
- package/types/lib/inventory/rustFormulationParser.d.ts +7 -2
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -1
- package/types/lib/inventory/scalaDedupe.d.ts +43 -0
- package/types/lib/inventory/scalaDedupe.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +15 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceManifest.d.ts +10 -1
- package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts +12 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/kiroPermissions.d.ts +62 -0
- package/types/lib/parsers/kiroPermissions.d.ts.map +1 -0
- package/types/lib/parsers/vscodeExtensionId.d.ts +48 -0
- package/types/lib/parsers/vscodeExtensionId.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +7 -0
- 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/postgen/specVersionCompat.d.ts +25 -0
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts +5 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceEngine.d.ts +4 -4
- package/types/lib/validator/complianceEngine.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts +14 -2
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +10 -0
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render the 1.x-shaped internal model as a CycloneDX 2.0 document: the
|
|
3
|
+
* document sections, reference integrity and the render report. The public
|
|
4
|
+
* entry points are re-exported from cyclonedx20.js.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { thoughtLog } from "../core/logger.js";
|
|
8
|
+
import {
|
|
9
|
+
clone,
|
|
10
|
+
collectDeclaredRefIds,
|
|
11
|
+
countDrop,
|
|
12
|
+
countRewrite,
|
|
13
|
+
countSignatureDrop,
|
|
14
|
+
countUnmapped,
|
|
15
|
+
createRenderStats,
|
|
16
|
+
isBomLink,
|
|
17
|
+
isNonEmptyString,
|
|
18
|
+
isObjectRecord,
|
|
19
|
+
referencesParty,
|
|
20
|
+
setDefined,
|
|
21
|
+
takeShadow,
|
|
22
|
+
} from "./cyclonedx20Common.js";
|
|
23
|
+
import { appendDerivedSections } from "./cyclonedx20Derived.js";
|
|
24
|
+
import { toCommonContact, toCommonEntity } from "./cyclonedx20Parties.js";
|
|
25
|
+
import {
|
|
26
|
+
convertComponent,
|
|
27
|
+
convertLicenses,
|
|
28
|
+
convertService,
|
|
29
|
+
convertTools,
|
|
30
|
+
} from "./cyclonedx20RenderComponents.js";
|
|
31
|
+
import {
|
|
32
|
+
convertStandards,
|
|
33
|
+
renderDeclarations,
|
|
34
|
+
} from "./cyclonedx20RenderDefinitions.js";
|
|
35
|
+
import {
|
|
36
|
+
derivedPartyDeclarations,
|
|
37
|
+
identityClaimContext,
|
|
38
|
+
} from "./cyclonedx20RenderEvidence.js";
|
|
39
|
+
import { convertPatents } from "./cyclonedx20RenderPatents.js";
|
|
40
|
+
import {
|
|
41
|
+
dedupeRefIds,
|
|
42
|
+
resolveAffectsRefs,
|
|
43
|
+
resolveDependencyRefs,
|
|
44
|
+
} from "./cyclonedx20RenderRefIds.js";
|
|
45
|
+
import {
|
|
46
|
+
convertFormulation,
|
|
47
|
+
renderAnnotations,
|
|
48
|
+
renderCitations,
|
|
49
|
+
renderCompositions,
|
|
50
|
+
renderVulnerabilities,
|
|
51
|
+
} from "./cyclonedx20RenderSections.js";
|
|
52
|
+
import {
|
|
53
|
+
CDX20_INTERNAL_SECTIONS_KEY,
|
|
54
|
+
FORMULA_COMPONENT_REFS_PROPERTY,
|
|
55
|
+
} from "./cyclonedx20Tables.js";
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Render metadata (#/$defs/cyclonedx-metadata-2.0/$defs/metadata). Every 1.x
|
|
59
|
+
* field except `component` (the inventory subject) and the deprecated
|
|
60
|
+
* `manufacture` has a 2.0 home; organizations and contacts rename `bom-ref`
|
|
61
|
+
* to `refId`. Key order follows the input.
|
|
62
|
+
*/
|
|
63
|
+
function renderMetadata(metadata, stats) {
|
|
64
|
+
if (!isObjectRecord(metadata)) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const { component: _component, tools, ...rest } = metadata;
|
|
68
|
+
const shadow = takeShadow(stats, "metadata", rest);
|
|
69
|
+
if (shadow) {
|
|
70
|
+
const restoredTools = convertTools(tools, stats);
|
|
71
|
+
if (restoredTools) {
|
|
72
|
+
shadow.tools = restoredTools;
|
|
73
|
+
}
|
|
74
|
+
return Object.keys(shadow).length ? shadow : undefined;
|
|
75
|
+
}
|
|
76
|
+
const converted = {};
|
|
77
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
78
|
+
// 1.x builders sometimes leave explicit `undefined` members (addMetadata's
|
|
79
|
+
// `supplier: undefined`); JSON drops them, so the render does too.
|
|
80
|
+
if (value === undefined || key === "component") {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (key === "tools") {
|
|
84
|
+
const tools = convertTools(value, stats);
|
|
85
|
+
if (tools) {
|
|
86
|
+
converted.tools = tools;
|
|
87
|
+
}
|
|
88
|
+
} else if (key === "supplier" || key === "manufacturer") {
|
|
89
|
+
const entity = toCommonEntity(value, stats);
|
|
90
|
+
if (entity) {
|
|
91
|
+
converted[key] = entity;
|
|
92
|
+
}
|
|
93
|
+
} else if (key === "authors" && Array.isArray(value)) {
|
|
94
|
+
const authors = value
|
|
95
|
+
.map((contact) => toCommonContact(contact, stats))
|
|
96
|
+
.filter(Boolean);
|
|
97
|
+
if (authors.length) {
|
|
98
|
+
converted.authors = authors;
|
|
99
|
+
}
|
|
100
|
+
} else if (key === "licenses") {
|
|
101
|
+
converted.licenses = convertLicenses(value, stats) ?? clone(value);
|
|
102
|
+
} else if (
|
|
103
|
+
[
|
|
104
|
+
"timestamp",
|
|
105
|
+
"lifecycles",
|
|
106
|
+
"distributionConstraints",
|
|
107
|
+
"properties",
|
|
108
|
+
].includes(key)
|
|
109
|
+
) {
|
|
110
|
+
converted[key] = clone(value);
|
|
111
|
+
} else {
|
|
112
|
+
countDrop(stats, `metadata.${key}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return Object.keys(converted).length ? converted : undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Render the inventory
|
|
120
|
+
* (#/$defs/cyclonedx-component-2.0/$defs/inventory, required
|
|
121
|
+
* `[subject, components]`). Root components and services become the
|
|
122
|
+
* inventory's components; without a subject component (cbom/aibom style) the
|
|
123
|
+
* entries are returned for the definitions registry instead.
|
|
124
|
+
*/
|
|
125
|
+
function renderInventory(source, stats) {
|
|
126
|
+
const subject = isObjectRecord(source.metadata?.component)
|
|
127
|
+
? source.metadata.component
|
|
128
|
+
: undefined;
|
|
129
|
+
const subjectRef = isNonEmptyString(subject?.["bom-ref"])
|
|
130
|
+
? subject["bom-ref"]
|
|
131
|
+
: undefined;
|
|
132
|
+
const context = {
|
|
133
|
+
hasSubject: Boolean(subject),
|
|
134
|
+
subjectRef,
|
|
135
|
+
inventoryRefId: subjectRef ? `inventory-${subjectRef}` : undefined,
|
|
136
|
+
subjectlessEntries: [],
|
|
137
|
+
};
|
|
138
|
+
const entries = [
|
|
139
|
+
...(Array.isArray(source.components)
|
|
140
|
+
? source.components.map((c) => convertComponent(c, stats))
|
|
141
|
+
: []),
|
|
142
|
+
...(Array.isArray(source.services)
|
|
143
|
+
? source.services.map((s) => convertService(s, stats))
|
|
144
|
+
: []),
|
|
145
|
+
];
|
|
146
|
+
if (!subject) {
|
|
147
|
+
context.subjectlessEntries = entries;
|
|
148
|
+
return context;
|
|
149
|
+
}
|
|
150
|
+
// A document read from 2.0 keeps its inventory's own members (refId,
|
|
151
|
+
// properties, signatures); the renderer otherwise derives the refId.
|
|
152
|
+
const own = takeShadow(stats, "inventory", subject);
|
|
153
|
+
if (own) {
|
|
154
|
+
context.inventoryRefId = isNonEmptyString(own.refId)
|
|
155
|
+
? own.refId
|
|
156
|
+
: undefined;
|
|
157
|
+
}
|
|
158
|
+
const inventory = {
|
|
159
|
+
subject: convertComponent(subject, stats),
|
|
160
|
+
components: entries,
|
|
161
|
+
};
|
|
162
|
+
if (context.inventoryRefId) {
|
|
163
|
+
inventory.refId = context.inventoryRefId;
|
|
164
|
+
}
|
|
165
|
+
if (own) {
|
|
166
|
+
const { refId: _refId, ...members } = own;
|
|
167
|
+
Object.assign(inventory, members);
|
|
168
|
+
}
|
|
169
|
+
context.inventories = [inventory];
|
|
170
|
+
return context;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Render dependencies (#/$defs/cyclonedx-dependency-2.0/$defs/dependency,
|
|
175
|
+
* required `[ref]`); refs are refIds now, values unchanged. An empty
|
|
176
|
+
* `dependsOn` says the component has no dependencies (a missing one says
|
|
177
|
+
* nothing), so it is kept. References that resolve to nothing are dropped
|
|
178
|
+
* once every refId is known (resolveDependencyRefs).
|
|
179
|
+
*/
|
|
180
|
+
function renderDependencies(dependencies, stats) {
|
|
181
|
+
if (!Array.isArray(dependencies) || !dependencies.length) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
return dependencies
|
|
185
|
+
.filter((dep) => {
|
|
186
|
+
const valid = isObjectRecord(dep) && typeof dep.ref === "string";
|
|
187
|
+
if (!valid) {
|
|
188
|
+
countDrop(stats, "dependency (no ref)");
|
|
189
|
+
}
|
|
190
|
+
return valid;
|
|
191
|
+
})
|
|
192
|
+
.map((dep) => {
|
|
193
|
+
const converted = { ref: dep.ref };
|
|
194
|
+
if (Array.isArray(dep.dependsOn)) {
|
|
195
|
+
converted.dependsOn = [...dep.dependsOn];
|
|
196
|
+
}
|
|
197
|
+
if (Array.isArray(dep.provides)) {
|
|
198
|
+
converted.provides = [...dep.provides];
|
|
199
|
+
}
|
|
200
|
+
return converted;
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 1.x definitions members (and the components and services the internal
|
|
205
|
+
// model may hold there) that renderOwnDefinitions converts itself.
|
|
206
|
+
const DEFINITION_MEMBERS = new Set([
|
|
207
|
+
"components",
|
|
208
|
+
"services",
|
|
209
|
+
"parties",
|
|
210
|
+
"standards",
|
|
211
|
+
"patents",
|
|
212
|
+
]);
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Convert the 1.x definitions members: standards and patents take their 2.0
|
|
216
|
+
* form, components, services and parties are left to renderOwnDefinitions,
|
|
217
|
+
* and any other member is counted.
|
|
218
|
+
*/
|
|
219
|
+
function convertDefinitionMembers(sourceDefinitions, stats) {
|
|
220
|
+
if (!isObjectRecord(sourceDefinitions)) {
|
|
221
|
+
return {};
|
|
222
|
+
}
|
|
223
|
+
const { components, services, parties } = sourceDefinitions;
|
|
224
|
+
const definitions = clone({ components, services, parties });
|
|
225
|
+
for (const key of Object.keys(definitions)) {
|
|
226
|
+
if (definitions[key] === undefined) {
|
|
227
|
+
delete definitions[key];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
setDefined(
|
|
231
|
+
definitions,
|
|
232
|
+
"standards",
|
|
233
|
+
convertStandards(sourceDefinitions.standards, stats),
|
|
234
|
+
);
|
|
235
|
+
setDefined(
|
|
236
|
+
definitions,
|
|
237
|
+
"patents",
|
|
238
|
+
convertPatents(sourceDefinitions.patents, stats),
|
|
239
|
+
);
|
|
240
|
+
countUnmapped(stats, "definitions", sourceDefinitions, DEFINITION_MEMBERS);
|
|
241
|
+
return definitions;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Assemble the renderer's own definitions
|
|
246
|
+
* (#/$defs/cyclonedx-definition-2.0/$defs/definitions): converted 1.x
|
|
247
|
+
* definitions, the entries of a subject-less document (the way upstream's
|
|
248
|
+
* valid-component-evidence-assertion-2.0.json declares its components) and
|
|
249
|
+
* the formula components the 2.0 formula has no field for, after the
|
|
250
|
+
* components a 2.0 read kept for 2.0 output. A subject-less document's
|
|
251
|
+
* formula components are counted and dropped instead: they would share
|
|
252
|
+
* definitions with the SBOM components, and 2.0 records no formula
|
|
253
|
+
* membership. 2.0 definitions have no `services`, so 1.x definition
|
|
254
|
+
* services become service components.
|
|
255
|
+
*/
|
|
256
|
+
function renderOwnDefinitions(
|
|
257
|
+
sourceDefinitions,
|
|
258
|
+
{ subjectlessEntries, hasSubject },
|
|
259
|
+
carriedComponents,
|
|
260
|
+
stats,
|
|
261
|
+
) {
|
|
262
|
+
const definitions = convertDefinitionMembers(sourceDefinitions, stats);
|
|
263
|
+
// Definitions components a 2.0 read kept for 2.0 output come first, the
|
|
264
|
+
// order the reader expects (see cyclonedx20Read.js).
|
|
265
|
+
const components = [
|
|
266
|
+
...(Array.isArray(carriedComponents) ? clone(carriedComponents) : []),
|
|
267
|
+
...(Array.isArray(definitions.components)
|
|
268
|
+
? definitions.components.map((c) => convertComponent(c, stats))
|
|
269
|
+
: []),
|
|
270
|
+
];
|
|
271
|
+
if (Array.isArray(definitions.services)) {
|
|
272
|
+
components.push(
|
|
273
|
+
...definitions.services.map((s) => convertService(s, stats)),
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
delete definitions.services;
|
|
277
|
+
components.push(...subjectlessEntries);
|
|
278
|
+
if (hasSubject) {
|
|
279
|
+
components.push(...stats.formulaComponents);
|
|
280
|
+
} else {
|
|
281
|
+
components.push(...subjectlessFormulaComponents(subjectlessEntries, stats));
|
|
282
|
+
}
|
|
283
|
+
// An empty list read from 2.0 stays, so the round trip keeps it.
|
|
284
|
+
if (components.length || Array.isArray(carriedComponents)) {
|
|
285
|
+
definitions.components = components;
|
|
286
|
+
} else {
|
|
287
|
+
delete definitions.components;
|
|
288
|
+
}
|
|
289
|
+
return definitions;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* The formula components of a subject-less document. Its SBOM entries live in
|
|
294
|
+
* definitions.components too, so each formula records which entries are its
|
|
295
|
+
* own in the FORMULA_COMPONENT_REFS_PROPERTY property (see
|
|
296
|
+
* addFormulaMembership); the reader puts them back. A formula component
|
|
297
|
+
* without a refId cannot be named there, and one whose refId is also an SBOM
|
|
298
|
+
* entry stays only in the SBOM; both are counted.
|
|
299
|
+
*/
|
|
300
|
+
function subjectlessFormulaComponents(subjectlessEntries, stats) {
|
|
301
|
+
const sbomRefIds = new Set(
|
|
302
|
+
subjectlessEntries
|
|
303
|
+
.map((entry) => entry?.refId)
|
|
304
|
+
.filter((refId) => isNonEmptyString(refId)),
|
|
305
|
+
);
|
|
306
|
+
const kept = new Set();
|
|
307
|
+
const components = [];
|
|
308
|
+
stats.formulaMembership = stats.formulaMembership.map((members = []) =>
|
|
309
|
+
members.filter((component) => {
|
|
310
|
+
if (!isNonEmptyString(component?.refId)) {
|
|
311
|
+
countDrop(
|
|
312
|
+
stats,
|
|
313
|
+
"formula component without a refId (subject-less document)",
|
|
314
|
+
);
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
if (sbomRefIds.has(component.refId)) {
|
|
318
|
+
countDrop(
|
|
319
|
+
stats,
|
|
320
|
+
"formula component that is also an SBOM component (subject-less document)",
|
|
321
|
+
);
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
if (!kept.has(component.refId)) {
|
|
325
|
+
kept.add(component.refId);
|
|
326
|
+
components.push(component);
|
|
327
|
+
}
|
|
328
|
+
return true;
|
|
329
|
+
}),
|
|
330
|
+
);
|
|
331
|
+
return components;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Record the components of each formula of a subject-less document in the
|
|
336
|
+
* formula's FORMULA_COMPONENT_REFS_PROPERTY property (a JSON array of
|
|
337
|
+
* refIds), since 2.0 has no formula member for them.
|
|
338
|
+
*/
|
|
339
|
+
function addFormulaMembership(formulation, stats) {
|
|
340
|
+
if (!Array.isArray(formulation)) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
formulation.forEach((formula, index) => {
|
|
344
|
+
const refIds = (stats.formulaMembership[index] || []).map(
|
|
345
|
+
(component) => component.refId,
|
|
346
|
+
);
|
|
347
|
+
if (!isObjectRecord(formula) || !refIds.length) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const properties = (
|
|
351
|
+
Array.isArray(formula.properties) ? formula.properties : []
|
|
352
|
+
).filter((property) => property?.name !== FORMULA_COMPONENT_REFS_PROPERTY);
|
|
353
|
+
properties.push({
|
|
354
|
+
name: FORMULA_COMPONENT_REFS_PROPERTY,
|
|
355
|
+
value: JSON.stringify(refIds),
|
|
356
|
+
});
|
|
357
|
+
formula.properties = properties;
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Annotation subjects are elementLinks
|
|
363
|
+
* (#/$defs/cyclonedx-common-2.0/$defs/elementLink): a refId declared in this
|
|
364
|
+
* document or a BOM-Link into another one. A 1.x subject naming the document
|
|
365
|
+
* serial number (cdxgen's own summary annotation) is retargeted to the
|
|
366
|
+
* inventory; any other subject that resolves to nothing is dropped. An
|
|
367
|
+
* annotation may end with no subjects, which the schema allows.
|
|
368
|
+
*/
|
|
369
|
+
function resolveAnnotationSubjects(annotations, context, declared, stats) {
|
|
370
|
+
for (const annotation of annotations || []) {
|
|
371
|
+
const subjects = [];
|
|
372
|
+
for (const subject of annotation.subjects) {
|
|
373
|
+
if (declared.has(subject) || isBomLink(subject)) {
|
|
374
|
+
subjects.push(subject);
|
|
375
|
+
} else if (
|
|
376
|
+
subject === context.serialNumber &&
|
|
377
|
+
context.inventoryRefId &&
|
|
378
|
+
declared.has(context.inventoryRefId)
|
|
379
|
+
) {
|
|
380
|
+
countRewrite(stats, "annotation subject (serial number → inventory)");
|
|
381
|
+
subjects.push(context.inventoryRefId);
|
|
382
|
+
} else {
|
|
383
|
+
countDrop(stats, "annotation subject (undeclared)");
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
annotation.subjects = [...new Set(subjects)];
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Drop composition targets that resolve to nothing, then empty entries. */
|
|
391
|
+
function resolveCompositionTargets(compositions, declared, stats) {
|
|
392
|
+
if (!compositions) {
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
const kept = [];
|
|
396
|
+
for (const composition of compositions) {
|
|
397
|
+
composition.targets = composition.targets.filter((target) => {
|
|
398
|
+
const resolves = declared.has(target) || isBomLink(target);
|
|
399
|
+
if (!resolves) {
|
|
400
|
+
countDrop(stats, "composition target (undeclared)");
|
|
401
|
+
}
|
|
402
|
+
return resolves;
|
|
403
|
+
});
|
|
404
|
+
if (composition.targets.length) {
|
|
405
|
+
kept.push(composition);
|
|
406
|
+
} else {
|
|
407
|
+
countDrop(stats, "composition (no declared target)");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return kept.length ? kept : undefined;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Declared parties: the document's own and carried parties, then each
|
|
415
|
+
* derived party (see derivedPartyDeclarations: the cdxgen party, and the
|
|
416
|
+
* party of the tool asserting the identifiers of another tool's BOM) that is
|
|
417
|
+
* not declared yet and that something in the finished document references
|
|
418
|
+
* (identifiers it asserts, identity evidence it is the asserter of). Each is
|
|
419
|
+
* declared at most once.
|
|
420
|
+
*/
|
|
421
|
+
function renderParties(ownParties, carriedParties, document, declared, stats) {
|
|
422
|
+
const parties = [
|
|
423
|
+
...(Array.isArray(ownParties) ? ownParties : []),
|
|
424
|
+
...carriedParties,
|
|
425
|
+
];
|
|
426
|
+
for (const party of derivedPartyDeclarations(stats.claims, declared)) {
|
|
427
|
+
if (
|
|
428
|
+
referencesParty(document, party.refId) &&
|
|
429
|
+
!parties.some((candidate) => candidate?.refId === party.refId)
|
|
430
|
+
) {
|
|
431
|
+
parties.push(party);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return parties.length ? parties : undefined;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Root required members (#/$defs properties specFormat, specVersion). */
|
|
438
|
+
function renderDocumentHeader(source) {
|
|
439
|
+
const out = {};
|
|
440
|
+
// A document read from 2.0 carries its own `$schema`, `$comment` and
|
|
441
|
+
// `ext:` extension members.
|
|
442
|
+
const root = source[CDX20_INTERNAL_SECTIONS_KEY]?.root;
|
|
443
|
+
if (isObjectRecord(root) && root.$schema !== undefined) {
|
|
444
|
+
out.$schema = root.$schema;
|
|
445
|
+
} else if (source.$schema !== undefined) {
|
|
446
|
+
out.$schema = source.$schema;
|
|
447
|
+
}
|
|
448
|
+
for (const [key, value] of Object.entries(isObjectRecord(root) ? root : {})) {
|
|
449
|
+
if (key !== "$schema") {
|
|
450
|
+
out[key] = clone(value);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
out.specFormat = "CycloneDX";
|
|
454
|
+
out.specVersion = "2.0";
|
|
455
|
+
if (source.serialNumber !== undefined) {
|
|
456
|
+
out.serialNumber = source.serialNumber;
|
|
457
|
+
}
|
|
458
|
+
if (source.version !== undefined) {
|
|
459
|
+
out.version = source.version;
|
|
460
|
+
}
|
|
461
|
+
return out;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Keys of the internal sections object that are not document sections.
|
|
465
|
+
const NON_SECTION_KEYS = Object.freeze(
|
|
466
|
+
new Set([
|
|
467
|
+
"definitions",
|
|
468
|
+
"definitionsParties",
|
|
469
|
+
"root",
|
|
470
|
+
"shadows",
|
|
471
|
+
"compositions",
|
|
472
|
+
]),
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
/** Copy the carried 2.0-only sections, except the definitions parts. */
|
|
476
|
+
function renderCarriedSections(out, carried) {
|
|
477
|
+
for (const [section, value] of Object.entries(carried)) {
|
|
478
|
+
if (!NON_SECTION_KEYS.has(section)) {
|
|
479
|
+
out[section] = clone(value);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Render the 1.x-shaped internal model as a CycloneDX 2.0 document and report
|
|
486
|
+
* what could not be carried. Pure: the input is never mutated and the output
|
|
487
|
+
* shares no objects with it. Deterministic: the same input renders to the
|
|
488
|
+
* same JSON.
|
|
489
|
+
*
|
|
490
|
+
* @param {object} internalBom BOM in the 1.x shape
|
|
491
|
+
* @param {{hardwareMapper?: Function, hardwareOptions?: {includeSensitiveIdentifiers?: boolean, identifierPolicy?: string}}} [context]
|
|
492
|
+
* What the render may use besides the model: `hardwareMapper` is the
|
|
493
|
+
* optional toCdx20HardwareComponent of @cdxgen/cdx-hbom and
|
|
494
|
+
* `hardwareOptions` its identifier options (see hbomLoader.js:
|
|
495
|
+
* hardwareRenderContext and cyclonedx20RenderHardware.js). The output
|
|
496
|
+
* depends only on the model and this context.
|
|
497
|
+
* @returns {{bomJson: object, report: {drops: Object<string, number>, rewrites: Object<string, number>, customSchemes: string[], droppedMethods: Array<{reason: string, technique: string}>}}}
|
|
498
|
+
* The 2.0 document, the counts of values the render could not carry
|
|
499
|
+
* (`drops`) and of values it rewrote without losing data (`rewrites`), by
|
|
500
|
+
* label
|
|
501
|
+
*/
|
|
502
|
+
export function renderCycloneDx20WithReport(internalBom, context = {}) {
|
|
503
|
+
const source = isObjectRecord(internalBom) ? internalBom : {};
|
|
504
|
+
const stats = createRenderStats(
|
|
505
|
+
source[CDX20_INTERNAL_SECTIONS_KEY]?.shadows,
|
|
506
|
+
identityClaimContext(source),
|
|
507
|
+
);
|
|
508
|
+
if (typeof context?.hardwareMapper === "function") {
|
|
509
|
+
stats.hardwareMapper = context.hardwareMapper;
|
|
510
|
+
stats.hardwareOptions = isObjectRecord(context.hardwareOptions)
|
|
511
|
+
? { ...context.hardwareOptions }
|
|
512
|
+
: {};
|
|
513
|
+
}
|
|
514
|
+
const { out, inventory, definitions } = renderOwnSections(source, stats);
|
|
515
|
+
// Inventories first: the subject of a self-scan keeps its members when it
|
|
516
|
+
// repeats the refId of the cdxgen tool in metadata.
|
|
517
|
+
dedupeRefIds(
|
|
518
|
+
[
|
|
519
|
+
{ section: "inventories", value: out.inventories },
|
|
520
|
+
{ section: "metadata", value: out.metadata },
|
|
521
|
+
{ section: "annotations", value: out.annotations },
|
|
522
|
+
{ section: "formulation", value: out.formulation },
|
|
523
|
+
{ section: "declarations", value: out.declarations },
|
|
524
|
+
{ section: "citations", value: out.citations },
|
|
525
|
+
{ section: "vulnerabilities", value: out.vulnerabilities },
|
|
526
|
+
{ section: "definitions", value: definitions },
|
|
527
|
+
],
|
|
528
|
+
stats,
|
|
529
|
+
);
|
|
530
|
+
// dedupeRefIds removes the definitions components an inventory declares
|
|
531
|
+
// too; a list left empty goes, unless a 2.0 read carried it.
|
|
532
|
+
if (
|
|
533
|
+
definitions.components?.length === 0 &&
|
|
534
|
+
!Array.isArray(source[CDX20_INTERNAL_SECTIONS_KEY]?.definitions?.components)
|
|
535
|
+
) {
|
|
536
|
+
delete definitions.components;
|
|
537
|
+
}
|
|
538
|
+
finalizeDocument(out, source, inventory, definitions, stats);
|
|
539
|
+
logRenderStats(stats);
|
|
540
|
+
return { bomJson: out, report: buildRenderReport(stats) };
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Root members of the 1.x model the render handles; any other is counted.
|
|
544
|
+
const ROOT_MEMBERS = Object.freeze(
|
|
545
|
+
new Set([
|
|
546
|
+
"$schema",
|
|
547
|
+
"bomFormat",
|
|
548
|
+
"specFormat",
|
|
549
|
+
"specVersion",
|
|
550
|
+
"serialNumber",
|
|
551
|
+
"version",
|
|
552
|
+
"metadata",
|
|
553
|
+
"components",
|
|
554
|
+
"services",
|
|
555
|
+
"externalReferences",
|
|
556
|
+
"dependencies",
|
|
557
|
+
"compositions",
|
|
558
|
+
"vulnerabilities",
|
|
559
|
+
"annotations",
|
|
560
|
+
"formulation",
|
|
561
|
+
"declarations",
|
|
562
|
+
"definitions",
|
|
563
|
+
"citations",
|
|
564
|
+
"properties",
|
|
565
|
+
"signature",
|
|
566
|
+
CDX20_INTERNAL_SECTIONS_KEY,
|
|
567
|
+
]),
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Render every section the 1.x model has, in document order. Definitions
|
|
572
|
+
* are returned separately: they are merged with the carried 2.0 sections
|
|
573
|
+
* last.
|
|
574
|
+
*/
|
|
575
|
+
function renderOwnSections(source, stats) {
|
|
576
|
+
const out = renderDocumentHeader(source);
|
|
577
|
+
setDefined(out, "metadata", renderMetadata(source.metadata, stats));
|
|
578
|
+
const inventory = renderInventory(source, stats);
|
|
579
|
+
setDefined(out, "inventories", inventory.inventories);
|
|
580
|
+
setDefined(
|
|
581
|
+
out,
|
|
582
|
+
"dependencies",
|
|
583
|
+
renderDependencies(source.dependencies, stats),
|
|
584
|
+
);
|
|
585
|
+
setDefined(
|
|
586
|
+
out,
|
|
587
|
+
"compositions",
|
|
588
|
+
renderCompositions(
|
|
589
|
+
source.compositions,
|
|
590
|
+
inventory,
|
|
591
|
+
source[CDX20_INTERNAL_SECTIONS_KEY]?.compositions,
|
|
592
|
+
stats,
|
|
593
|
+
),
|
|
594
|
+
);
|
|
595
|
+
setDefined(out, "annotations", renderAnnotations(source.annotations, stats));
|
|
596
|
+
if (source.formulation !== undefined) {
|
|
597
|
+
out.formulation = convertFormulation(source.formulation, stats);
|
|
598
|
+
}
|
|
599
|
+
setDefined(
|
|
600
|
+
out,
|
|
601
|
+
"declarations",
|
|
602
|
+
renderDeclarations(source.declarations, stats),
|
|
603
|
+
);
|
|
604
|
+
// After the formulation, which collects the formula components.
|
|
605
|
+
const definitions = renderOwnDefinitions(
|
|
606
|
+
source.definitions,
|
|
607
|
+
inventory,
|
|
608
|
+
source[CDX20_INTERNAL_SECTIONS_KEY]?.definitions?.components,
|
|
609
|
+
stats,
|
|
610
|
+
);
|
|
611
|
+
if (!inventory.hasSubject) {
|
|
612
|
+
addFormulaMembership(out.formulation, stats);
|
|
613
|
+
}
|
|
614
|
+
setDefined(
|
|
615
|
+
out,
|
|
616
|
+
"citations",
|
|
617
|
+
renderCitations(source.citations, inventory.hasSubject, stats),
|
|
618
|
+
);
|
|
619
|
+
setDefined(
|
|
620
|
+
out,
|
|
621
|
+
"vulnerabilities",
|
|
622
|
+
renderVulnerabilities(source.vulnerabilities, stats),
|
|
623
|
+
);
|
|
624
|
+
setDefined(out, "properties", clone(source.properties));
|
|
625
|
+
setDefined(out, "externalReferences", clone(source.externalReferences));
|
|
626
|
+
if (source.signature !== undefined) {
|
|
627
|
+
countSignatureDrop(stats, "signature");
|
|
628
|
+
}
|
|
629
|
+
countUnmapped(stats, "document", source, ROOT_MEMBERS);
|
|
630
|
+
return { out, inventory, definitions };
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Add the carried 2.0-only sections and the derived ones (post-quantum risks,
|
|
635
|
+
* agent parties and blueprint, see cyclonedx20Derived.js), resolve
|
|
636
|
+
* annotation subjects and composition targets against every declared refId,
|
|
637
|
+
* and declare the parties. A carried `definitions` object merges with the
|
|
638
|
+
* freshly assembled one so a round trip keeps both; carried parties append
|
|
639
|
+
* to the declared parties.
|
|
640
|
+
*/
|
|
641
|
+
function finalizeDocument(out, source, inventory, definitions, stats) {
|
|
642
|
+
const carried = isObjectRecord(source[CDX20_INTERNAL_SECTIONS_KEY])
|
|
643
|
+
? source[CDX20_INTERNAL_SECTIONS_KEY]
|
|
644
|
+
: {};
|
|
645
|
+
renderCarriedSections(out, carried);
|
|
646
|
+
const ownParties = definitions.parties;
|
|
647
|
+
delete definitions.parties;
|
|
648
|
+
// The carried definitions components were merged in renderOwnSections, so
|
|
649
|
+
// dedupeRefIds saw them.
|
|
650
|
+
const { components: _carriedComponents, ...carriedDefinitions } =
|
|
651
|
+
isObjectRecord(carried.definitions) ? carried.definitions : {};
|
|
652
|
+
const mergedDefinitions = {
|
|
653
|
+
...clone(carriedDefinitions),
|
|
654
|
+
...definitions,
|
|
655
|
+
};
|
|
656
|
+
const carriedParties = appendDerivedSections(
|
|
657
|
+
out,
|
|
658
|
+
Array.isArray(carried.definitionsParties)
|
|
659
|
+
? clone(carried.definitionsParties)
|
|
660
|
+
: [],
|
|
661
|
+
source,
|
|
662
|
+
);
|
|
663
|
+
const declared = collectDeclaredRefIds([
|
|
664
|
+
out,
|
|
665
|
+
mergedDefinitions,
|
|
666
|
+
ownParties,
|
|
667
|
+
carriedParties,
|
|
668
|
+
]);
|
|
669
|
+
resolveAnnotationSubjects(
|
|
670
|
+
out.annotations,
|
|
671
|
+
{ ...inventory, serialNumber: source.serialNumber },
|
|
672
|
+
declared,
|
|
673
|
+
stats,
|
|
674
|
+
);
|
|
675
|
+
const dependencies = resolveDependencyRefs(out.dependencies, declared, stats);
|
|
676
|
+
if (dependencies) {
|
|
677
|
+
out.dependencies = dependencies;
|
|
678
|
+
} else {
|
|
679
|
+
delete out.dependencies;
|
|
680
|
+
}
|
|
681
|
+
resolveAffectsRefs(out.vulnerabilities, declared, stats);
|
|
682
|
+
const compositions = resolveCompositionTargets(
|
|
683
|
+
out.compositions,
|
|
684
|
+
declared,
|
|
685
|
+
stats,
|
|
686
|
+
);
|
|
687
|
+
if (compositions) {
|
|
688
|
+
out.compositions = compositions;
|
|
689
|
+
} else {
|
|
690
|
+
delete out.compositions;
|
|
691
|
+
}
|
|
692
|
+
setDefined(
|
|
693
|
+
mergedDefinitions,
|
|
694
|
+
"parties",
|
|
695
|
+
renderParties(
|
|
696
|
+
ownParties,
|
|
697
|
+
carriedParties,
|
|
698
|
+
[out, mergedDefinitions],
|
|
699
|
+
declared,
|
|
700
|
+
stats,
|
|
701
|
+
),
|
|
702
|
+
);
|
|
703
|
+
if (Object.keys(mergedDefinitions).length) {
|
|
704
|
+
out.definitions = mergedDefinitions;
|
|
705
|
+
}
|
|
706
|
+
delete out[CDX20_INTERNAL_SECTIONS_KEY];
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Render the 1.x-shaped internal model as a CycloneDX 2.0 document. Pure: the
|
|
711
|
+
* input is never mutated and the output shares no objects with it. What the
|
|
712
|
+
* render could not carry is logged through thoughtLog; use
|
|
713
|
+
* renderCycloneDx20WithReport for the counts.
|
|
714
|
+
*
|
|
715
|
+
* @param {object} internalBom BOM in the 1.x shape
|
|
716
|
+
* @param {object} [context] See renderCycloneDx20WithReport
|
|
717
|
+
* @returns {object} BOM in the 2.0 shape
|
|
718
|
+
*/
|
|
719
|
+
export function renderCycloneDx20(internalBom, context = {}) {
|
|
720
|
+
return renderCycloneDx20WithReport(internalBom, context).bomJson;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/** The render counters as a plain, sorted, serializable report. */
|
|
724
|
+
function buildRenderReport(stats) {
|
|
725
|
+
const drops = {};
|
|
726
|
+
for (const label of Object.keys(stats.drops).sort()) {
|
|
727
|
+
drops[label] = stats.drops[label];
|
|
728
|
+
}
|
|
729
|
+
const rewrites = {};
|
|
730
|
+
for (const label of Object.keys(stats.rewrites).sort()) {
|
|
731
|
+
rewrites[label] = stats.rewrites[label];
|
|
732
|
+
}
|
|
733
|
+
return {
|
|
734
|
+
drops,
|
|
735
|
+
rewrites,
|
|
736
|
+
customSchemes: [...stats.customSchemes].sort(),
|
|
737
|
+
droppedMethods: stats.droppedMethods.map((method) => ({ ...method })),
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function logRenderStats(stats) {
|
|
742
|
+
if (stats.droppedMethods.length) {
|
|
743
|
+
thoughtLog(
|
|
744
|
+
`CycloneDX 2.0 render dropped ${stats.droppedMethods.length} identity method(s): ` +
|
|
745
|
+
JSON.stringify(stats.droppedMethods.slice(0, 10)),
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
if (stats.customSchemes.size) {
|
|
749
|
+
thoughtLog(
|
|
750
|
+
`CycloneDX 2.0 render used custom identity schemes: ${[...stats.customSchemes].join(", ")}`,
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
for (const [kind, counts] of [
|
|
754
|
+
["could not carry", stats.drops],
|
|
755
|
+
["rewrote", stats.rewrites],
|
|
756
|
+
]) {
|
|
757
|
+
const labels = Object.keys(counts).sort();
|
|
758
|
+
if (labels.length) {
|
|
759
|
+
thoughtLog(
|
|
760
|
+
`CycloneDX 2.0 render ${kind}: ${labels
|
|
761
|
+
.map((label) => `${label} ×${counts[label]}`)
|
|
762
|
+
.join(", ")}`,
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|