Model provenance is the verifiable record of where an ML model came from: which source commit, training data, base model, and build platform produced the weights. In practice it ships as a signed attestation, SLSA build provenance in the in-toto format, alongside a model signature proving the files have not changed since they were signed.
Most teams treat a downloaded model the way they would treat a trusted internal artifact: pull the weights, load them, serve. The assumption baked into that workflow is that the file on disk is the file the publisher intended to ship. For software packages, an ecosystem of signing, lockfiles, and provenance exists to back that assumption. For model weights, until recently, nothing did. A serialized checkpoint pulled from a hub was an unauthenticated binary, and in several common formats, loading it executes code. Closing that gap takes model signing, provenance attestations, and load-time verification, and each of them stops being sufficient on its own at a predictable point.
Model Provenance vs. Model Signing
Model signing proves who published a model and that its files are unchanged. Model provenance proves how the model was built: from which source, inputs, and base checkpoint, on which build platform. Signing answers “is this the artifact the publisher produced?” Provenance answers “was it produced by a process I trust?” A production load path needs both, plus the controls neither one covers.
| Control | Question it answers | Format | Tooling (checked October 2026) | What it does not prove |
|---|---|---|---|---|
| Model signing | Who published these exact files? | Sigstore bundle over a manifest of per-file hashes | OpenSSF model-signing 1.1.1 | That the model is benign |
| Build provenance | Which source, inputs, and builder produced it? | in-toto statement, predicate https://slsa.dev/provenance/v1 | GitHub actions/attest@v4, gh attestation verify | That the training data or code was trustworthy |
| Lineage metadata | Which base model was it derived from? | Model card YAML (base_model, datasets) | Hugging Face Hub model tree | That the claim is true; the uploader declares it |
| ML-BOM | Which models, datasets, and configurations make up the system? | CycloneDX ML-BOM | CycloneDX tooling | File integrity, unless the BOM is itself signed |
The two cryptographic rows compose: the signature covers a manifest of file digests, and the SLSA attestation lists the same files as subjects with their digests. Check both and you know the bytes are what a trusted identity published and the output of a build you can inspect.
Why the Model Is a Supply-Chain Attack Surface
The core problem is that a model file is not inert data. The Python serialization format that still backs a large fraction of published checkpoints encodes a sequence of opcodes that can include arbitrary callable invocation. torch.load, joblib.load, and the equivalent deserializers on attacker-influenced bytes are remote code execution primitives. The model “loads,” and somewhere in that deserialization an attacker’s payload runs with the privileges of your serving process.
This is not theoretical. In February 2025, ReversingLabs documented malicious models hosted on Hugging Face that carried reverse-shell payloads in their serialized data. The technique, which the researchers named “nullifAI,” deliberately corrupted the serialization stream so that the platform’s picklescan scanner failed to flag it, while the payload still executed when the file was deserialized. Two models were affected and taken down after disclosure. The significant part is not the count; it is the demonstration that the artifact and the scanner designed to vet it can be defeated by the same input. Scanning is a useful layer, but it inspects content the attacker controls, using assumptions the attacker can study.
The attack surface is broader than the weights file. It includes:
- The checkpoint itself: deserialization RCE, or a backdoored model that behaves normally except on a trigger input.
- The retrieval path: a typosquatted repo, a hijacked maintainer account, or a man-in-the-middle on an unauthenticated download.
- The conversion and quantization steps: every transform between the published artifact and what you serve is an opportunity for tampering if it isn’t verified end to end.
- The dependency graph: the loader libraries (
transformers,vllm, custom code in a repo’s*.pyfiles thattrust_remote_code=Truewill happily execute).
Defense-in-depth here means answering two distinct questions at load time: is this the artifact the publisher actually produced (integrity and authenticity), and was it produced by a process I trust (provenance). Signing answers the first. Provenance attestations answer the second.
Model Signing: Authenticating the Artifact
Model signing binds a cryptographic signature to a model’s content so that a verifier can confirm, before loading, that the bytes match what a known identity signed. In 2025 this moved from ad-hoc to standardized: the OpenSSF AI/ML Working Group released Model Signing v1.0, a library and CLI authored by engineers from Google, NVIDIA, and HiddenLayer that signs and verifies models of any format and any size, hashing the constituent files into a manifest and producing a signature over that manifest. It installs from PyPI as model-signing; 1.1.1 (October 2025) was the current release when checked in October 2026, and it needs Python 3.9 or newer.
The default backend is Sigstore, which is the more interesting design choice. Traditional signing requires the publisher to manage a long-lived private key: the thing that gets leaked, committed to a repo, or stolen. Sigstore’s keyless flow instead binds the signature to an OpenID Connect identity (a CI workload identity, or a developer’s federated identity) and records the signing event in a public, append-only transparency log. There is no long-lived secret to rotate or exfiltrate, and the transparency log makes a signature over a malicious artifact auditable after the fact rather than invisible.
The model-signing project (sigstore/model-transparency) supports several PKI backends (Sigstore keyless, your own public/private keypair, X.509 certificates, and PKCS#11 hardware tokens), all producing the same signature format. Verification needs the path to the detached signature and, for Sigstore, the identity and OIDC issuer used at signing time:
pip install model-signing
# Sign with Sigstore keyless (the default); starts an OIDC flow
model_signing sign sigstore ./my-model/ --signature model.sig
# Verify before loading; identity and issuer come from config
model_signing verify sigstore ./my-model/ \
--signature model.sig \
--identity "$RELEASE_IDENTITY" \
--identity-provider "$OIDC_ISSUER"
For a model signed in GitHub Actions, the README gives the identity as the signing workflow’s URL pinned to a git ref, and the issuer as https://token.actions.githubusercontent.com. Pin that workload identity in production, not a person’s.
The discipline that matters is where you verify. The model-signing model is to check signatures at every boundary the artifact crosses: when it is uploaded to a hub, when it is selected for deployment, and when it is consumed as input by another model. A signature checked once at download and never again does not protect against tampering in your own conversion pipeline.
# Verification belongs in the load path, not in a one-time setup script.
import os
import subprocess
# The issuer is configuration, not a constant to paste in. For workflows signed
# from GitHub Actions the value is GitHub's OIDC issuer; other CI systems and
# identity providers publish their own.
OIDC_ISSUER = os.environ["MODEL_SIGNING_OIDC_ISSUER"]
def load_model_verified(model_dir: str, sig_path: str,
expected_identity: str) -> "Model":
result = subprocess.run(
["model_signing", "verify", "sigstore", model_dir,
"--signature", sig_path,
"--identity", expected_identity,
"--identity-provider", OIDC_ISSUER],
capture_output=True,
)
if result.returncode != 0:
raise RuntimeError(
f"Model signature verification FAILED for {model_dir}: "
f"{result.stderr.decode()}"
)
# Only now do we touch the bytes. Even so, prefer safetensors over the
# legacy serialization so a verification gap is not also an RCE.
return _load_safetensors(model_dir)
Two caveats keep this honest. First, a valid signature proves authenticity, not safety: a publisher can sign a backdoored model, and the signature will verify correctly. Signing tells you who produced the artifact and that it hasn’t changed since; it does not tell you the artifact is benign. Second, signing is only as meaningful as your identity policy. Verifying against “any identity that signed” is nearly worthless; verifying against a specific, expected releaser identity and a specific issuer is the control. Keep the issuer in configuration rather than hard-coded, because the right value depends on where the artifact was signed. For GitHub Actions it is the token’s iss claim, documented in the GitHub Actions OIDC reference; the audience is a separate claim.
Hub-side adoption has started. NVIDIA has signed every NVIDIA-published model in the NGC Catalog since March 2025, and Kaggle ran an experimental integration that signs with the uploader’s OIDC identity. NGC models verify against NVIDIA’s published certificate rather than a Sigstore identity, using model_signing verify certificate with --certificate_chain, per NVIDIA’s NIM documentation.
Provenance: Trusting the Process, Not Just the Publisher
Signing authenticates the artifact. Provenance authenticates how it was built. SLSA (Supply-chain Levels for Software Artifacts), developed at Google, contributed to the OpenSSF in 2021, and now vendor-neutral, defines incrementally adoptable levels of build integrity, expressed as machine-readable attestations in the in-toto format. The current specification is SLSA v1.2, which added a Source track for source code development alongside the Build track that applies to model artifacts.
For models, the relevant SLSA idea is build provenance: a signed statement describing what was built, from what source, by what build process, in what environment. The Build track levels are coarse but useful as a target:
- Build L1, Provenance exists: provenance describes how the artifact was built. This alone catches “where did this checkpoint even come from” questions, but SLSA rates it as easy to bypass or forge; it stops mistakes, not attackers.
- Build L2, Hosted build platform: provenance is signed by the build platform, so post-build tampering is detectable.
- Build L3, Hardened builds: the build runs on a hardened, isolated builder that resists tampering during the build, raising the bar against a compromised build step injecting a backdoor.
What goes in a model provenance record
A SLSA provenance attestation is an in-toto statement (_type of https://in-toto.io/Statement/v1) with the predicate type https://slsa.dev/provenance/v1. The build provenance spec fixes the fields; what you put in them for a training or fine-tuning job is your decision:
| Field | What the spec records | For a model build |
|---|---|---|
subject | Output artifacts and their digests | Every weight shard, tokenizer, and config file |
buildDefinition.externalParameters | Inputs from outside the platform, which verifiers must check | Training config, hyperparameters, dataset and base-model IDs |
buildDefinition.resolvedDependencies | Artifacts fetched during the build, with digests | Code commit, base checkpoint digest, dataset snapshot digests |
runDetails.builder.id | URI of the build platform; determines the SLSA level | The CI or training platform identity |
runDetails.metadata | invocationId, startedOn, finishedOn | The job ID that locates training logs |
A model that ships with a SLSA provenance attestation lets a consumer answer questions a signature alone cannot: which training data manifest and code commit produced these weights, whether the build ran in an ephemeral isolated environment, and whether the chain from source to artifact is unbroken. For internal pipelines, generating provenance for your own fine-tunes is often the higher-leverage move. It gives you an audit trail when production monitoring shows a model misbehaving and you need to know exactly what produced the artifact you’re serving.
Lineage across fine-tunes, adapters, and quantizations
Most served models are several hops from a foundation model. Hugging Face lets uploaders declare the chain in model card YAML: base_model, an optional base_model_relation (adapter, merge, quantized, or finetune), and datasets (Hugging Face model card docs). That is the uploader’s claim, not evidence, until each parent checkpoint appears by digest in the resolvedDependencies of a signed attestation.
Without one, lineage can be tested statistically. Model Provenance Testing for Large Language Models detects derived models with black-box access only, reporting 90 to 95% precision and 80 to 90% recall across more than 600 models of 30M to 4B parameters: useful for auditing a claimed parent, not a substitute for a signed record.
How to Generate and Verify ML Model Provenance in CI
For models trained or fine-tuned in GitHub Actions, provenance takes one workflow step and one verification command. GitHub’s artifact attestations provide SLSA v1.0 Build Level 2 on their own and can help meet Level 3 when the build runs in a reusable workflow. Public repositories log attestations to Sigstore’s public transparency log; private repositories use GitHub’s own Sigstore instance, which has none.
-
Build in CI, not on a workstation. A laptop build has no hosted platform to sign its provenance, which caps it at Build L1.
-
Attest the exported files. With the permissions from GitHub’s attestation guide,
actions/attestgenerates SLSA build provenance by default;subject-pathaccepts a glob, up to 1024 subjects per call:permissions: id-token: write contents: read attestations: write steps: # ... checkout, fetch pinned base model and data, train, export ... - name: Attest model build provenance uses: actions/attest@v4 with: subject-path: 'out/model/*' -
Sign the same directory with
model_signingin that job, so the identity consumers pin is the workflow, not a person. -
Verify before deployment with the GitHub CLI. It requires
--owneror--repo, enforces thehttps://slsa.dev/provenance/v1predicate type by default, and expectshttps://token.actions.githubusercontent.comas the issuer:gh attestation verify out/model/model.safetensors \ --repo my-org/model-training \ --signer-workflow my-org/model-training/.github/workflows/train.ymlPinning
--signer-workflowstops other workflows in the organization from producing an acceptable attestation.--deny-self-hosted-runnersrejects self-hosted builds; GPU training often runs on self-hosted machines, which makes that fleet part of what you trust. -
Record the result. Log verified digests, signer identity, and the attestation reference with each deployment, alongside the fields in LLM audit logging, so an incident review can tie a served model to its build.
Putting the Layers Together
No single control is sufficient, which is the recurring theme of defensible AI architecture. A practical model supply-chain posture stacks:
- Format hygiene. Prefer
safetensorsover the executable-capable serialization wherever the ecosystem allows; it cannot carry executable payloads. Treattrust_remote_code=Trueas a privileged operation requiring review, not a convenience flag. - Scanning. Run a model scanner (picklescan, ModelScan, or a commercial equivalent) as a fast first filter, knowing it inspects attacker-controlled content and can be evaded, per nullifAI.
- Signature verification at every boundary. Verify against a specific expected identity in the load path, not once at download.
- Provenance attestation. Require SLSA build provenance, Build L2 or better, for artifacts you produce, and prefer published models that ship it.
- Runtime isolation. Load and run models in a sandbox with least privilege and no outbound network by default, so that a verification gap or a signed-but-backdoored model has a constrained blast radius. This is the same containment logic that makes output-side controls hold when an upstream layer is bypassed.
The order matters: cheap filters first, cryptographic verification before the bytes are touched, and isolation as the assumption-of-failure backstop. The supply-chain controls are now mature enough (OpenSSF Model Signing, Sigstore, SLSA, GitHub artifact attestations) that the question for most teams is not whether the tooling exists but whether the load path actually invokes it before the deserializer runs.
FAQ
what is the difference between model provenance and model lineage
Lineage is the family tree: which base model a fine-tune, adapter, or quantization descends from. Provenance is broader and verifiable: the signed record of the source commit, inputs, builder, and output digests for one build. Lineage becomes provenance when each hop’s parent checkpoint appears, by digest, in a signed attestation rather than a self-declared model card field.
do you need sigstore to sign an ml model
No. Sigstore keyless signing is the default in the OpenSSF model-signing tool, but the same tool signs with your own key pair, X.509 certificates, or a PKCS#11 hardware token, all in one signature format. The trade-off is key custody: you protect and rotate a private key yourself instead of relying on Sigstore’s short-lived certificates.
what does an openssf model signature contain
It is a detached Sigstore bundle. Inside is a DSSE envelope holding an in-toto statement with the predicate type https://model_signing/signature/v1.0, which covers a manifest of hashes for the model’s files or file shards. Verification recomputes those hashes and compares them with the signed manifest, so any file that differs fails the check.