CycloneDX ML-BOM: Fields and a Worked Example
A CycloneDX ML-BOM is a CycloneDX bill of materials in which models are components of type machine-learning-model carrying a modelCard (modelParameters, quantitativeAnalysis, considerations), datasets are components of type data, and run-time services such as a RAG index or an MCP tool server sit under services. ML support arrived in CycloneDX 1.5; the current version is 1.7 (ECMA-424). This guide explains each part against one schema-valid example file for a fictional support assistant, with the field table, an excerpt, the common mistakes, and how to validate and diff it.
A CycloneDX ML-BOM is an ordinary CycloneDX bill of materials in which the AI parts are first-class. Each model is a component of type machine-learning-model with a modelCard, each dataset is a component of type data, and anything the model calls at run time, such as a retrieval index or an MCP tool server, sits under services. Machine-learning support arrived in CycloneDX 1.5; the current version is 1.7.
Who this is for: AppSec and AI-security engineers who generate the file in CI, and SOC analysts who need it to answer “which model was running, and what could it reach?”. If you are new to the idea, read what an AIBOM is and ML-BOM explained first. This post is the hands-on version: one real file, every part explained.
Fictional example. Northwind Health, Triage Assistant and its datasets, services, scores and URLs are invented for illustration; the base model and open-source libraries are real.
The worked example
Northwind Health ships Triage Assistant 2.3.0, a support assistant that drafts replies for a human agent to approve. The model, northwind/triage-assistant 2.3.0, is a LoRA fine-tune of mistralai/Mistral-7B-Instruct-v0.3 (Apache-2.0), stored as safetensors. It was trained on northwind/support-tickets-2025 (48,210 de-identified tickets) and evaluated on northwind/triage-eval-v4 (1,200 labelled tickets). It runs on vLLM 0.6.3, reads a weekly-rebuilt RAG index over the public help centre (northwind-helpcentre-index) and calls one MCP tool server (ticketing-mcp) that can read and write tickets.
The ML-BOM for it is northwind-triage-assistant-2.3.0.cdx.json. It validates against the official CycloneDX 1.7 JSON schema.
What an ML-BOM adds to an SBOM
Seven parts, each with one job. The right-hand column is what Northwind put there.
| Part | Where in the file | What it records | Northwind |
|---|---|---|---|
| The model | components[] with type: machine-learning-model | Identity, hash, licence, lineage and a model card | northwind/triage-assistant 2.3.0 |
| The base model | A second machine-learning-model component, plus pedigree.ancestors on the fine-tune | Where the weights came from, pinned to a revision | mistralai/Mistral-7B-Instruct-v0.3, Apache-2.0 |
| Datasets | components[] with type: data and a data[] entry | Classification, sensitive data, owner, location, hash | northwind/support-tickets-2025, northwind/triage-eval-v4 |
| Run-time services | services[] | Endpoints, authentication, trust boundary, data flows | northwind-helpcentre-index (RAG), ticketing-mcp (MCP) |
| Libraries | components[] with type: library or framework | The ordinary SBOM layer | vllm 0.6.3, torch, transformers, safetensors, peft |
| The graph | dependencies[] | What depends on what at run time | App → model, vLLM, RAG index, MCP server |
| Evidence | externalReferences[] | Links to the model card, signature, attestation and download | Sigstore bundle, SLSA provenance |
The model component
type: machine-learning-model is the switch: it tells tools to expect a model card rather than a package registry entry. group, name and version are the identity an incident responder searches for. bom-ref is the file-internal ID that the dependency graph and the model card point at. hashes holds the SHA-256 of the weights file (a placeholder in this example).
Lineage goes in pedigree.ancestors. Northwind records the base model there with a purl pinned to an exact Hugging Face revision, and adds the base model as its own component so its Apache-2.0 licence is visible to licence checks. The fine-tune carries Northwind’s own licence as a named licence, because it is not on the SPDX licence list.
modelCard.modelParameters: what the model is
approach.type: one ofsupervised,unsupervised,reinforcement-learning,semi-supervised,self-supervised. LoRA fine-tuning on ticket and reply pairs is supervised.task: free text. Northwind usestext-generation.architectureFamilyandmodelArchitecture:mistralandMistralForCausalLM, so a vulnerability or a loader bug in that family can be matched to this model.datasets: each entry is either an inline dataset description or arefto adatacomponent. Northwind uses refs, so each dataset is described once.inputsandoutputs: the format of what goes in and comes out (stringfor both).
modelCard.quantitativeAnalysis: how well it works
performanceMetrics is a list of type, value, slice and an optional confidenceInterval (lowerBound, upperBound). There is also graphics for charts. Northwind reports answer-accuracy 0.87 on northwind/triage-eval-v4 and a toxicity rate of 0.002 (0.2%) on an internal red-team prompt set. Both numbers are fictional. value is a string in the schema; write decimals rather than “87%” so tools can compare releases.
modelCard.considerations: what it is for, and what can go wrong
Seven lists: users, useCases, technicalLimitations, performanceTradeoffs, ethicalConsiderations (each a name with a mitigationStrategy), environmentalConsiderations and fairnessAssessments. Northwind fills the first three and names two ethical risks: a reply that strays into medical advice, and personal data in free-text tickets, each with its mitigation. It leaves trade-offs, environmental and fairness sections empty. An empty section is something to explain to a reviewer, not something to fill with filler. Treat useCases and technicalLimitations as the contract: “drafts for human approval” and “never sends on its own” are the lines a reviewer will hold the deployment to.
Data components
A dataset is a component of type data with a data[] entry. That entry has a type (dataset, source-code, configuration, definition or other), a name, contents (a URL, an attachment or properties), a classification, a list of sensitiveData notes, governance (owners, stewards, custodians) and an optional description and graphics.
Northwind classifies both datasets as confidential, names itself as owner, and records in sensitiveData that direct identifiers were removed but free text may still mention health context. CycloneDX has no record-count field, so the 48,210 tickets go in the component description. The component also carries its own version, SHA-256 and licence, so a dataset change shows up as a version change. For collection method, biases and intended use of the data, CycloneDX has no dedicated fields; that is where a Croissant RAI file or the SPDX Dataset profile carries more.
Services: the RAG index and the MCP tool server
The retrieval index and the tool server are not shipped inside the application. They are called at run time, so an SBOM generator that scans the container image does not see them. CycloneDX models them as services:
endpoints: where the service lives.authenticated: whether calls need credentials.x-trust-boundary:truemeans using the service crosses a trust boundary.trustZonecan name the zone.data[]: each flow with a direction relative to the service (inbound,outbound,bi-directional,unknown) and a classification.
Northwind’s help-centre index is authenticated, does not cross a trust boundary and sends out public passages; its weekly rebuild is a custom property. ticketing-mcp is authenticated, crosses a trust boundary and moves confidential tickets in both directions. CycloneDX has no field for tool permissions, so Northwind records tickets:read, tickets:write as a namespaced property. For a SOC analyst this is the blast radius: if the model is prompt-injected, ticketing-mcp is what it can write to.
Dependencies
dependencies[] is the run-time graph. Northwind’s application depends on the model, vLLM, the RAG index and the MCP server; the model depends on its base model; vLLM depends on PyTorch, Transformers and safetensors. Datasets are not run-time dependencies; the model card links to them instead. The schema is strict about completeness: a component or service with no dependencies must appear with an empty dependsOn, and one missing from the graph may have unknown dependencies. That rule is what lets a tool answer “what is affected if X is compromised?” with confidence.
Licences
Use an SPDX licence ID when one exists ({"license": {"id": "Apache-2.0"}} for the base model and vLLM) and a named licence otherwise ({"license": {"name": "Northwind Health Proprietary Licence"}}). Put a licence on every component, datasets included. A missing licence on a base model or dataset is the gap that turns into an audit finding.
Signature and build provenance
The model component links its evidence through externalReferences: model-card for the readable card, digital-signature for a Sigstore bundle produced in CI with OpenSSF model signing, attestation for SLSA build provenance in in-toto format, and distribution for the download location. The BOM only points at the evidence. Whoever relies on it still has to fetch and verify the signature and the attestation; a link is a claim until then.
The Northwind field table
Every field in the example, its value, and what it tells a reader.
| Field | Northwind value | What it tells you |
|---|---|---|
bomFormat, specVersion | CycloneDX, 1.7 | Which standard and version the file follows. Validators pick the schema from specVersion. |
serialNumber, version | urn:uuid:7fffa837-67cc-40b9-963f-55c4f9bf7152, 1 | A unique ID for this BOM and its revision. Bump version when you reissue the same BOM. |
metadata.timestamp | 2026-02-16T09:00:00Z | When the BOM was generated. |
metadata.lifecycles[].phase | build | Generated in the build pipeline, not reconstructed later. |
metadata.component | Triage Assistant 2.3.0 (application) | The system the BOM describes. Everything else is a part of it. |
metadata.manufacturer / supplier | Northwind Health | Who built and who supplies the system: the accountable party. |
components[].type | machine-learning-model | Marks the component as a model, so tools look for a model card. |
components[].group / name / version | northwind / triage-assistant / 2.3.0 | Model identity. This is what an incident responder searches for. |
components[].hashes | SHA-256 3bad18cc9805… (placeholder) | Digest of the weights file, so you can prove which file was deployed. |
components[].pedigree.ancestors | mistralai/Mistral-7B-Instruct-v0.3 (pkg:huggingface/mistralai/Mistral-7B-Instruct-v0.3) | Lineage: the base model this one was fine-tuned from. |
modelCard.modelParameters.approach.type | supervised | Training approach. LoRA fine-tuning on ticket and reply pairs is supervised. |
modelCard.modelParameters.task | text-generation | What the model does. |
modelCard.modelParameters.architectureFamily / modelArchitecture | mistral / MistralForCausalLM | Architecture, so you can match vulnerabilities and tooling to it. |
modelCard.modelParameters.datasets[].ref | data:northwind/support-tickets-2025, data:northwind/triage-eval-v4 | Links to the data components the model was trained and evaluated on. |
modelCard.quantitativeAnalysis.performanceMetrics | answer-accuracy 0.87 on northwind/triage-eval-v4; toxicity-rate 0.002 (0.2%) (fictional) | Measured results, each tied to the dataset or slice it was measured on. |
modelCard.considerations.users / useCases | Draft replies to customer support tickets for a human support agent to review, edit and approve before sending. | Intended users and use. The baseline for "is this use allowed?". |
modelCard.considerations.technicalLimitations | Medical advice or clinical triage of symptoms; Sending any reply to a customer without human approval | Out-of-scope uses and known limits. |
modelCard.considerations.ethicalConsiderations | Medical advice; personal data in tickets (each with a mitigation) | Named risks, each with how it is handled. |
components[] (base model).licenses / purl | Apache-2.0 / pkg:huggingface/mistralai/Mistral-7B-Instruct-v0.3@c170c708c41dac9275d15a8fff4eca08d52bab71 | Licence and a package URL pinned to an exact revision of the base model. |
components[].type = data, data[].classification | northwind/support-tickets-2025: confidential; northwind/triage-eval-v4: confidential | Datasets as first-class components, with classification, sensitive-data notes and owner. |
components[] (libraries) | vllm 0.6.3, torch 2.4.0, transformers 4.45.2, safetensors 0.4.5, peft 0.13.2 (scope excluded) | Runtime software. Scope "excluded" marks training-only tools that are not shipped. |
services[] (RAG index) | northwind-helpcentre-index: authenticated, outbound public data, refreshed weekly | Run-time data the model reads that is not in the weights. |
services[] (MCP server) | ticketing-mcp: x-trust-boundary true, bi-directional confidential data | Tools the model can call, and the fact that calls cross a trust boundary. |
dependencies[] | Triage Assistant → model, vllm, RAG index, MCP server; model → base model | The graph that answers "what is affected if X is compromised?". |
externalReferences[] (model) | model-card, digital-signature (Sigstore), attestation (SLSA in-toto), distribution | Where to find the signature and build provenance that back the claims in this file. |
The example file, excerpted
First, the model component: identity, lineage, the model card’s parameters and metrics, and the two evidence links.
{
"components": [
{
"type": "machine-learning-model",
"bom-ref": "model:northwind/triage-assistant@2.3.0",
"group": "northwind",
"name": "triage-assistant",
"version": "2.3.0",
"hashes": [
{
"alg": "SHA-256",
"content": "3bad18cc9805cb1211d9929cbef60f590e64c4bc71bf88fb5c22ba6da68754fa"
}
],
"pedigree": {
"ancestors": [
{
"type": "machine-learning-model",
"name": "Mistral-7B-Instruct-v0.3",
"purl": "pkg:huggingface/mistralai/Mistral-7B-Instruct-v0.3@c170c708c41dac9275d15a8fff4eca08d52bab71",
"licenses": [{ "license": { "id": "Apache-2.0" } }]
}
]
},
"modelCard": {
"modelParameters": {
"approach": { "type": "supervised" },
"task": "text-generation",
"architectureFamily": "mistral",
"modelArchitecture": "MistralForCausalLM",
"datasets": [
{ "ref": "data:northwind/support-tickets-2025" },
{ "ref": "data:northwind/triage-eval-v4" }
]
},
"quantitativeAnalysis": {
"performanceMetrics": [
{ "type": "answer-accuracy", "value": "0.87", "slice": "northwind/triage-eval-v4" },
{ "type": "toxicity-rate", "value": "0.002", "slice": "internal red-team prompt set" }
]
}
},
"externalReferences": [
{
"type": "digital-signature",
"url": "https://models.northwind-health.example/northwind/triage-assistant/2.3.0/model.sig",
"comment": "Sigstore bundle from OpenSSF model signing, produced in CI."
},
{
"type": "attestation",
"url": "https://ci.northwind-health.example/attestations/triage-assistant-2.3.0.intoto.jsonl",
"comment": "SLSA build provenance as an in-toto attestation."
}
]
}
]
}
Excerpt, 57 lines: the model component inside components[]: identity, hash, lineage, model parameters, metrics and the two evidence links. Keys and list entries not shown are left out; every value shown is exactly as in the full file, which runs to 519 lines pretty-printed.
Second, the part SBOM tools usually miss: the MCP server as a service, and the dependency entry that ties the application to the model, the runtime and both services.
{
"services": [
{
"bom-ref": "service:ticketing-mcp",
"name": "ticketing-mcp",
"endpoints": ["https://mcp.internal.northwind-health.example/ticketing-mcp"],
"authenticated": true,
"x-trust-boundary": true
}
],
"dependencies": [
{
"ref": "app:triage-assistant@2.3.0",
"dependsOn": [
"model:northwind/triage-assistant@2.3.0",
"pkg:pypi/vllm@0.6.3",
"service:northwind-helpcentre-index",
"service:ticketing-mcp"
]
}
]
}
Excerpt, 22 lines: the ticketing-mcp service and the application’s dependency entry. Keys and list entries not shown are left out; every value shown is exactly as in the full file, which runs to 519 lines pretty-printed.
The complete file, with the application, the base model, both datasets, all libraries, both services and the full dependency graph, is in the AIBOM Template Library (Pro), where you can also regenerate it with your own values.
Common mistakes
- The model typed as
libraryorfile. Tools then look for a package registry, not a model card. Usemachine-learning-model. - No base model. A fine-tune without
pedigree.ancestorshides the licence and the vulnerabilities it inherits. Pin the base model to a revision in itspurl, as Northwind does with@c170c708…. - Datasets only in prose. “Trained on internal tickets” in a description cannot be queried. Make each dataset a
datacomponent and reference it frommodelCard.modelParameters.datasets. - Metrics without a slice. “Accuracy 0.87” means nothing without the dataset it was measured on. Fill
slice, write the value as a decimal string (the schema types it as a string), and addconfidenceIntervalwhen you have one. - No services. An image scanner finds vLLM and PyTorch but never sees the RAG index or the MCP server. Those are what a prompt-injected model can read from or write to, so they belong in
serviceswithx-trust-boundaryset. - Gaps in the dependency graph. The schema says a node with no dependencies must still appear with an empty
dependsOn; a node missing from the graph may have unknown dependencies. Northwind lists every node. - Training-only tools shipped by accident. PEFT is needed to fine-tune, not to serve. Northwind marks it
scope: excludedso nobody chases its CVEs in production. - Hashing the wrong file. Hash the weights you deploy. Northwind merges the LoRA adapters into the base weights before release, so the hash covers the merged file.
- Signing the BOM and calling the model signed. A JSF
signatureon the document proves who issued the BOM. The model needs its own signature, linked asdigital-signature.
How to validate it
- Structure. Validate against the official schema for the
specVersionyou declare. With the CycloneDX CLI:cyclonedx validate --input-file northwind-triage-assistant-2.3.0.cdx.json --input-version v1_7 --fail-on-errors. The non-zero exit code is what fails a build. - Content. A schema-valid file can still list no models. The AIBOM Validator (Pro) checks that models and datasets are present, that every component has a licence and that models have model cards, and names the components that fail.
- Evidence. Verify the signature and the attestation with the tools that produced them. The BOM cannot do that for you.
Starting from model metadata rather than a hand-written file? The CycloneDX ML-BOM Converter (Pro) builds the model and dataset components for you.
How to diff two versions
Generate a new ML-BOM for every release and compare it with the last one. If Northwind retrained and shipped 2.4.0, the diff should show the model version and hash change, new metric values, and any dataset or library that was added, removed or bumped. The AIBOM Diff (Pro) shows components added, removed and version-changed, and flags new components without a licence. On the command line, cyclonedx diff old.cdx.json new.cdx.json --component-versions reports component version changes. Gate the release on the diff: a new base model or dataset is a decision, not a silent change.
Where to go next
This guide is part of the AIBOM Library. The same Northwind system in the other formats: SPDX 3.0 AI and Dataset profiles and Croissant RAI. To choose between formats, start with AIBOM standards and templates compared; to see one field across every format, use the AIBOM Format Field Map (Pro).
For the list of fields any AIBOM should carry, regardless of format, see the AIBOM parameters checklist.
Frequently asked questions
What is a CycloneDX ML-BOM?
A CycloneDX bill of materials that describes the machine-learning parts of a system as first-class entries: models as components of type machine-learning-model with a modelCard, datasets as components of type data, and run-time services such as a retrieval index or a tool server under services. It is the same file format as a CycloneDX SBOM, so the same tools can read it.
Which CycloneDX version supports machine learning models?
CycloneDX 1.5 added the machine-learning-model and data component types and the modelCard object. The current version is 1.7, released on 21 October 2025 and published by Ecma International as ECMA-424. Write new ML-BOMs against 1.7 unless a consumer can only read an older version.
Where do training datasets go in a CycloneDX ML-BOM?
Give each dataset its own component of type data, with a data entry for classification, sensitive data, governance and where the contents live. Then point at it from the model: modelCard.modelParameters.datasets takes either an inline description or a ref to the dataset component’s bom-ref. Refs keep each dataset described once.
How do I record a RAG index or an MCP server in CycloneDX?
As entries in services, not components, because they are called at run time rather than shipped. Record the endpoints, whether calls are authenticated, whether using the service crosses a trust boundary (x-trust-boundary) and the data that flows in or out with its classification. Then list the service in the dependencies of the application that calls it.
How do I validate a CycloneDX ML-BOM?
Check the structure against the official JSON schema for the specVersion you declare, for example with the CycloneDX CLI: cyclonedx validate --input-file bom.json --input-version v1_7 --fail-on-errors. Schema-valid only means well-formed, so also check the content: every model and dataset present, a licence on every component, a model card on every model, and evidence links that resolve.
Should the model signature go inside the BOM?
Link to it. Put the model’s signature (for example a Sigstore bundle) in the model component’s externalReferences as type digital-signature, and the build provenance as type attestation. CycloneDX also has a signature property that signs the BOM document itself with JSF; that proves who issued the BOM, not that the weights are genuine.
Sources
- CycloneDX specification overview (current version 1.7, released 21 October 2025; ECMA-424)
- CycloneDX 1.7 JSON schema (bom-1.7.schema.json)
- CycloneDX 1.7 JSON reference documentation
- CycloneDX: Machine Learning Bill of Materials (ML-BOM)
- CycloneDX 1.5 JSON schema (first with machine-learning-model and modelCard)
- CycloneDX CLI (validate and diff commands)
- OpenSSF / Sigstore model-transparency (model_signing)
- Mistral-7B-Instruct-v0.3 on Hugging Face (the real base model in the example)
Checked on 29 October 2026. PlayCISO is not affiliated with OWASP, Ecma, the Linux Foundation or MLCommons. This is general information, not legal advice.
Ready to practise the decisions these articles describe?
Run your first War Room free →One run free with a free account · more runs with Pro