PQC Status
Guide

Generate a CycloneDX CBOM in CI

A practical pipeline: collect, merge, validate, store and diff a CycloneDX 1.6 cryptographic bill of materials on every build.

· 8 min

A CycloneDX CBOM (cryptographic bill of materials) is a CycloneDX document whose components describe cryptographic assets: algorithms, protocols, certificates and key material. Producing one in continuous integration is mostly plumbing: run detectors over source, dependencies, container images and test endpoints, merge the results into a single CycloneDX 1.6 document, validate it against the schema, then store it and compare it with the previous build. The difficult part is being honest about what a build pipeline can actually see. For the concept itself, start with our introduction to the CBOM.

Where cryptographic assets live in the CycloneDX 1.6 model

CycloneDX 1.6 introduced the component type cryptographic-asset. Each such component carries a cryptoProperties object, and its assetType field decides which group of properties applies. Four asset types exist.

assetTypeProperty groupTypical content
algorithmalgorithmPropertiesPrimitive (kem, signature, pke, hash, mac, block cipher), parameter set identifier, mode, padding, curve, cryptographic functions, NIST quantum security level
certificatecertificatePropertiesSubject name, issuer name, validity dates, certificate format, references to the signature algorithm and to the subject public key
protocolprotocolPropertiesProtocol type (tls, ssh, ipsec and others), version, cipher suites with their identifiers, references to the algorithms used
related-crypto-materialrelatedCryptoMaterialPropertiesKeys, secrets, tokens, digests, initialization vectors, with size, state and lifecycle dates

Two further fields do the real inventory work. The oid field pins an algorithm to its object identifier when you know it. And in the dependencies array, the provides field states that a software component implements a cryptographic asset. That is how a CBOM answers the question an auditor actually asks: which application uses this algorithm, and where?

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "serialNumber": "urn:uuid:4e1e1f2a-0000-0000-0000-000000000000",
  "version": 1,
  "metadata": {
    "timestamp": "2026-10-19T06:12:00Z",
    "component": {
      "type": "application",
      "bom-ref": "payments-api@1.42.0",
      "name": "payments-api",
      "version": "1.42.0"
    }
  },
  "components": [
    {
      "type": "cryptographic-asset",
      "bom-ref": "crypto/algorithm/ml-kem-768",
      "name": "ML-KEM-768",
      "cryptoProperties": {
        "assetType": "algorithm",
        "algorithmProperties": {
          "primitive": "kem",
          "parameterSetIdentifier": "768",
          "cryptoFunctions": ["keygen", "encapsulate", "decapsulate"],
          "nistQuantumSecurityLevel": 3
        }
      }
    },
    {
      "type": "cryptographic-asset",
      "bom-ref": "crypto/protocol/tls-1.3",
      "name": "TLS 1.3",
      "cryptoProperties": {
        "assetType": "protocol",
        "protocolProperties": {
          "type": "tls",
          "version": "1.3",
          "cipherSuites": [
            { "name": "TLS_AES_256_GCM_SHA384", "identifiers": ["0x13", "0x02"] }
          ],
          "cryptoRefArray": ["crypto/algorithm/ml-kem-768"]
        }
      }
    }
  ],
  "dependencies": [
    { "ref": "payments-api@1.42.0", "provides": ["crypto/protocol/tls-1.3"] }
  ]
}

ML-KEM is the key encapsulation mechanism standardized in NIST FIPS 203; its 768 parameter set sits at NIST quantum security category 3. Protocol components are where the hybrid key exchange groups appear, for example X25519MLKEM768, whose codepoint 0x11EC is listed in the IANA TLS parameters registry.

Static detection versus observed behavior

A CI job sees a repository, a lockfile and an image. It does not see a negotiated handshake. Treating the two as the same thing is the most common way a CBOM becomes misleading.

AssetWhat a static job can establishWhat only observation establishes
Algorithm calls in your own codeThe call sites, with file and line, plus parameter sets written as constantsWhether that code path runs in production
Crypto librariesLibrary and version from the lockfile or the image, which bounds what is possibleWhich provider or FIPS mode is loaded at run time
TLS key exchange and cipher suitesOnly the directives in configuration files that live in the repositoryThe group and suite actually negotiated, including upstream defaults
CertificatesCertificate files, truststores and keystores shipped in the image, with validity and signature algorithmThe chain a live endpoint serves, and certificates issued after the build
Keys and lifetimesDeclared sizes, rotation parameters in configurationKey state and dates held in a key manager or HSM
Third party endpoints you callHostnames at bestWhat those endpoints offer, which you do not control

Record the difference in the document. Keep statically derived facts in the component itself, with the file path in the component evidence occurrences, and keep measured facts in protocolProperties and certificateProperties produced by a probing job. Always populate metadata.tools so a reader knows which detector made which claim.

A pipeline outline

1. Declare the scope

Before any tool runs, decide what one CBOM covers: one deployable component, one version, one environment. A single file describing twelve services ages badly and cannot be diffed usefully.

2. Collect in parallel

  1. Source and dependency scan: a static analyzer over the repository and the lockfiles, emitting algorithm assets with evidence locations.
  2. Image or filesystem scan: inspect the built artifact for crypto libraries, certificate bundles, keystores and configuration files.
  3. Observed endpoint probe: against staging only, confirm what is really negotiated.
# TLS: does the staging endpoint negotiate a hybrid group?
# requires OpenSSL 3.5 or later for the ML-KEM groups
openssl s_client -connect staging.example.com:443 \
  -groups X25519MLKEM768 </dev/null | grep "Negotiated TLS1.3 group"

# SSH: which key agreement algorithms does this client build support?
ssh -Q kex

# SSH: confirm the server accepts the post-quantum hybrid key agreement
ssh -o KexAlgorithms=mlkem768x25519-sha256 -o BatchMode=yes \
  git@staging.example.com true

The algorithm mlkem768x25519-sha256 was added in OpenSSH 9.9 and became the default key agreement in OpenSSH 10.0, per the OpenSSH release notes. Probe staging, never production, and keep a classical fallback such as X25519 after the hybrid group in every server configuration you touch.

3. Merge, stamp and validate

Merge the partial documents into one, then fail the job if the result is not schema valid. The CycloneDX CLI does both.

cyclonedx merge \
  --input-files static.cbom.json image.cbom.json observed.cbom.json \
  --output-file cbom.json

cyclonedx validate --input-file cbom.json --fail-on-errors

Stamp metadata.timestamp, the component version and the commit SHA. A CBOM without provenance is an opinion.

Storing and diffing the CBOM

Publish the file as a build artifact for traceability, and keep one normalized copy per component in a dedicated repository or BOM server so that history is queryable. Normalization matters more than storage: sort the components, use stable bom-ref values that do not contain the build number, and exclude the timestamp from the diff. Otherwise every build looks like a change.

# extract a stable, sorted view of the cryptographic assets
jq -S '[.components[]
       | select(.type == "cryptographic-asset")
       | {name, assetType: .cryptoProperties.assetType,
          primitive: .cryptoProperties.algorithmProperties.primitive}]
       | sort_by(.name)' cbom.json > current.view.json

diff -u previous.view.json current.view.json

Then decide what a difference means. Useful gates, in increasing severity: a new asset appears that no previous build declared; a quantum-vulnerable public key algorithm appears in a component that previously had none; an obsolete pre-standard group such as a Kyber draft TLS group appears anywhere; a hybrid group disappears from a service that had it. Start by warning, not failing, until the detector noise is understood. This diff is the measurable part of crypto agility: it shows whether your cryptographic surface changes on purpose or by accident.

Limits worth writing down

What a CI generated CBOM evidences

Several texts require an inventory of cryptography in use. PCI DSS v4.0.1 requirement 12.3.3 asks that cipher suites and protocols in use be documented and reviewed at least every 12 months, including where they are used and a documented strategy for anticipated changes in cryptographic vulnerabilities, and it has been mandatory since 31 March 2025 (PCI SSC document library). DORA, Commission Delegated Regulation (EU) 2024/1774, requires in Article 6 an encryption policy with provisions for updating or changing cryptographic technology as cryptanalysis develops, and in Article 7 key management across the lifecycle plus a register of certificates for ICT assets supporting critical or important functions (EUR-Lex). For US federal agencies, OMB M-26-15 calls for an automated, continuously updated cryptographic inventory (M-26-15). The EU Coordinated Implementation Roadmap v1.1 lists a cryptographic inventory among first steps by the end of 2026, as coordinated guidance for Member States rather than a regulation binding companies.

Our interpretation, clearly labelled as such: none of these texts names CycloneDX, and a CBOM is one practical way to produce and keep such an inventory, not a required format. Build the file because it makes the next migration decision cheaper, then map it to whichever obligation applies. This is information, not legal advice.

Next step: compare the CBOM with reality

A CBOM generated in CI tells you what your build contains. It cannot tell you what your servers negotiate. Close that gap by testing the endpoints themselves: scan your domain with PQC Status to see the hybrid ML-KEM groups, TLS versions and certificate chain a client is actually offered, then feed those observed values back into the protocolProperties and certificateProperties of your next CBOM so the document reflects both code and behavior.

Frequently asked questions

Which CycloneDX version do I need to describe cryptographic assets?

CycloneDX 1.6 is the first version with the cryptographic-asset component type and the cryptoProperties object. It also added the provides field in the dependencies array, which links a software component to the cryptographic assets it implements. Earlier versions can only approximate this with generic properties.

Can a CI pipeline alone produce a complete cryptographic inventory?

No. Static analysis of source, dependencies and images shows what is possible, not what runs: configuration resolved at run time, upstream proxies and third party endpoints stay invisible. Combine the static CBOM with observed data from endpoint probes and from your key and certificate management systems. Record explicitly which scopes were not covered.

How often should the CBOM be regenerated?

Generate it on every build, since the cost is close to zero once the pipeline exists and the value comes from the diff between builds. Review obligations are separate: PCI DSS v4.0.1 requirement 12.3.3 asks for documentation reviewed at least every 12 months, and OMB M-26-15 asks US federal agencies for an automated, continuously updated inventory.

Does a CBOM satisfy a regulatory requirement by itself?

A CBOM is a format, and no text we track names it. PCI DSS, the DORA RTS and the EU roadmap require an inventory, a policy and, under DORA Article 7, a register of certificates. A CBOM is one practical way to evidence the inventory part, which is our interpretation and not a certification. This is information, not legal advice.