Safeguard
AI Security

Model Weights as Supply Chain Artifacts: Signing and Provenance

A 4 GB safetensors file deserves the same signing, hashing, and provenance discipline as a container image. How to actually do it with Sigstore, OCI registries, and AIBOMs.

Daniel Osei
Security Analyst
6 min read

Model weights are supply chain artifacts and should be handled like container images: content-addressed by hash, signed at publication, verified at load time, and traced back to their training pipeline via attestation. Today most organizations do none of this. They wget a multi-gigabyte file from Hugging Face, load it into a process with GPU access and production data, and call it "using an open model." The 2024 discovery of malicious pickle-based models on Hugging Face — some with reverse shells triggered at torch.load — settled the question of whether weights are executable attack surface. What is left is engineering.

Why weights are a harder artifact than containers

Containers had a decade to grow Notary, then Cosign, then SLSA. Weights start further behind for three structural reasons:

  • Serialization formats execute code. Python pickle (inside legacy .bin and .pt files) runs arbitrary bytecode during deserialization. A model file is not data; it is a program until proven otherwise. safetensors and GGUF fixed this by design, but pickle checkpoints still circulate widely.
  • Provenance is genuinely hard to attest. A container's Dockerfile is inspectable. A model's "source" is a training run over a dataset you cannot see, on infrastructure you cannot audit. Signing tells you who published the weights, not what went into them — poisoned training data survives a valid signature.
  • Mutation is silent. A Hugging Face repo is a git repo; main moves. Downloading org/model without a revision pin is FROM ubuntu:latest all over again, except the diff is billions of opaque floats.

Step 1: eliminate executable formats

Before signing anything, stop loading programs:

# Bad: executes pickle opcodes from the file
model = torch.load("model.bin")

# Better: refuses non-tensor payloads
from safetensors.torch import load_file
state = load_file("model.safetensors")

Enforce it in CI: fail any build that vendors .bin, .pt, or .pkl checkpoints. Scan incoming files with picklescan or modelscan (Protect AI) for the interim period where legacy checkpoints still exist. Hugging Face runs picklescan server-side, but treat that as a courtesy, not a control — several 2024 bypasses proved the scanners incomplete.

Step 2: pin and hash, always

The zero-infrastructure baseline any team can adopt this week:

# Pin the exact revision, not a branch
huggingface-cli download meta-llama/Llama-3.1-8B \
  --revision 0e9e39f2 --local-dir ./models/llama31

# Record and verify content hashes
sha256sum ./models/llama31/*.safetensors > weights.sha256
sha256sum -c weights.sha256   # in every deploy pipeline

Store weights.sha256 in git next to the serving config. Now a swapped weight file fails the deploy instead of silently changing model behavior. This is unglamorous and catches the majority of realistic tampering scenarios.

Step 3: sign with Sigstore model-signing

The OpenSSF model-signing project (v1.0 in April 2025, an OpenSSF Model Signing SIG deliverable) brings keyless Sigstore signing to weights:

pip install model-signing
# Sign (produces a Sigstore bundle covering a manifest of all files)
model_signing sign ./models/llama31 --signature model.sig
# Verify against the publisher's identity
model_signing verify ./models/llama31 --signature model.sig \
  --identity release@example.com --identity_provider https://accounts.google.com

Because it is keyless, verification binds the artifact to an OIDC identity and lands in a transparency log — the same trust story as Cosign for containers. Hugging Face and Google both backed the effort, and NVIDIA started publishing signatures for NGC models in 2025. If you publish internal models, signing at the training pipeline's exit point costs one CI step.

For distribution, OCI registries work well: push weights as OCI artifacts with ORAS, and your existing registry policies — Cosign verification, admission control, replication — apply unchanged. One artifact discipline instead of two.

Step 4: attest provenance, not just identity

A signature says "this org published these bytes." SLSA-style provenance says "these bytes came from this pipeline, these inputs, this commit." For models, an attestation should capture: base model and revision (for fine-tunes), dataset identifiers and hashes, training code commit, and the hyperparameter config. in-toto attestation formats handle this today, and CycloneDX 1.6's ML profile can carry the result as part of an AIBOM.

That last part matters operationally: when the next poisoned-dataset disclosure lands, "which of our deployed models were fine-tuned from that base?" must be a query, not a project. Keeping model components inventoried alongside packages in SBOM Studio is how teams we work with make that query take seconds; the same inventory feeds SCA policy gates so an unsigned or unpinned model fails the deploy like a critical CVE would.

Verification at load time is the whole point

Signing without verification is theater. The enforcement points, in descending order of value:

Enforcement pointMechanism
Deploy pipelinemodel_signing verify + sha256sum -c as required steps
Admission controlOCI registry policy: only signed model artifacts pullable
Serving processLoader refuses unhashed paths; safetensors only
Incident responseTransparency log lookup: when was this artifact signed, by whom

Teams running inference at the edge add one more: verify on device at model download, since edge boxes are exactly where an on-path attacker would swap weights.

Frequently asked questions

Does signing protect against poisoned training data?

No. A signature authenticates the publisher and the bytes, not the training process. Data poisoning requires provenance attestations (what dataset, what pipeline) plus behavioral evaluation. Signing is necessary — it stops artifact swaps — but it is the floor, not the ceiling.

Are safetensors files completely safe to load?

Safe from code execution at load time, yes — the format stores raw tensors and a JSON header, with no serialized code paths. The model's behavior can still be malicious (backdoored weights), and the surrounding repo can carry malicious config.json handling code, so keep loading trust_remote_code=False.

What should I do about the pickle checkpoints we already depend on?

Convert them: load once in an isolated sandbox (no network, throwaway VM), export to safetensors, hash, sign, and re-host internally. Scan with modelscan before that first load. Then ban the originals in CI so the tree stays clean.

Is any of this required by regulation yet?

Increasingly. The EU AI Act's provider obligations (phasing in through 2026–2027) require technical documentation and traceability that provenance attestations map onto directly, and US federal procurement guidance now references ML supply chain integrity. An AIBOM with signed components is the artifact auditors are starting to ask for.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.