Skip to content

Generate OCI Image

This utility allows users to generate SBOMs related to Container Images.

Architecture

The scripts accepts any number of SBOMs generated by SYFT and at most one SBOM generated by Hermeto (previously known as Cachi2), with the requirement that at least one SBOM is provided in total. It combines these SBOMs and takes them as a context of the built image.

The script takes a simple YAML file to determine container content and what other images were used to build the image:

# the image being built:
image:
  pullspec: example.com/foo:1.2.3
  digest: sha256:bf07a7fbb825fc0aae7bf4a1177b2b31fcf8a3feeaf7092761e18c859ee52a9c

# base (parent) image of the final stage (FROM in the last stage):
base_image:
  pullspec: example.com/bar:4.5.6
  digest: sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c

# base images of builder stages (non-final FROM stages):
builder_base_images:
- pullspec: example.com/builder:1.0
  digest: sha256:a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

# for other images used in the OCI image (COPY --from=image:tag):
extra_images:
- pullspec: example.com/baz:7.8.9
  digest: sha256:7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730

Note: base_image is absent when the final stage is FROM scratch or FROM oci-archive:.... builder_base_images and extra_images are empty lists when there are no builder stages or external image references.

You can easily generate this metadata using buildprobe (see capo for details).

All provided SBOMs must be in the same specification! This script does not support combining SPDX and CycloneDX SBOMs.

Usage

mobster --verbose  generate oci-image \
--from-syft tests/sbom/test_merge_data/cyclonedx/syft-sboms/pip-e2e-test.bom.json \
--from-syft tests/sbom/test_merge_data/cyclonedx/syft-sboms/ubi-micro.bom.json \
--from-hermeto tests/sbom/test_merge_data/cyclonedx/cachi2.bom.json \
--metadata-path tests/data/buildprobe_metadata/metadata_multistage_base_with_builder_and_extra.yaml

List of arguments

  • --from-syft -- points to an SBOM file (in a JSON format) created by Syft, can be used multiple times
  • --from-hermeto -- points to an SBOM file (in a JSON format) created by Hermeto
  • --image-pullspec -- the pullspec of the image processed in the format <registry>/<repository>:<tag>
  • --image-digest -- the digest of the image processed in the format sha256:<digest value>
  • --metadata-path -- points to a Dockerfile/Containerfile metadata YAML file
  • --contextualize -- Allows SBOM contextualization (see Contextual SBOM)
  • --output -- where to save the SBOM. prints it to STDOUT if this is not specified
  • --arch -- Image architecture in OCI format (e.g., amd64, arm64, ppc64le, s390x). Linux kernel format values (e.g., x86_64, aarch64) are also accepted and normalized automatically to the OCI format. Defaults to the architecture of the current system.
  • --skip-validation -- skips validation of the SBOM
  • --additional-base-image -- base (builder) image to add, can be specified multiple times. Expects the format <registry>/<repository>:<tag>@sha256:<digest value>.
  • --build-metadata-path -- points to a builder content metadata JSON file produced by capo. Required for builder content contextualization.

Deprecated arguments (will be removed in future versions and fully replaced by --metadata-path)

  • --parsed-dockerfile-path -- points to a dockerfile processed by dockerfile-json. Use --metadata-path instead.
  • --base-image-digest-file -- points to a file with digests for images used in Dockerfile. Expected format: <registry>/<repository>:<tag> <registry>/<repository>:<tag>@sha256:<digest>. Use --metadata-path instead.
  • --dockerfile-target -- the name of the build target from the Dockerfile. Use --metadata-path instead.

Generating a (non-hermetic) SBOM from scratch

To build an SBOM with only the OCI image, you will need to run several tools to get prerequisite files to use in the mobster generate command. These two tools are:

  • Syft (https://github.com/anchore/syft): for initial scanning and SBOM generation
  • buildprobe (provided by capo at https://github.com/konflux-ci/capo): for generating a human-readable Dockerfile/Containerfile manifest

Download and install them before this process.

Assuming you have the Containerfile, and the OCI image stored in a repository (or your localhost), the process is as follows:

  1. Use Syft to generate the intermediate SBOM to use in --from-syft, in SPDX format:
syft scan quay.io/konflux-ci/mobster:latest --output spdx-json > syft.json
  1. Use buildprobe to generate a machine-readable Dockerfile/Containerfile description for the tool to use:
buildprobe buildah --tag="quay.io/your-image:latest" --target="" --containerfile="Containerfile" > metadata.yaml
  1. Run mobster generate with the prerequisite files and the same OCI image URL you used in the previous steps (note the argument order, --output should come before oci-image):
mobster generate \
    --output full-sbom.json \
    oci-image \
    --from-syft syft.json \
    --metadata-path metadata.yaml

Once the command is complete, you should see the full Mobster SBOM in full-sbom.json in your working directory.

Deprecated: using dockerfile-json instead of buildprobe

For backward compatibility, you can still use the deprecated dockerfile-json based workflow. Note: This path only supports parent contextualization (no builder content). It will be removed in a future version. Full contextualization parent + builder content can be achieved only by use of the --metadata-path argument.

mobster generate \
    --output full-sbom.json \
    oci-image \
    --from-syft syft.json \
    --parsed-dockerfile-path containerfile.json \
    --base-image-digest-file base_images_digests.txt \
    --image-pullspec quay.io/konflux-ci/mobster:latest \
    --image-digest sha256:1234567890abcdef...

Contextual SBOM

Contextual SBOM is a feature that establishes relationships between container images and their parent (base) or builder images in the software supply chain. Instead of treating each image as isolated, it creates a hierarchical view showing how packages flow from parent or builder images to child images. Knowing the origin of the content establishes a foundation for targeted content and vulnerability tracking within the image hierarchy.

This documentation is intended for anyone interested in contextual SBOM or directly using it. It aims to set clear expectations about the feature by defining the requirements for obtaining it, explaining how it is assembled and structured, outlining potential future changes, and describing its benefits and limitations.

  • Release status:
    • Contextual SBOM for non-hermetic build
      • identification of the base image content in component in non-hermetic build
      • achieved by matching packages from the base image SBOM to the component SBOM and marking their origin with relationships
    • Builder content contextualization
      • identification of the origin of the content copied from multistage build stages to component
      • achieved by tracing content from builder and intermediate images using buildah build artifacts and reparenting packages in the component SBOM to their origin builder or intermediate images
    • Contextual SBOM for hermetic build
      • differentiation of the base image content, builder content and component content in hermetic build
      • assembled from base image content, hermeto content and - in multistage builds - builder content

Requirements for executing Contextual SBOM workflow

  • --contextualize flag is present
  • --metadata-path is provided - buildprobe metadata YAML describing participating images (base image, builder base images, extra images). Generated by buildprobe from buildah storage after the build.
  • For parent content contextualization: base image of the component must have an SBOM attached (e.g. via cosign) in SPDX format with version 2.X. When the final stage is FROM scratch or FROM oci-archive:..., parent contextualization is skipped but builder content contextualization still proceeds.
  • For builder content contextualization: --build-metadata-path is provided - JSON file containing per-package metadata - PURL, origin type (builder base image or intermediate image), source pullspec - produced by capo from buildah build artifacts. The image must be built with buildah >= 1.44.0 using --save-stages --stage-labels flags so that intermediate images are preserved and labeled for capo to identify them for correct resolving of the origin of the content.

If any requirement is not met, non-contextual SBOM will be produced. Note: if --build-metadata-path is omitted, parent contextualization still proceeds but builder content is skipped with warning. This is not a recommended usage - it exists to preserve backward compatibility and for experimentation purposes. Users should be aware that the resulting SBOM will be incomplete for multistage builds (a warning is logged in this case). Parent and builder content contextualization are treated as an atomic operation: if parent contextualization fails (e.g. parent SBOM cannot be downloaded), builder content contextualization is not attempted. If builder content contextualization fails, parent contextualization is rolled back. In both cases, non-contextual SBOM is produced.

Functional breakdown

  • Feature profoundly changes relationship structure of the SBOM
    • Non-contextual SBOM: Relationships capture only first base image as component ancestor. All package relationships point to component. There is no concept of the base/parent or builder image content, origin of the packages is unknown. Those are all SBOMs produced before contextualization.
    • Contextual SBOM: Relationships capture all base images as component ancestors in ordered chain up to last non-contextualized ancestor. In multistage builds, builder base images are captured as build tools of the component. When a builder stage installs or creates content on top of its base image (via RUN, COPY, etc.) and that content is copied to the final image, an intermediate image is created in the SBOM representing that modified state. If no such content is detected, only the builder base image appears. Package relationships point to their actual origin - parent image (or its ancestors), builder base image (package already present in the builder image before the build), intermediate image (package installed or created during the builder stage), or component itself (package installed in the final stage) defining true origin all of the content present in the component.
  • Mechanism of Contextual SBOM assembly is different for the hermetic and non-hermetic build. Non-hermetic builds use matching - syft scans the final image and packages are mapped between parent and component SBOMs by unique identifiers (checksum, verification code, PURL). In multistage builds, capo output is used to reparent matched packages to their origin builder or intermediate images. Hermetic builds use assembly — the SBOM is composed from downloaded parent content, hermeto content (component-only and intermediate, from verified lockfile) and builder content (from capo). Matching is still used but differently: to reparent intermediate content in hermeto's lockfile and to identify and remove packages removed at build time from the parent image. Syft scan of the component is not used in the final SBOM - it serves only as a helper for removed-at-build-time detection. This results in a more accurate SBOM with less dependence on syft.
  • Comparing to non-contextual SBOM, all packages remain present; none are removed.
  • Changes are backward compatible and build on each other, resulting in a richer and more accurate SBOM. Base image content identification in non-hermetic builds has been enhanced with builder content for components with multistage builds, forming the foundation for contextual SBOMs in hermetic builds.
  • Contextual SBOMs are designed to be self-assembling and this works across hermetic and non-hermetic builds interchangeably and also with contextualized or non-contextualized parent SBOMs. It means that built component can be contextualized by its parent SBOM regardless if parent SBOM is sourced from hermetic or non-hermetic build, or if it is contextualized or non-contextualized. It also means that if parent SBOM is contextualized (is aware of its parent - or component's grandparent content), contextual information is preserved and passed to the component.
  • Contextual SBOM mechanism can be disabled by not including --contextualize flag in mobster generate oci-image command - in that case, non-contextual SBOM will be produced.

Contextual SBOM for non-hermetic build

Contextual SBOM for non-hermetic build consists of two steps:

Parent content contextualization: Syft scans the final image to produce a component SBOM. A SBOM of the base (parent) image is downloaded (if it exists and is in SPDX format) and packages are matched from parent to component by unique identifiers (checksum, verification code, PURL). CONTAINS relationships of matched packages are shifted from the component to their origin parent or grandparent images.

Builder content contextualization: For multistage builds, capo traces only the content that was actually copied from builder stages to the final image. Starting from COPY instructions in the final stage, it follows the source paths back through builder stages and diffs intermediate images against their builder base images to identify whether the copied content originated from the builder base image or was installed during the builder stage. Packages in the component SBOM that match capo's output are reparented to their origin builder base or intermediate images. No packages are added or removed — builder-copied content is already present in the component SBOM from the syft scan; capo's metadata only provides the origin information for reparenting.

Warning: Matching mechanism is not perfect, because SPDX specification is missing a consistent unique identifier across different packages and thus Contextual SBOM for non-hermetic build is for now the best effort. Package with missing unique identifier is named as anonymous. Anonymous package situated in parent cannot be matched in component, and anonymous package in component cannot be matched with any parent package to be contextualized (tracked up the image chain) - their source origin cannot be determined. Also from the perspective of the builder content, capo excludes anonymous packages from its builder metadata output. This might be improved in future versions by flagging such anonymous packages in SBOM making them more visible. For every Contextual SBOM document produced in non-hermetic build, packages fall into one of the following categories: - contextualized to parent - matched and reparented to parent or grandparent image - contextualized to builder - matched and reparented to builder base or intermediate image - component-only - has unique identifier but was not matched to parent or builder content; considered as installed in the final stage - anonymous - missing unique identifier; origin cannot be determined

Contextual SBOM for hermetic build

Unlike non-hermetic builds, hermetic Contextual SBOM is not produced by matching packages between two independently scanned SBOMs. Instead, it is assembled from two (single-stage builds) or three (multi-stage builds) content sources:

  1. Downloaded parent content - defines the content of the base image. May be previously contextualized or not. If not available, contextualization is skipped and non-contextual SBOM is produced. Before assembly it must be cleaned from packages removed at build time - a syft scan of the component is used as a helper to detect which parent packages are no longer present in the component, but this helper scan is not included in the final Contextual SBOM.
  2. Builder content (multistage builds only) - content copied from builder stages to the component, with origin information (builder base vs intermediate) provided by capo. Unlike non-hermetic builds where capo produces only metadata for reparenting existing packages, hermetic builds require capo to produce a full partial SBOM (packages and relationships) that is merged into the final assembled SBOM.
  3. Hermeto content - represents content installed in the component’s final stage on top of the base image and intermediate content (content installed in builder stage over the top of any builder base image). Hermeto produces a flat SBOM from the verified lockfile without distinguishing between component-only and intermediate content. In multistage builds, matching against capo’s builder metadata is used to separate intermediate content from component-only content in hermeto’s output.

This assembly approach reduces dependence on syft. The more ancestors in an image chain are built hermetically, the more SBOM content comes from verified lockfiles (hermeto) rather than post-build scanning (syft), resulting in a more accurate SBOM.

Warning: Downloaded parent SBOM content may be affected by the component’s build process (e.g. dnf update may remove packages by resolving dependency tree). Packages removed at build time are detected using the helper syft scan and cleaned from the parent content, but this detection is limited to packages with unique identifiers — anonymous packages in the parent cannot be identified as removed and may remain as false positives in the resulting SBOM.

Structure of the generated SBOM

Non-contextual SBOM

The generated SBOM has following structure:

 - SPDXRef-DOCUMENT
    - SPDXRef-image (DESCRIBES)
        - package A (CONTAINS)
        - package B (CONTAINS)
        - package C (CONTAINS)
        - package D (CONTAINS)
        - package E (CONTAINS)
        - package F (CONTAINS)
        - package G (CONTAINS)
        - base image (DESCENDANT_OF)
        - builder image 1 (BUILD_TOOL_OF)
        - builder image 2 (BUILD_TOOL_OF)

Contextual SBOM

The generated SBOM has following structure:

 - SPDXRef-DOCUMENT
    - SPDXRef-image (DESCRIBES)
        - package A (CONTAINS)
        - package B (CONTAINS)
        - base image (DESCENDANT_OF)
            - package C (CONTAINS)
            - grandparent image (DESCENDANT_OF)
                - package D (CONTAINS)
        - builder image 1 (BUILD_TOOL_OF)
            - package E (CONTAINS)
        - builder image 2 (BUILD_TOOL_OF)
            - package F (CONTAINS)
            - intermediate image (DESCENDANT_OF)
                - package G (CONTAINS)