For platform + security engineers · 2026-07-30

Building a CISA 2026 conformant SBOM by hand: everything you do without monsys

The 2026 Minimum Elements turned an SBOM into a signed, licensed, machine-verifiable artifact. Here is the full manual pipeline for one Linux host: inventory three layers, map every required field, canonicalize and sign with Ed25519, then derive VEX. It is a lot.

Our last post covered what the CISA 2026 Minimum Elements require. This is the honest sequel: if you do not have monsys, here is what it actually takes to produce a conformant SBOM for a single machine, by hand. Do it once and you understand why "just generate an SBOM" is not a checkbox.

We will build one for a Linux host. Windows is similar in shape, different in tooling.

Step 1: inventory three layers, not one

A machine is not just its application dependencies. A conformant host SBOM covers everything that can carry a CVE:

Application dependencies

Parse each ecosystem's lockfile: package-lock.json, requirements.txt, composer.lock, go.sum, and so on. Tools like Syft help here, but you still own merging the results into one document and reconciling versions.

OS packages

Query the package manager, one syntax per distro family.

Debian / Ubuntu:

dpkg-query -W -f='${Package}\t${Version}\t${Architecture}\n'

RPM (RHEL / Fedora / Rocky / Alma):

rpm -qa --qf '%{NAME}\t%{VERSION}-%{RELEASE}\t%{ARCH}\t%{LICENSE}\n'

Alpine:

apk info -v

Now turn every line into a Package URL. openssl 3.0.13 on Ubuntu 22.04 becomes:

pkg:deb/ubuntu/openssl@3.0.13?arch=amd64&distro=ubuntu-22.04

Get the qualifiers wrong and downstream CVE matching silently misses.

The running kernel

uname -r   # 6.8.0-100-generic
uname -m   # x86_64

Which becomes pkg:deb/ubuntu/linux-kernel@6.8.0-100-generic?arch=x86_64&distro=ubuntu. Then tag it as an operating-system component (CycloneDX type, SPDX primaryPackagePurpose), or a scanner treats your kernel like an npm library.

Step 2: fill every required 2026 field

The standard is not "a list of names". Per component you now owe:

Then the document-level metadata: Author, Author Signature (Step 3), Data Format Name and Version, Timestamp (RFC 9557 / RFC 3339), Tool Name and Version, Generation Context (before / during / after build), and an SBOM Version using semantic versioning.

Step 3: sign it so anyone can verify

This is the part most homemade SBOMs skip, and it is now mandatory.

  1. Generate a key:
openssl genpkey -algorithm ed25519 -out sbom-key.pem
  1. Canonicalize the document with RFC 8785 (JSON Canonicalization Scheme), with the signature holder removed. There is no openssl flag for this. JCS sorts object keys by UTF-16 code unit, uses minimal escaping, and has a specific number format. You write or import a JCS library, and if you sign the wrong bytes, every downstream verifier rejects the signature.
  2. Sign the canonical bytes with the Ed25519 key.
  3. Embed the signature. CycloneDX has a native JSF signature block; SPDX has no signature field, so you carry it in a document-level annotation.
  4. Publish the public key somewhere fetchable, so a third party can re-canonicalize the file, fetch your key, and verify it offline.

Miss any of these five and you have a signature nobody else can check, which is the same as no signature.

Step 4: derive VEX, because a list is not the goal

The 2026 standard adds a section on correlating SBOMs with security advisories, naming VEX and CSAF explicitly. So the inventory is only half the deliverable. For every component you now:

  1. Match it against a vulnerability source. OSV.dev takes batched (ecosystem, name, version) triplets; NVD matches by CPE.
  2. Decide a status per CVE: affected, not_affected, fixed, or under_investigation.
  3. Justify it. OpenVEX requires an action_statement for affected and a justification or impact_statement for not_affected. A backported kernel fix reads as "fixed" even though the version string still looks vulnerable, so you need distro backport awareness or you will over-report.
  4. Emit OpenVEX or CycloneDX-VEX.

Get this wrong in the safe direction and you flood your auditor with false positives. Get it wrong in the convenient direction and you silently hide a real exposure.

Step 5: now do it forever, on every host

The steps above produce one SBOM for one machine at one moment. The standard also asks for Frequency (regenerate on change) and Distribution and Delivery (offer it over an API or URL). So you wrap the whole pipeline in a scheduler, store the outputs, expose an endpoint, rotate and protect the signing key, and repeat for every host in the fleet. Each new distribution is a new producer map and a new license-parsing quirk.

The honest tally

For one Linux host, conformant and signed, by hand:

That is a small project, not a command.

Or: the monsys version

monsys does all of the above from data the agent already reports. Open a host, go to the CVEs tab, and the audit export bar gives you SBOM (SPDX 2.3 or CycloneDX 1.5) and VEX (OpenVEX or CycloneDX-VEX), each signed with our Ed25519 key and verifiable offline against our published public key. Or pull them straight from the API:

GET /api/v1/agents/{id}/sbom?format=spdx-json
GET /api/v1/agents/{id}/sbom?format=cyclonedx-json
GET /api/v1/agents/{id}/vex?format=openvex

Same data, same signature, bundled into the monthly Audit Pack. Everything is generated inside an EU-hosted platform, with nothing sent to third parties.


Background: CISA rewrote the SBOM rulebook. Technical detail: docs.monsys.ai/en/security/sbom-vex. First five servers free: monsys.ai/en/signup.

Back to blog