Back to glossary

AI Bill of Materials (AIBOM)

An AI bill of materials (AIBOM) is a machine-readable inventory of the models, datasets, and libraries an AI system uses, including each model's lineage.

Also known as AI-BOM, AI BOM, ML-BOM, MLBOM, AI SBOM, SBOM for AI

An AI bill of materials (AIBOM), also called an AI-BOM, ML-BOM, or SBOM for AI, is a machine-readable inventory, usually CycloneDX or SPDX, of the models, datasets, and software an AI system runs on, including each model's revision, hashes, lineage, and training claims. An AIBOM records what a system contains, not what that content does.

The difference from an ordinary SBOM is what the inventory has to describe. A library is code you can read. A model is a file of weights whose behavior comes from data you cannot inspect, usually derived from another model, and sometimes stored in a format that executes code when loaded. Only three kinds of AIBOM field can be checked against bytes: the repository revision, the file hashes, and the file format. Everything about training data, bias, and limitations is text supplied by the model's author.

Most of that text is missing anyway. A July 2026 study generated AIBOMs for 97,940 Hugging Face models and measured a mean completeness score of 54.31 out of 100. Only 39.35% named any dataset (arXiv:2607.17242).

Our position: produce an AIBOM for every AI system you ship, pin every model by commit and file hash, and read everything else as a claim. Then analyze the model files and libraries themselves. Below, a pickle checkpoint that runs a shell command on load gets a schema-valid AIBOM entry with a correct SHA-256.

What is an AIBOM?

#

An AIBOM is the AI layer of a software inventory. It names every model, dataset, and AI framework a system depends on, precisely enough to look each one up after an incident.

Answering that requires knowing what a model actually is on disk. A model repository on the Hugging Face Hub is a Git repository. It holds weight files, a config, a tokenizer, a README model card with YAML metadata, and sometimes Python code. Revisions are Git commits, and main moves whenever the author pushes. The Hub hosted over 2 million models in 2025, as we noted when we added Hugging Face scanning.

Weight files come in two families. Pickle-based files (.bin, .pt, .ckpt, .pkl) are programs for Python's unpickler, which can import and call any function. Data-only formats such as safetensors hold a JSON header and raw tensor bytes. A model's lineage is metadata too: the card's base_model field names the parent, and base_model_relation takes adapter, merge, quantized, or finetune. The Hub's model card documentation describes how it displays these fields, not how it verifies them.

So an AIBOM has to answer four questions per model: which exact revision, which files, derived from what, and trained on what. The first two can be pinned to bytes. The last two are assertions.

AIBOM contents: the G7 clusters and the CycloneDX model card

#

The reference list of AIBOM contents is the G7's Software Bill of Materials for AI – Minimum Elements. The G7 Cybersecurity Working Group published it on May 12, 2026, led by Germany's BSI and Italy's ACN. It sorts elements into seven clusters: metadata, system-level properties, models, dataset properties, infrastructure, security properties, and key performance indicators. It supplements general SBOM minimum elements rather than replacing them, and the authoring agencies describe the elements as not mandatory.

CycloneDX has carried AI components since version 1.5, released in June 2023. It added the component types machine-learning-model and data and a modelCard object. Here is one model component for a placeholder repository, example-org/sentiment-mini, validated against the CycloneDX 1.6 JSON schema with cyclonedx-python-lib 11.12.0. The hash is that of the demo checkpoint we build further down:

JSON
{
  "type": "machine-learning-model",
  "bom-ref": "model-sentiment-mini",
  "name": "sentiment-mini",
  "version": "9f2c41d0b7e3a5c8d1e6f4a2b9c0d7e5f3a1b8c6",
  "purl": "pkg:huggingface/example-org/sentiment-mini@9f2c41d0b7e3a5c8d1e6f4a2b9c0d7e5f3a1b8c6",
  "licenses": [{ "license": { "id": "Apache-2.0" } }],
  "hashes": [{ "alg": "SHA-256", "content": "b3d0bef37e65517900c6f4ba2b568bc0ae38d9e0767c3bcab5b86227b5027a0a" }],
  "pedigree": {
    "ancestors": [{ "type": "machine-learning-model", "name": "distilbert-base-uncased",
                    "purl": "pkg:huggingface/distilbert/distilbert-base-uncased" }]
  },
  "modelCard": {
    "modelParameters": {
      "approach": { "type": "supervised" },
      "task": "text-classification",
      "architectureFamily": "distilbert",
      "datasets": [{ "ref": "data-reviews-2025" }]
    },
    "considerations": {
      "technicalLimitations": ["English only"],
      "ethicalConsiderations": [{ "name": "Trained on public product reviews; no PII filtering verified" }]
    }
  }
}

The purl uses the huggingface Package URL type, whose version is the repository's Git commit. The pedigree.ancestors entry records the base model. The dataset referenced by data-reviews-2025 is a separate data component in the same document. The validator also accepted the ancestor with no revision at all. The huggingface PURL type makes the version optional, so nothing in the format forces lineage to be pinned.

SPDX added AI and Dataset profiles in SPDX 3.0, released April 16, 2024. The AIPackage class has properties such as typeOfModel, informationAboutTraining, limitation, safetyRiskAssessment, and energyConsumption. DatasetPackage requires only datasetType, with optional knownBias, intendedUse, and dataCollectionProcess. Relationships named trainedOn and testedOn link a model to its data.

CycloneDX or SPDX for an AIBOM

#

The two formats cover the same ground with different shapes. Both rely on free-text strings for nearly everything beyond identity.

PropertyCycloneDX (1.5 and later)SPDX 3.0
Model elementComponent of type machine-learning-model with modelCardAIPackage in the AI profile
Dataset elementComponent of type data, with classification, sensitive data, and governance fieldsDatasetPackage, where only datasetType is required
Model-to-data linkmodelCard.modelParameters.datasets, inline or by referencetrainedOn and testedOn relationships
Lineagepedigree.ancestors on the componentancestorOf and descendantOf relationships
Model identityPURL, including pkg:huggingface/...@<commit>PURL via packageUrl, plus verifiedUsing for hashes
AI-specific fields with no direct counterpartFairness assessments, performance tradeoffsAutonomy type, safety risk assessment
Same document as the software SBOMYesYes, as profiles in one SPDX 3 document

Our verdict: use CycloneDX for AIBOMs that feed security tooling, because model identity is a PURL and the model, its datasets, and the surrounding libraries share one dependency graph. This matches our verdict for software SBOMs. Use SPDX 3.0 when a customer or regulator asks for SPDX, or when you need its dataset fields such as knownBias and dataCollectionProcess as named properties.

What is the difference between an AIBOM and an SBOM?

#

An AIBOM is an SBOM that also describes models and datasets, the two components whose behavior cannot be read from source. A library's version identifies code you can review. A model's revision identifies weights whose behavior was set by training data, and the AIBOM can only report what the trainer says about that data.

Three more differences follow. Model identity lives on a hub with mutable branch names, so an unpinned reference like main names a moving target. Model lineage is a chain of derivations, such as fine-tune of a quantization of a base model, that package dependency graphs were not built to express. And model files can be executable: loading a pickle-based checkpoint runs code, with no install step involved. An AIBOM does not replace the SBOM. In CycloneDX, both live in one document.

Which AIBOM fields can you actually verify?

#

You can verify an AIBOM's identity fields against bytes, and almost nothing else. The figure maps one illustrative AI service.

The figure shows an application, review-triage-service 4.2.0, with two children. The first is the library pkg:pypi/litellm@1.107.0, whose version and wheel hash match the registry, and which was a malicious release. The second is the model sentiment-mini pinned to commit 9f2c41d. Under the model sit five entries. model.safetensors (SHA-256 521ca9c2…) and model.pkl (SHA-256 b3d0bef3…) are bound to bytes, and the pickle file calls posix.system when loaded. The base model distilbert-base-uncased is named without a revision. The dataset reviews-2025 and the limitations, bias, and intended-use fields are self-reported text.

AIBOM fieldExampleCheck it againstVerdict
Model revisionpkg:huggingface/...@9f2c41d…The hub's Git commitVerifiable when present. The PURL version is optional.
File hashesSHA-256 of model.pklThe fileVerifiable. Proves identity, not safety.
File format.pkl versus .safetensorsThe file's header or opcodesVerifiable, and decisive for code execution on load
Librariespkg:pypi/litellm@1.107.0Registry hashesVerifiable. Says nothing about intent.
Base modelpedigree.ancestorsNothing, unless pinned by revisionClaim
Training datasetsmodelParameters.datasets, trainedOnNothing in the weightsClaim
Limitations, bias, intended useconsiderations, knownBiasNothingClaim
Performance metricsquantitativeAnalysis, metricRerun the evaluationReproducible, at your cost

The study cited above shows how thin the claim rows are in practice. Across 97,940 generated AIBOMs, license information appeared in 73.14%, dataset information in 39.35%, and the SPDX-aligned informationAboutTraining field in 0.00%. The authors generated the documents with the OWASP GenAI Security Project AIBOM Generator, which reads Hub metadata. An AIBOM generator can only copy what the model card says.

A valid AIBOM for a model that runs a shell command

#

We built a two-tensor "model" in two formats. The pickle version carries one extra object whose __reduce__ method tells the unpickler to call os.system("id"):

Python
import pickle, os, numpy as np
from safetensors.numpy import save_file
weights = {"classifier.weight": np.zeros((2, 4), dtype=np.float32),
           "classifier.bias": np.zeros((2,), dtype=np.float32)}

class Payload:
    def __reduce__(self):
        return (os.system, ("id",))

# A checkpoint whose state dict is fine, plus one entry that runs a command on load.
with open("model.pkl", "wb") as f:
    pickle.dump({"state_dict": weights, "_meta": Payload()}, f, protocol=4)

save_file(weights, "model.safetensors")

The AIBOM component shown earlier describes model.pkl. Its hash is correct and the document passes schema validation (Python 3.13, cyclonedx-python-lib 11.12.0, safetensors 0.8.0, numpy 2.5.3):

Shell Session
$ sha256sum model.pkl
b3d0bef37e65517900c6f4ba2b568bc0ae38d9e0767c3bcab5b86227b5027a0a  model.pkl
$ python3 -c "from cyclonedx.validation.json import JsonStrictValidator; from cyclonedx.schema import SchemaVersion; print(JsonStrictValidator(SchemaVersion.V1_6).validate_str(open('aibom.cdx.json').read()))"
None

None means no validation errors. Now load the model the way a naive pipeline would:

Shell Session
$ python3 -c "import pickle; pickle.load(open('model.pkl','rb'))"
uid=0(root) gid=0(root) groups=0(root)

The Python documentation is blunt about this: "The pickle module is not secure. Only unpickle data you trust." The fix is to read what the file asks for before anything runs. This unpickler records every global the stream imports and hands back an inert stub instead:

Python
import pickle, sys

class Stub:
    def __init__(self, *args, **kwargs): pass
    def __setstate__(self, state): pass

class Recorder(pickle.Unpickler):
    """Print every global the pickle imports. Hand back a stub so nothing real runs."""
    def find_class(self, module, name):
        print(f"{module}.{name}")
        return Stub

with open(sys.argv[1], "rb") as f:
    Recorder(f).load()
Shell Session
$ python3 pickle_imports.py model.pkl
numpy._core.multiarray._reconstruct
numpy.ndarray
numpy.dtype
posix.system

The first three imports rebuild the NumPy arrays and are normal. The fourth, posix.system, is the shell (on Windows it appears as nt.system). Benign checkpoints also import functions, so detection means allowlisting what tensor reconstruction needs, not flagging every import. PyTorch takes that approach: since version 2.6, torch.load defaults to weights_only=True, an unpickler that "is not allowed to dynamically import anything during unpickling" (PyTorch serialization notes). Older loaders and other frameworks do not. PyTorch's own .pt files are ZIP archives with a data.pkl inside, so the same analysis applies to them. Unsafe torch.load and pickle paths were behind the PyTorch Lightning deserialization vulnerabilities disclosed under VU#252619 in April 2025.

The safetensors file has nothing to execute. Its first 8 bytes give a header length, followed by a JSON header and raw tensor bytes:

Plain text
{"classifier.bias": {"dtype": "F32", "shape": [2], "data_offsets": [0, 8]},
 "classifier.weight": {"dtype": "F32", "shape": [2, 4], "data_offsets": [8, 40]}}

Choosing safetensors removes load-time code execution from the weight files, not from the repository. Models that need trust_remote_code=True run Python files from the repository, and the transformers security policy says to "always verify the content of the modeling files" and to set a revision. GGUF files carry chat templates that have been used to plant backdoors. These are the cases malicious AI models exploit, and none of them shows up in an AIBOM as anything but a filename.

An AIBOM earns its keep as a work order for analysis. Socket's model scanner inspects Hugging Face model files for deserialization attacks, including pickle code execution, TensorFlow and Keras Lambda layer injection, and GGUF metadata payloads. Enterprise users can upload a CycloneDX AIBOM, and Socket scans the Hugging Face models it lists by PURL (announcement, changelog).

The software half of an AIBOM fails the same way

#

The libraries that serve and train models are packages, and they get compromised like any other. In the LiteLLM compromise on October 21, 2026, litellm 1.107.0 on PyPI shipped Python startup files that collect cloud credentials and AI-tool tokens. The payload runs whenever Python starts in the environment, even if nothing imports LiteLLM. The package receives about 23 million weekly downloads, and Socket's AI scanner flagged all seven wheels seven minutes after publication.

An AIBOM generated from an affected build that day lists pkg:pypi/litellm@1.107.0 with correct hashes. A malicious package looks perfectly valid in any bill of materials. Matching it against CVE data finds nothing, because malware does not get a CVE. The release's container provenance did its job too: it faithfully recorded a source repository named Berri-AI/litellm, one hyphen away from the real BerriAI/litellm. Provenance tells you what a build consumed, not whether those inputs deserved trust.

The lightning compromise on April 30, 2026 hit the training side. Versions 2.6.2 and 2.6.3 of the deep learning framework ran a downloader and an 11 MB obfuscated JavaScript payload when the module was imported. In both cases an inventory would have been accurate, and the answer had to come from analyzing the package before it was installed.

Is an AIBOM required?

#

No law requires a document called an AIBOM, as of November 2026. The G7 minimum elements are voluntary. In the US, OMB memo M-26-05 of January 23, 2026 rescinded the federal software supply chain mandates and made SBOMs optional in procurement.

The EU asks for adjacent artifacts. Since August 2, 2025, Article 53(1)(d) of the AI Act has required providers of general-purpose AI models to publish "a sufficiently detailed summary about the content used for training." That is a summary for the public, not a machine-readable inventory. Obligations for high-risk systems listed in Annex III now apply from December 2, 2027, after Regulation (EU) 2026/1744 delayed them. The EU Cyber Resilience Act requires an SBOM for products with digital elements from December 11, 2027, whether or not they contain a model.

Building an AIBOM that holds up

#

Generate the AIBOM from what the system loads, not from model cards. Record the resolved commit for every model and the SHA-256 of every file the loader opens. The commit pins the repository; the file hashes catch a mirror or cache that serves different bytes. Neither tells you the files are safe.

Record the format of every weight file, and refuse pickle where a safetensors file exists. In transformers, use_safetensors=True makes loading fail when no safetensors file is present. That closes the largest load-time code execution path. It does not cover repository Python code, GGUF templates, or the libraries around the model.

Pin ancestors by revision when you know them. When you do not, say so: a CycloneDX compositions entry with "aggregate": "unknown" marks the gap explicitly, which is more honest than a bare name that looks complete.

Keep each AIBOM with the release it describes. A hub author can force-push a branch or delete a repository; your record of which commit you shipped survives both.

The open problem is the data. Nothing in a set of weights proves what they were trained on. A training pipeline can sign an attestation listing its datasets, the way SLSA provenance attests to a build. That converts an unsigned claim into a signed one, and LiteLLM's provenance shows how faithfully such a record can capture a bad input. Until a third party can check training data against the weights, the dataset section of every AIBOM is testimony, and only the fields bound to bytes deserve automated trust.