@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,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CycloneDX 2.0 rendering and reading.
|
|
3
|
+
*
|
|
4
|
+
* cdxgen assembles every BOM in the CycloneDX 1.x shape and converts to the
|
|
5
|
+
* requested spec version at the end of the pipeline (see
|
|
6
|
+
* lib/stages/postgen/specVersionCompat.js). The 2.0 draft is a structural
|
|
7
|
+
* rewrite rather than an additive change, so the conversion lives here, as two
|
|
8
|
+
* pure functions plus small helpers, instead of inside the compat walker:
|
|
9
|
+
*
|
|
10
|
+
* - `renderCycloneDx20(internalBom, context)` turns the 1.x-shaped internal
|
|
11
|
+
* model into a 2.0 document. It never mutates its input, and its output
|
|
12
|
+
* depends only on the model and the optional context (the cdx-hbom
|
|
13
|
+
* hardware mapper, see hbomLoader.js: hardwareRenderContext).
|
|
14
|
+
* `renderCycloneDx20WithReport(internalBom, context)` does the same and
|
|
15
|
+
* also returns the counts of everything the render could not carry.
|
|
16
|
+
* - `readCycloneDx20(bom20)` turns a 2.0 document into the internal 1.x-shaped
|
|
17
|
+
* model, reporting everything it could not carry in `losses`.
|
|
18
|
+
* - `loadBomForAnalysis(bomJson)` is the single entry point every BOM reader
|
|
19
|
+
* uses; it returns the document as is for 1.x and calls readCycloneDx20 for
|
|
20
|
+
* 2.0, so no reader learns the 2.0 shape.
|
|
21
|
+
* - `getInternalBom(bomNSData)` and `refreshRenderedBom(bomNSData, options)`
|
|
22
|
+
* are the generation-side half: postProcess keeps the 1.x model on
|
|
23
|
+
* `bomNSData.internalBomJson` at 2.0, and the writer re-renders once after
|
|
24
|
+
* the last mutation.
|
|
25
|
+
*
|
|
26
|
+
* Every field name below is quoted from the pinned draft schema
|
|
27
|
+
* (data/cyclonedx-2.0-bundled.schema.json, CycloneDX/specification 2.0-dev at
|
|
28
|
+
* ea8c3605829d1da13339a0790b6da73548e42a6b); citations give the `$defs` path.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { thoughtLog } from "../core/logger.js";
|
|
32
|
+
import { isCycloneDx20SpecVersion } from "./bomUtils.js";
|
|
33
|
+
import { isObjectRecord } from "./cyclonedx20Common.js";
|
|
34
|
+
import { readCycloneDx20 } from "./cyclonedx20Read.js";
|
|
35
|
+
import { isMarkedInternalModel } from "./cyclonedx20ReadContext.js";
|
|
36
|
+
import { renderCycloneDx20 } from "./cyclonedx20Render.js";
|
|
37
|
+
import { CDX20_INTERNAL_SECTIONS_KEY } from "./cyclonedx20Tables.js";
|
|
38
|
+
|
|
39
|
+
export { readCycloneDx20 } from "./cyclonedx20Read.js";
|
|
40
|
+
export {
|
|
41
|
+
renderCycloneDx20,
|
|
42
|
+
renderCycloneDx20WithReport,
|
|
43
|
+
} from "./cyclonedx20Render.js";
|
|
44
|
+
export {
|
|
45
|
+
CDX20_INTERNAL_SECTIONS_KEY,
|
|
46
|
+
CDXGEN_PARTY_REF_ID,
|
|
47
|
+
DEPENDENCY_TRACK_CDX20_ERROR,
|
|
48
|
+
} from "./cyclonedx20Tables.js";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Load any CycloneDX BOM for analysis: the document as is for 1.x, the
|
|
52
|
+
* internal 1.x-shaped model for 2.0.
|
|
53
|
+
*
|
|
54
|
+
* A document whose specVersion is 2.0 or later, or which has a specFormat, is
|
|
55
|
+
* read as 2.0, and the reader reports stray 1.x members such as
|
|
56
|
+
* `components` as losses. A model the 2.0 reader or the 2.0 compatibility
|
|
57
|
+
* pass produced carries the internal model mark (markInternalModel) and is
|
|
58
|
+
* returned as is; cdxgen hands that model to the evidence analysis in memory
|
|
59
|
+
* (evinse's `inputBomJson`), because the mark does not survive JSON. The
|
|
60
|
+
* internal sections key is never trusted on a 1.x document: the returned
|
|
61
|
+
* copy leaves it out, so it cannot reach a 2.0 render.
|
|
62
|
+
*
|
|
63
|
+
* @param {object} bomJson Parsed CycloneDX BOM
|
|
64
|
+
* @returns {{internalBom: object, specVersion: string, losses: Array<{pointer: string, reason: string}>}}
|
|
65
|
+
* @throws {Error} With a short message when the 2.0 reader fails on the
|
|
66
|
+
* document
|
|
67
|
+
*/
|
|
68
|
+
export function loadBomForAnalysis(bomJson) {
|
|
69
|
+
if (!isObjectRecord(bomJson)) {
|
|
70
|
+
return { internalBom: bomJson, specVersion: "1.5", losses: [] };
|
|
71
|
+
}
|
|
72
|
+
const specVersion = `${bomJson.specVersion ?? ""}`.trim() || "1.5";
|
|
73
|
+
if (isMarkedInternalModel(bomJson)) {
|
|
74
|
+
return { internalBom: bomJson, specVersion, losses: [] };
|
|
75
|
+
}
|
|
76
|
+
if (
|
|
77
|
+
isCycloneDx20SpecVersion(specVersion) ||
|
|
78
|
+
bomJson.specFormat !== undefined
|
|
79
|
+
) {
|
|
80
|
+
let read;
|
|
81
|
+
try {
|
|
82
|
+
read = readCycloneDx20(bomJson);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Unable to read the CycloneDX 2.0 document: ${err?.message || err}`,
|
|
86
|
+
{ cause: err },
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
internalBom: read.internalBom,
|
|
91
|
+
specVersion: "2.0",
|
|
92
|
+
losses: read.losses,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (Object.hasOwn(bomJson, CDX20_INTERNAL_SECTIONS_KEY)) {
|
|
96
|
+
thoughtLog(
|
|
97
|
+
`Ignoring the ${CDX20_INTERNAL_SECTIONS_KEY} member of a CycloneDX ${specVersion} document; only the 2.0 reader sets it.`,
|
|
98
|
+
);
|
|
99
|
+
const { [CDX20_INTERNAL_SECTIONS_KEY]: _ignored, ...internalBom } = bomJson;
|
|
100
|
+
return { internalBom, specVersion, losses: [] };
|
|
101
|
+
}
|
|
102
|
+
return { internalBom: bomJson, specVersion, losses: [] };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The one-line notice a reader prints when loading a 2.0 document lost
|
|
107
|
+
* data: the number of losses, with the details left to thoughtLog.
|
|
108
|
+
*
|
|
109
|
+
* @param {Array<{pointer: string, reason: string}>} losses Reader losses
|
|
110
|
+
* @returns {string} The notice
|
|
111
|
+
*/
|
|
112
|
+
export function formatReadLossNotice(losses) {
|
|
113
|
+
const count = losses?.length || 0;
|
|
114
|
+
return `CycloneDX 2.0 input: reading it into the 1.x model reported ${count} loss${count === 1 ? "" : "es"}; those parts were not analysed. Set CDXGEN_THINK_MODE=true to list them.`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Return the 1.x-shaped model of a generation run. At 1.x the rendered BOM is
|
|
119
|
+
* the internal model, so this always yields something readers can walk.
|
|
120
|
+
*
|
|
121
|
+
* @param {{bomJson?: object, internalBomJson?: object}} bomNSData
|
|
122
|
+
* @returns {object} The 1.x-shaped BOM
|
|
123
|
+
*/
|
|
124
|
+
export function getInternalBom(bomNSData) {
|
|
125
|
+
return bomNSData?.internalBomJson ?? bomNSData?.bomJson;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Re-render the 2.0 document after a post-postProcess mutation of the
|
|
130
|
+
* internal model, with the render context postProcess used
|
|
131
|
+
* (`bomNSData.renderContext`). A no-op at 1.x, which keeps those versions
|
|
132
|
+
* byte-identical.
|
|
133
|
+
*
|
|
134
|
+
* @param {{bomJson?: object, internalBomJson?: object, renderContext?: object}} bomNSData
|
|
135
|
+
* @param {object} options CLI options carrying `specVersion`
|
|
136
|
+
* @returns {{bomJson?: object, internalBomJson?: object}} The same bomNSData
|
|
137
|
+
*/
|
|
138
|
+
export function refreshRenderedBom(bomNSData, options) {
|
|
139
|
+
if (
|
|
140
|
+
!bomNSData?.internalBomJson ||
|
|
141
|
+
!isCycloneDx20SpecVersion(
|
|
142
|
+
options?.specVersion ?? bomNSData.bomJson?.specVersion,
|
|
143
|
+
)
|
|
144
|
+
) {
|
|
145
|
+
return bomNSData;
|
|
146
|
+
}
|
|
147
|
+
bomNSData.bomJson = renderCycloneDx20(
|
|
148
|
+
bomNSData.internalBomJson,
|
|
149
|
+
bomNSData.renderContext,
|
|
150
|
+
);
|
|
151
|
+
return bomNSData;
|
|
152
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small shared helpers for the CycloneDX 2.0 renderer and reader: type
|
|
3
|
+
* guards, cloning, render counters and the environment-variable shape change.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
|
|
8
|
+
/** True for a plain object (not null, not an array). */
|
|
9
|
+
export const isObjectRecord = (value) =>
|
|
10
|
+
Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
11
|
+
|
|
12
|
+
/** True for a string with at least one character. */
|
|
13
|
+
export const isNonEmptyString = (value) =>
|
|
14
|
+
typeof value === "string" && value.length > 0;
|
|
15
|
+
|
|
16
|
+
/** structuredClone that passes `undefined` through. */
|
|
17
|
+
export const clone = (value) =>
|
|
18
|
+
value === undefined ? undefined : structuredClone(value);
|
|
19
|
+
|
|
20
|
+
// bomLinkElementType / bomLinkDocumentType values point into another document
|
|
21
|
+
// (#/$defs/cyclonedx-common-2.0/$defs/bomLink), so they never resolve here.
|
|
22
|
+
export const isBomLink = (value) =>
|
|
23
|
+
typeof value === "string" && value.startsWith("urn:cdx:");
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A fresh, empty set of render counters.
|
|
27
|
+
*
|
|
28
|
+
* @param {object} [shadows] 2.0 originals kept by readCycloneDx20 (see
|
|
29
|
+
* takeShadow)
|
|
30
|
+
* @param {{toolRefId?: string, timestamp?: string}} [claims] What identity
|
|
31
|
+
* claims are attributed with (see identityClaimContext)
|
|
32
|
+
* @returns {object} Render counters
|
|
33
|
+
*/
|
|
34
|
+
export function createRenderStats(shadows, claims) {
|
|
35
|
+
return {
|
|
36
|
+
claims: isObjectRecord(claims) ? claims : {},
|
|
37
|
+
customSchemes: new Set(),
|
|
38
|
+
droppedMethods: [],
|
|
39
|
+
drops: {},
|
|
40
|
+
rewrites: {},
|
|
41
|
+
formulaComponents: [],
|
|
42
|
+
formulaMembership: [],
|
|
43
|
+
shadows: isObjectRecord(shadows) ? shadows : {},
|
|
44
|
+
shadowUse: new Map(),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The key a 1.x value is shadowed under: the SHA-256 of its JSON. The
|
|
50
|
+
* reader and the renderer serialize the same object the same way, so an
|
|
51
|
+
* unchanged value finds its 2.0 original again.
|
|
52
|
+
*
|
|
53
|
+
* @param {*} value 1.x value
|
|
54
|
+
* @returns {string} Hex digest
|
|
55
|
+
*/
|
|
56
|
+
export function shadowKey(value) {
|
|
57
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The 2.0 original readCycloneDx20 kept for this unchanged 1.x value, as a
|
|
62
|
+
* fresh clone, or undefined. Identical 1.x values kept for several originals
|
|
63
|
+
* get them back in document order. Never mutates the stored shadows.
|
|
64
|
+
*
|
|
65
|
+
* @param {object} stats Render counters from createRenderStats
|
|
66
|
+
* @param {string} kind Shadow namespace (component, metadata, …)
|
|
67
|
+
* @param {*} value 1.x value about to be rendered
|
|
68
|
+
* @returns {*} The 2.0 original or undefined
|
|
69
|
+
*/
|
|
70
|
+
export function takeShadow(stats, kind, value) {
|
|
71
|
+
const byKind = stats.shadows[kind];
|
|
72
|
+
if (!isObjectRecord(byKind) || value === undefined) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
const key = shadowKey(value);
|
|
76
|
+
const originals = byKind[key];
|
|
77
|
+
if (!Array.isArray(originals)) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const useKey = `${kind}:${key}`;
|
|
81
|
+
const used = stats.shadowUse.get(useKey) || 0;
|
|
82
|
+
if (used >= originals.length) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
stats.shadowUse.set(useKey, used + 1);
|
|
86
|
+
return clone(originals[used]);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The single 2.0 original readCycloneDx20 kept for this unchanged 1.x value,
|
|
91
|
+
* as a fresh clone, or undefined. Unlike takeShadow nothing is consumed: the
|
|
92
|
+
* reader registers a key here only when every entry it read under that key
|
|
93
|
+
* had the same original, so the lookup does not depend on render order.
|
|
94
|
+
*
|
|
95
|
+
* @param {object} stats Render counters from createRenderStats
|
|
96
|
+
* @param {string} kind Shadow namespace (identity)
|
|
97
|
+
* @param {*} value 1.x value about to be rendered
|
|
98
|
+
* @returns {*} The 2.0 original or undefined
|
|
99
|
+
*/
|
|
100
|
+
export function peekShadow(stats, kind, value) {
|
|
101
|
+
const byKind = stats.shadows[kind];
|
|
102
|
+
if (!isObjectRecord(byKind) || value === undefined) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
const originals = byKind[shadowKey(value)];
|
|
106
|
+
return Array.isArray(originals) && originals.length === 1
|
|
107
|
+
? clone(originals[0])
|
|
108
|
+
: undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Count one dropped or rewritten value under a stable label. */
|
|
112
|
+
export function countDrop(stats, label, count = 1) {
|
|
113
|
+
stats.drops[label] = (stats.drops[label] || 0) + count;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Count one value the render rewrote without losing data (a duplicate refId
|
|
118
|
+
* turned into a reference, a retargeted annotation subject).
|
|
119
|
+
*/
|
|
120
|
+
export function countRewrite(stats, label, count = 1) {
|
|
121
|
+
stats.rewrites[label] = (stats.rewrites[label] || 0) + count;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Count a 1.x JSF `signature` the render leaves out: it signs the 1.x
|
|
126
|
+
* serialization, so it cannot verify over the re-rendered 2.0 document, and
|
|
127
|
+
* 2.0 `signatures` hold JSS signatures
|
|
128
|
+
* (#/$defs/cyclonedx-common-2.0/$defs/signatures).
|
|
129
|
+
*/
|
|
130
|
+
export function countSignatureDrop(stats, path) {
|
|
131
|
+
countDrop(
|
|
132
|
+
stats,
|
|
133
|
+
`${path} (1.x JSF signature: does not verify after re-rendering)`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Count every defined key of `source` that `handled` does not list. */
|
|
138
|
+
export function countUnmapped(stats, prefix, source, handled) {
|
|
139
|
+
for (const [key, value] of Object.entries(source)) {
|
|
140
|
+
if (value !== undefined && !handled.has(key)) {
|
|
141
|
+
countDrop(stats, `${prefix}.${key}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Clone the listed fields of `source` that are defined onto `target`. */
|
|
147
|
+
export function copyDefined(target, source, fields) {
|
|
148
|
+
for (const field of fields) {
|
|
149
|
+
if (source[field] !== undefined) {
|
|
150
|
+
target[field] = clone(source[field]);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Remove duplicate entries (by JSON value) for `uniqueItems` arrays. */
|
|
156
|
+
export function uniqueByValue(values) {
|
|
157
|
+
const seen = new Set();
|
|
158
|
+
const unique = [];
|
|
159
|
+
for (const value of values) {
|
|
160
|
+
const key = JSON.stringify(value);
|
|
161
|
+
if (!seen.has(key)) {
|
|
162
|
+
seen.add(key);
|
|
163
|
+
unique.push(value);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return unique;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Split a 1.x `author` string the way specVersionCompat does. */
|
|
170
|
+
export function authorStringToNames(authorValue) {
|
|
171
|
+
if (typeof authorValue !== "string") {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
return authorValue
|
|
175
|
+
.split(",")
|
|
176
|
+
.map((name) => name.trim())
|
|
177
|
+
.filter(Boolean);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** A 1.x scalar-or-array identifier field as a list of strings. */
|
|
181
|
+
export function identifierValues(value) {
|
|
182
|
+
if (Array.isArray(value)) {
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
return typeof value === "string" ? [value] : [];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Assign `value` to `out[key]` unless it is undefined. */
|
|
189
|
+
export function setDefined(out, key, value) {
|
|
190
|
+
if (value !== undefined) {
|
|
191
|
+
out[key] = value;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Convert a 1.x `environmentVars` array of {name, value} entries into the 2.0
|
|
197
|
+
* map of names to values
|
|
198
|
+
* (#/$defs/cyclonedx-formulation-2.0/$defs/environmentVars). Reverse for read.
|
|
199
|
+
* Entries the map cannot hold are counted when `stats` is given.
|
|
200
|
+
*/
|
|
201
|
+
export function convertEnvironmentVars(value, stats) {
|
|
202
|
+
if (Array.isArray(value)) {
|
|
203
|
+
const mapped = {};
|
|
204
|
+
for (const entry of value) {
|
|
205
|
+
// A 1.x string entry becomes a name with a null value, which the 2.0
|
|
206
|
+
// map allows.
|
|
207
|
+
const name = typeof entry === "string" ? entry : entry?.name;
|
|
208
|
+
if (!isObjectRecord(entry) && typeof entry !== "string") {
|
|
209
|
+
countDropIf(stats, "environmentVars (not a name or {name, value})");
|
|
210
|
+
} else if (typeof name !== "string") {
|
|
211
|
+
countDropIf(stats, "environmentVars (no name)");
|
|
212
|
+
} else if (Object.hasOwn(mapped, name)) {
|
|
213
|
+
countDropIf(stats, "environmentVars (repeated name)");
|
|
214
|
+
} else {
|
|
215
|
+
mapped[name] = typeof entry?.value === "string" ? entry.value : null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return mapped;
|
|
219
|
+
}
|
|
220
|
+
return clone(value);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function countDropIf(stats, label) {
|
|
224
|
+
if (stats) {
|
|
225
|
+
countDrop(stats, label);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Turn a 2.0 environmentVars map back into the 1.x {name, value} array. */
|
|
230
|
+
export function reverseEnvironmentVars(value) {
|
|
231
|
+
if (!Array.isArray(value) && isObjectRecord(value)) {
|
|
232
|
+
return Object.entries(value).map(([name, val]) => ({
|
|
233
|
+
name,
|
|
234
|
+
value: val === null ? undefined : val,
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
return value;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Every refId string declared anywhere under the given roots. */
|
|
241
|
+
export function collectDeclaredRefIds(roots) {
|
|
242
|
+
const declared = new Set();
|
|
243
|
+
const visit = (node) => {
|
|
244
|
+
if (Array.isArray(node)) {
|
|
245
|
+
node.forEach(visit);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (!isObjectRecord(node)) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (typeof node.refId === "string") {
|
|
252
|
+
declared.add(node.refId);
|
|
253
|
+
}
|
|
254
|
+
for (const value of Object.values(node)) {
|
|
255
|
+
visit(value);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
roots.forEach(visit);
|
|
259
|
+
return declared;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* True when any `party` member (a refId string or a partyReference) or any
|
|
264
|
+
* `ref` member below `node` names `refId`. Restored 2.0 originals reference
|
|
265
|
+
* a derived party (see derivedPartyDeclarations) without passing through the
|
|
266
|
+
* converters, so its declaration follows the rendered document rather than a
|
|
267
|
+
* render flag.
|
|
268
|
+
*/
|
|
269
|
+
export function referencesParty(node, refId) {
|
|
270
|
+
if (Array.isArray(node)) {
|
|
271
|
+
return node.some((child) => referencesParty(child, refId));
|
|
272
|
+
}
|
|
273
|
+
if (!isObjectRecord(node)) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
for (const [key, value] of Object.entries(node)) {
|
|
277
|
+
if (
|
|
278
|
+
(key === "party" && (value === refId || value?.ref === refId)) ||
|
|
279
|
+
(key === "ref" && value === refId) ||
|
|
280
|
+
referencesParty(value, refId)
|
|
281
|
+
) {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 2.0 sections the renderer derives from the 1.x-shaped internal model
|
|
3
|
+
* rather than converts: post-quantum migration risks (pqcMigration.js,
|
|
4
|
+
* #4592), and the coding-agent parties and behavioral blueprint
|
|
5
|
+
* (agentBlueprint.js, #4593).
|
|
6
|
+
*
|
|
7
|
+
* A model read from 2.0 carries its 2.0-only sections (risks, blueprints,
|
|
8
|
+
* parties) for 2.0 output. Derived entries in them are recognised by refId
|
|
9
|
+
* (`risk-pqc-*`, `blueprint-declared-agents`, agent parties `party-agent-*`
|
|
10
|
+
* of system kind `agent`). The renderer drops carried derived entries and
|
|
11
|
+
* derives them again from the model, so they follow edits to the model and
|
|
12
|
+
* are never declared twice. Every other carried entry is kept, first, and a
|
|
13
|
+
* derived entry never replaces a carried entry with the same refId.
|
|
14
|
+
*
|
|
15
|
+
* Only cdxgen's own analysis is derived: the sections are derived only when
|
|
16
|
+
* the model's `metadata.tools` lists cdxgen with a bom-ref, the rule
|
|
17
|
+
* identityClaimContext applies to cdxgen's identity claims. Another tool's
|
|
18
|
+
* BOM converted with `cdx-convert --to 2.0` gets no derived section.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { isDeepStrictEqual } from "node:util";
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
AGENT_BLUEPRINT_REF_ID,
|
|
25
|
+
AGENT_PARTY_REF_ID_PREFIX,
|
|
26
|
+
collectAgentBehaviour,
|
|
27
|
+
} from "./agentBlueprint.js";
|
|
28
|
+
import { clone, isObjectRecord } from "./cyclonedx20Common.js";
|
|
29
|
+
import { identityClaimContext } from "./cyclonedx20RenderEvidence.js";
|
|
30
|
+
import { collectPqcRisks, PQC_RISK_REF_ID_PREFIX } from "./pqcMigration.js";
|
|
31
|
+
|
|
32
|
+
const startsWith = (value, prefix) =>
|
|
33
|
+
typeof value === "string" && value.startsWith(prefix);
|
|
34
|
+
|
|
35
|
+
/** A risk entry renderCycloneDx20 derives (see collectPqcRisks). */
|
|
36
|
+
export const isDerivedRisk = (risk) =>
|
|
37
|
+
startsWith(risk?.refId, PQC_RISK_REF_ID_PREFIX);
|
|
38
|
+
|
|
39
|
+
/** The blueprint renderCycloneDx20 derives (see collectAgentBehaviour). */
|
|
40
|
+
export const isDerivedBlueprint = (blueprint) =>
|
|
41
|
+
blueprint?.refId === AGENT_BLUEPRINT_REF_ID;
|
|
42
|
+
|
|
43
|
+
/** An agent party renderCycloneDx20 derives (see collectAgentBehaviour). */
|
|
44
|
+
export const isDerivedAgentParty = (party) =>
|
|
45
|
+
startsWith(party?.refId, AGENT_PARTY_REF_ID_PREFIX) &&
|
|
46
|
+
party?.system?.kind === "agent";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Does the model's `metadata.tools` list cdxgen with a bom-ref? The same
|
|
50
|
+
* test decides whether cdxgen's identity claims are written.
|
|
51
|
+
*
|
|
52
|
+
* @param {object} internalBom The 1.x-shaped internal model
|
|
53
|
+
* @returns {boolean} True when cdxgen is a listed tool
|
|
54
|
+
*/
|
|
55
|
+
export function isCdxgenListedTool(internalBom) {
|
|
56
|
+
return identityClaimContext(internalBom).toolRefId !== undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Derive the 2.0 sections of an internal model; all empty unless cdxgen is a
|
|
61
|
+
* listed tool (isCdxgenListedTool).
|
|
62
|
+
*
|
|
63
|
+
* @param {object} internalBom The 1.x-shaped internal model
|
|
64
|
+
* @returns {{risks: object[], agentParties: object[], blueprints: object[]}}
|
|
65
|
+
*/
|
|
66
|
+
export function deriveCycloneDx20Sections(internalBom) {
|
|
67
|
+
if (!isCdxgenListedTool(internalBom)) {
|
|
68
|
+
return { risks: [], agentParties: [], blueprints: [] };
|
|
69
|
+
}
|
|
70
|
+
const { parties, blueprint } = collectAgentBehaviour(internalBom);
|
|
71
|
+
return {
|
|
72
|
+
risks: collectPqcRisks(internalBom),
|
|
73
|
+
agentParties: parties,
|
|
74
|
+
blueprints: blueprint ? [blueprint] : [],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Carried entries that are not derived, then derived ones with new refIds. */
|
|
79
|
+
function mergeByRefId(carried, derived, isDerived) {
|
|
80
|
+
const kept = (Array.isArray(carried) ? carried : []).filter(
|
|
81
|
+
(entry) => !isDerived(entry),
|
|
82
|
+
);
|
|
83
|
+
const refIds = new Set(kept.map((entry) => entry?.refId));
|
|
84
|
+
return [...kept, ...derived.filter((entry) => !refIds.has(entry.refId))];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Add the derived risks and blueprint to a rendered document whose carried
|
|
89
|
+
* 2.0 sections are already in place, and return the parties to declare: the
|
|
90
|
+
* carried parties without derived agent parties, then the derived ones.
|
|
91
|
+
*
|
|
92
|
+
* @param {object} out The document being rendered
|
|
93
|
+
* @param {object[]} carriedParties Parties carried from a 2.0 read
|
|
94
|
+
* @param {object} internalBom The internal model the document renders
|
|
95
|
+
* @returns {object[]} The parties to declare before the cdxgen party
|
|
96
|
+
*/
|
|
97
|
+
export function appendDerivedSections(out, carriedParties, internalBom) {
|
|
98
|
+
const derived = deriveCycloneDx20Sections(internalBom);
|
|
99
|
+
// For another tool's document cdxgen derives nothing, and entries that look
|
|
100
|
+
// derived are that tool's own, so they are kept.
|
|
101
|
+
const listed = isCdxgenListedTool(internalBom);
|
|
102
|
+
const derivedOnly = (isDerived) => (entry) => listed && isDerived(entry);
|
|
103
|
+
const risks = mergeByRefId(
|
|
104
|
+
(isObjectRecord(out.risks) ? out.risks : {}).risks,
|
|
105
|
+
derived.risks,
|
|
106
|
+
derivedOnly(isDerivedRisk),
|
|
107
|
+
);
|
|
108
|
+
const risksSection = isObjectRecord(out.risks) ? out.risks : {};
|
|
109
|
+
const { risks: _risks, ...riskMembers } = risksSection;
|
|
110
|
+
if (risks.length) {
|
|
111
|
+
out.risks = { ...riskMembers, risks };
|
|
112
|
+
} else if (Object.keys(riskMembers).length) {
|
|
113
|
+
out.risks = riskMembers;
|
|
114
|
+
} else {
|
|
115
|
+
delete out.risks;
|
|
116
|
+
}
|
|
117
|
+
const blueprints = mergeByRefId(
|
|
118
|
+
out.blueprints,
|
|
119
|
+
derived.blueprints,
|
|
120
|
+
derivedOnly(isDerivedBlueprint),
|
|
121
|
+
);
|
|
122
|
+
if (blueprints.length) {
|
|
123
|
+
out.blueprints = blueprints;
|
|
124
|
+
} else {
|
|
125
|
+
delete out.blueprints;
|
|
126
|
+
}
|
|
127
|
+
return mergeByRefId(
|
|
128
|
+
carriedParties,
|
|
129
|
+
derived.agentParties,
|
|
130
|
+
derivedOnly(isDerivedAgentParty),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Split the derived entries out of a list read from 2.0. An entry that is
|
|
136
|
+
* what the renderer derives again from the read model is dropped without a
|
|
137
|
+
* loss; a derived-looking entry that differs is dropped too (the renderer
|
|
138
|
+
* would replace it), and its index is returned so the reader reports the
|
|
139
|
+
* loss.
|
|
140
|
+
*
|
|
141
|
+
* @param {object[]} entries The 2.0 list (risks, blueprints, parties)
|
|
142
|
+
* @param {object[]} derived What the renderer derives from the read model
|
|
143
|
+
* @param {Function} isDerived Recogniser for derived entries
|
|
144
|
+
* @returns {{kept: object[], differing: number[]}} The entries to carry, and
|
|
145
|
+
* the indexes of dropped entries that differ from the derivation
|
|
146
|
+
*/
|
|
147
|
+
export function splitDerivedEntries(entries, derived, isDerived) {
|
|
148
|
+
const byRefId = new Map(derived.map((entry) => [entry.refId, entry]));
|
|
149
|
+
const kept = [];
|
|
150
|
+
const differing = [];
|
|
151
|
+
(Array.isArray(entries) ? entries : []).forEach((entry, index) => {
|
|
152
|
+
if (!isDerived(entry)) {
|
|
153
|
+
kept.push(entry);
|
|
154
|
+
} else if (!isDeepStrictEqual(byRefId.get(entry.refId), entry)) {
|
|
155
|
+
differing.push(index);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
return { kept: clone(kept), differing };
|
|
159
|
+
}
|