CI integration

One step in your pipeline. Every build publishes an SBOM, and the build fails if a component is on the CISA KEV catalog.

GitHub Actions

- uses: crakit/sbom-action@v1
  with:
    token: ${{ secrets.CRAKIT_TOKEN }}
    product: ${{ vars.CRAKIT_PRODUCT }}
    release: ${{ github.ref_name }}
    fail-on-kev: true

The build system is detected from the tree. Pass build-system if you want to be explicit, and dir if the build is not at the repository root.

GitLab CI

include:
  - remote: 'https://cra.synthworx.com/integrations/crakit.gitlab-ci.yml'

crakit-sbom:
  extends: .crakit-sbom
  variables:
    CRAKIT_DIR: "build"

Set CRAKIT_TOKEN and CRAKIT_PRODUCT as masked CI/CD variables. The token is a credential. Mask it and keep it out of the file.

Anything else

The script is one file with no dependencies. A CI step that pulls a dependency tree into a firmware build is a supply-chain problem inside a supply-chain tool, so it does not.

curl -fsSL https://cra.synthworx.com/integrations/crakit-sbom.mjs -o crakit-sbom.mjs
node crakit-sbom.mjs --product $PRODUCT --release 2.4.1 --fail-on-kev

# or write the SBOM to a file and do what you like with it
node crakit-sbom.mjs --build-system yocto --dir ./build --out sbom.json

What it reads

Build systemFileWhat comes out
Zephyrwest.ymlEvery project in the manifest with its pinned revision, plus the kernel version from the VERSION file.
Yoctolicense.manifestEvery recipe that went into the image, with its version and declared licence. This is the richest of the six.
Buildroot.config and package/*.mkEvery selected BR2_PACKAGE, with the version from .config or the package makefile.
PlatformIOplatformio.inilib_deps entries with their pinned versions, plus the platform and framework.
ESP-IDFdependencies.lockEvery managed component with its version, plus the IDF version itself.
STM32Cubethe .ioc project fileThe firmware package and version, the HAL family, the MCU part number and the CubeMX version.

Where a build system records no version for a dependency, the component is published with the version unknown. It is never guessed. Same rule as the binary path.

Failing the build

With --fail-on-kev, the step exits non-zero when a component in the build is on the CISA Known Exploited Vulnerabilities catalog. KEV membership means somebody is exploiting it against real systems, which is the Article 14(1) trigger.

It does not fail on ordinary CVEs. A gate that fires on every new advisory gets disabled in a week, and then it is not there for the one that mattered.

Merging with a binary scan

If you also upload the shipped image, the two lists merge into one canonical SBOM per release. The build manifest wins on versions, because it is ground truth. The image contributes everything the build never saw, which is exactly the ODM blobs you are exposed on. The merge records which side saw what.

The integrations wrap the public API and nothing else. Anything they do, you can do with a key and curl.