merge

Merges two or more CycloneDX input files into one. Inputs can either be specified directly as positional arguments on the command-line or using the --from-folder option. Files specified as arguments are merged in the order they are given, files in the folder are merged in alphabetical order (see note below).

If both positional arguments and the --from-folder option are used, then the position arguments are merged first, followed by the files in the folder. The command will not merge the same file twice, if it is specified on the command-line and also part of the folder.

When using the --from-folder option, the program looks for files matching either of the recommended CycloneDX naming schemes: bom.json or *.cdx.json.

usage: cdx-ev merge [-h] [--from-folder <from-folder>] [--hierarchical]
                    [--output <file>]
                    [<input> ...]

Positional Arguments

<input>

Paths to SBOM files to merge. You must specify at least two paths.

Named Arguments

--from-folder

Path to a folder with SBOMs to be merged.

--hierarchical

Flag to determine if the components should be merged hierarchical.

Default: False

--output, -o

The path to where the output should be written. If this is a file, output is written there. If it’s a directory, output is written to a file with an auto-generated name inside that directory. If it’s not specified, output is written to stdout.

Details

Input files in the folder provided to the --from-folder option are sorted by name in a platform-specific way. In other words, they are merged in the same order they appear in your operating system’s file browser (e.g., Windows Explorer) when sorted by name.

The process runs iteratively, merging two SBOMs in each iteration. In the first round, the second submitted SBOM is merged into the first. In the second round the third would be merged into the result of the first round and so on.

In mathematical terms: \(output = (((input_1 * input_2) * input_3) * input_4 ...)\)

The merge is per default not hierarchical for the components field of a component (CycloneDX documentation). This means that components that were contained in the components of an already present component will just be added as new components under the SBOMs’ components sections. The --hierarchical flag allows for hierarchical merges. This affects only the top level components of the merged SBOM. The structured of nested components is preserved in both cases (except the removal of already present components), as shown for “component 4” in the image below.

Merge components structure default and hierarchical.

A few notes on the merge algorithm:

  • The metadata field is always retained from the first input and never changed through a merge with the exception of the timestamp.

  • The command merges the contents of the fields components, dependencies, compositions and vulnerabilities.

  • Components are merged into the result in the order they first appear in the inputs. If any subsequent input specifies the same component (sameness in this case being defined as having identical identifying attributes such as name, version, purl, etc.), the later instance of the component will be dropped with a warning. Its properties are the exception: properties not already present on the retained component are appended in input order. Properties with the same name but different values are retained, while completely identical property objects are added only once. This applies iteratively to every input.

  • The resulting dependency graph will reflect all dependencies from all inputs. Dependencies from later inputs are always added to the result, even if the component is dropped as a duplicate as described above.

  • Uniqueness of bom-refs will be ensured across all inputs in every merge mode. This applies to components, services, vulnerabilities, compositions, licenses, annotations, formulation, declarations, definitions and every other element that can carry a bom-ref. Identical components (with the same identifying attributes) receive the same bom-ref; a different component that collides with an existing reference receives a new reference; other colliding elements from later inputs receive numeric suffixes such as -1 and -2. All references to a renamed element within its input are updated.

  • During --hierarchical merges, when components from a later input are added under an already present matching parent, all relocated descendants have the new parent bom-ref prepended. Existing parent prefixes are not duplicated, so relocating a subtree with app and app/logger under parent system changes those bom-ref values to system/app and system/app/logger; opaque refs such as pkg:npm/foo@1.0 become system/pkg:npm/foo@1.0. Prefix stripping only uses / as the separator. Only relocated components are rebased; components already present in the first input keep their refs, so the resulting refs are not necessarily full paths from the root. If rebasing would create a collision, suffixes such as -1 and -2 are appended.

  • Because of these renames, output bom-ref values can differ from the inputs. External documents that point at input references, such as separate VEX files or BOM-Links, may need to be updated.

  • The command is able to merge inputs containing only VEX information in the form of a vulnerabilities. To ensure a sensible result, it should be ensured that bom-refs in the affects field reference components of the same SBOM.

  • Vulnerabilities, like components, are merged into the result in the order they first appear in the inputs.

  • If a merged vulnerability contains additional entries in the affects field, those will be added to the original vulnerability object (duplicates are possible if version ranges are used).