The Model as an Artifact: Formats, Distribution, and the Supply Chain
A deployable model is a versioned bundle, not a filename. Chapter 73 defined the release contract that determines whether a team may and can use a model. This chapter follows the downloadable path after that decision. A loader may need weight shards, a shard index, architecture configuration, a tokenizer, a chat template, generation defaults, adapters, native libraries, and sometimes custom code. If any one of those inputs drifts, the deployed behavior can drift even when the main weight file keeps the same name.
The artifact therefore needs two kinds of control. Software supply-chain controls establish which bytes were selected, how they arrived, and how they were transformed. Model evaluation addresses what the resulting system does. A digest cannot find a learned backdoor, and an evaluation cannot detect a changed file that was never tested. The deployment record needs both.
A model arrives as a bundle
The required files depend on the runtime, but their roles are predictable:
- weight files, possibly split into shards, plus the shard index that maps tensor names to files;
- architecture configuration, tensor naming conventions, and numerical type information;
- tokenizer vocabulary, normalization rules, special-token assignments, and any preprocessor configuration;
- chat template and generation defaults that turn an application message into model input and control decoding;
- adapters, quantization metadata, projection sidecars, or other components applied to the base weights;
- loader or modeling code, including any custom code imported from the release repository;
- documentation and policy artifacts such as the license, model card, signature, provenance attestation, and bill of materials.
Not every release contains every role. The manifest should say which roles this runtime requires and identify each supplied file. Here, a bundle under review is represented as
An integrity gate can then be written as
In this formula, every descriptor binds one path and role to the bytes the reviewer expects:
where:
B : the bundle under review
v : the exact release revision
M : the canonical manifest for the bundle
F : the set of downloaded files
n : the number of descriptors in M
i : one descriptor index from 1 through n
d_i : the descriptor for file i
p_i : its normalized relative path
r_i : its artifact role, such as weights, tokenizer, or config
m_i : its media type or serialization format
s_i : its expected size in bytes
h_i : its expected cryptographic digest
b_i : the downloaded bytes at path p_i
SHA256 : the SHA-256 cryptographic hash function
pinned(v) : true only when v is immutable and fully resolved
paths(F) : the set of paths present in the downloaded bundle
safe(p_i) : true only for a normalized relative path with no traversal
|b_i| : the byte length of b_i
land : logical AND; every condition must hold
bigwedge : logical AND across all n descriptors
The exact path-set check is intentional. A digest for the weights does not
authorize an unlisted modeling_custom.py, and a partial snapshot is not a
complete bundle. A production manifest also records its schema version and is
serialized canonically before its root digest is computed or its signature is
created. This gate proves only that the selected files match the canonical
manifest. Authentication and behavioral acceptance come later.
Loading is a security boundary
Python pickle is a binary serialization format, but it is not a data-only
format. During unpickling, instructions can import globals and invoke callables.
Python therefore warns never to unpickle data from an untrusted source
(Python Software Foundation 2026). PyTorch checkpoints need a more precise description than
“pickle files.” Since PyTorch 1.6, torch.save normally writes an uncompressed
ZIP64 archive whose data.pkl describes the object graph while tensor storages
occupy separate files (PyTorch Contributors 2026). Unrestricted
torch.load(..., weights_only=False) can still follow attacker-selected pickle
reconstruction instructions.
The current default is safer than the historical one. Starting with PyTorch
2.6, torch.load uses weights_only=True when the caller does not provide a
custom pickle module. The restricted unpickler supports plain tensor state
dictionaries and a limited set of primitive types without dynamic imports. It
narrows the code-execution surface, but the PyTorch documentation still warns
against untrusted input: weights_only=True does not prevent denial of service,
and the documentation notes possible memory-corruption paths in the loader or
downstream tensor handling (PyTorch Contributors 2026). Setting
weights_only=False or adding custom globals widens the boundary again.
Safetensors changes the payload semantics. Its file begins with an eight-byte header length, followed by a JSON header containing tensor names, dtypes, shapes, and byte offsets, then the raw tensor bytes. It carries tensor metadata and bytes, not executable reconstruction instructions (Safetensors Project 2026). A 2023 external audit found no critical arbitrary-code execution path in the reviewed implementation after reported validation and parser issues were fixed, while stressing that the time-bounded review was not proof of absence (Dahlgren et al. 2023). The format removes intentional callable dispatch; implementation defects and surrounding loaders remain separate trust boundaries.
That boundary matters during conversion. Converting an untrusted pickle to
safetensors first requires reading the pickle, so the conversion step can
execute the payload it is meant to remove. Run such conversions in a disposable
sandbox with no credentials or network, read-only source files, a fresh output
directory, and resource limits. A safetensors weight file also does not make the
rest of a repository safe. In Transformers, trust_remote_code=True explicitly
loads custom modeling code; the official guidance recommends reviewing that
code and pinning a full commit hash (Hugging Face 2026).
Other formats make different tradeoffs. GGUF is designed for fast loading by GGML-based executors and stores extensible metadata, tensor descriptors, and aligned tensor bytes. Per-tensor types support quantized encodings. Although self-contained deployment is a design goal, current GGUF releases may be sharded or accompanied by sidecars, so the manifest still matters (ggml Project 2026). ONNX represents a versioned computation graph, operators, types, functions, and tensor initializers rather than weights alone; large tensors can live in external data files (ONNX Project 2026). A graph conformance check is not a security verdict, and a parser or native operator runtime is still code exposed to attacker-controlled sizes, shapes, and metadata.
The useful distinction is not one safe-or-unsafe ranking. Ask whether a format contains unrestricted object reconstruction, restricted reconstruction, tensor-only data, or an executable graph. Then review the exact parser, operator libraries, companion files, and resource limits used in production.
Security reports illustrate why the distinction must stay specific. In 2024,
JFrog reported around one hundred PyTorch or Keras artifacts whose payloads its
scanner classified as genuinely harmful; one investigated pickle checkpoint
contained a reverse shell (Cohen 2024). The report did not establish
how many were downloaded or executed. ReversingLabs later found two malformed,
7z-wrapped pickle artifacts that the hub had not flagged. Default torch.load
could not load those repositories, but a benign reproduction showed that an
extracted pickle stream can execute an earlier opcode before failing on later
corruption (Zanki 2025). These reports demonstrate coverage
gaps in particular scanners, not the prevalence of compromise.
Pin the release, then verify every blob
A repository name is a discovery handle. A branch or tag is mutable, and a
human-readable filename can be replaced. A full repository commit names a
specific snapshot. Hugging Face's download client accepts a branch, tag, pull
request, or commit as revision, and its documentation requires the full
commit hash when a commit is supplied (Hugging Face 2026). Pin the
repository commit because configuration, tokenizer files, templates, and code
can change independently of the weights.
A commit pin and a file digest answer different questions. The commit selects repository state while it remains available. It does not authenticate the publisher: Git author fields are not identity proof, and commit signing is a separate mechanism. A file digest checks the response bytes obtained through a cache, mirror, object store, or registry. The consumer should retain both the repository commit and the digest of every required blob.
Content-addressed distribution makes this relationship explicit. The OCI Distribution Specification is content-type agnostic: a manifest references blobs through descriptors containing media type, size, and digest. A tag is a human-readable pointer, while a manifest digest identifies exact manifest bytes (Open Container Initiative 2025). A model does not have to be distributed through OCI to use the same design. Pin a root manifest digest, verify the response bytes for every descriptor, and store signatures or attestations as linked records. The digest does not guarantee that the registry will retain the content, so an approved release also needs an independent cache or mirror for rollback and disaster recovery.
Shards introduce failure cases that a single checksum does not expose. Verify that every shard named by the shard index is present, that no tensor name maps to two shards, and that the index itself is in the manifest. A partial download or resumed transfer belongs in quarantine until its final size and file digest match. Only then should the downloader atomically move it into a shared cache. Never let an interrupted file occupy the path of a verified blob.
The following dependency-free example models an exact manifest check. It builds the trusted manifest separately, verifies every size and digest, and rejects any unexpected file. In production, the trusted manifest must come from an authenticated release process or a separately approved digest. Computing a new manifest from whatever arrived would prove nothing.
from hashlib import sha256
trusted_files = {
"config.json": b'{"model_type":"demo"}\n',
"model.safetensors": b"tensor-bytes-v1",
"tokenizer.json": b'{"version":"1.0"}\n',
}
manifest = {
path: {"size": len(blob), "sha256": sha256(blob).hexdigest()}
for path, blob in trusted_files.items()
}
def verify(label, files):
expected = set(manifest)
actual = set(files)
unexpected = sorted(actual - expected)
if unexpected:
return f"{label}: rejected (unexpected file: {unexpected[0]})"
missing = sorted(expected - actual)
if missing:
return f"{label}: rejected (missing file: {missing[0]})"
for path in sorted(expected):
blob = files[path]
descriptor = manifest[path]
if len(blob) != descriptor["size"]:
return f"{label}: rejected (size mismatch: {path})"
if sha256(blob).hexdigest() != descriptor["sha256"]:
return f"{label}: rejected (digest mismatch: {path})"
return f"{label}: verified ({len(files)} files)"
tampered = dict(trusted_files)
tampered["tokenizer.json"] = b'{"version":"1.1"}\n'
extra = dict(trusted_files)
extra["modeling_custom.py"] = b"raise RuntimeError('unexpected code')\n"
print(verify("release-a", trusted_files))
print(verify("release-a-tampered", tampered))
print(verify("release-a-extra", extra))
Expected output:
release-a: verified (3 files)
release-a-tampered: rejected (digest mismatch: tokenizer.json)
release-a-extra: rejected (unexpected file: modeling_custom.py)
Every transformation creates a derived artifact
The downloaded bundle is often not what a server loads. A deployment pipeline may convert tensor names, cast dtypes, change sharding, quantize weights, merge an adapter, add a projection sidecar, or compile a graph for one accelerator. Each operation creates a derived artifact. It must receive a new manifest and a new digest rather than inheriting the identity or signature of its parent.
Quantization makes this rule easy to see. GGUF is a container with per-tensor encoding types; it is not itself a quantization algorithm. Two tools can start from the same source digest and produce different bytes because they choose different schemes, calibration data, kernels, rounding behavior, or metadata. Even when two outputs behave similarly, they are not the same artifact.
The lineage record for a conversion, quantization, or adapter merge should include:
- source repository revision and every source digest;
- transformation tool version and source revision plus binary, package, or container digest;
- command, transformation parameters, calibration inputs, runtime versions, and actor;
- output tensor schema, new manifest, and new digest;
- evaluation results compared with the parent and the deployment threshold;
- inherited license obligations and the internal reviewer who approved the derived release.
If the transformation is nondeterministic, record the source of nondeterminism instead of pretending the output can be rebuilt bit for bit. The lineage can still explain which inputs and procedure produced the child. A new evaluation is mandatory because conversion can preserve file validity while changing numerical behavior, tokenizer compatibility, memory use, or kernel support.
Integrity, provenance, inventory, and behavior are different claims
Supply-chain evidence is useful only when its claim stays narrow.
| Claim | Evidence that can support it | What the evidence does not prove |
|---|---|---|
| Integrity | Recomputed cryptographic digest and expected size | Who chose the expected digest, or whether the bytes are good |
| Authenticity under a trust policy | Signature plus a trusted key, certificate, or workload identity | That the signer is competent, authorized for this release, or truthful |
| Provenance | Authenticated attestation bound to output and input digests | Completeness of undeclared inputs, safe behavior, or reproducibility |
| Inventory | ML-BOM, manifest, and model card | That every declared item is accurate, available, licensed, or actually used |
| Behavior | Evaluation of the exact bundle and runtime under stated conditions | Untested inputs, unknown triggers, or future deployment drift |
OpenSSF Model Signing (OMS) signs a detached manifest of file paths and digests. It can use keys, certificate chains, or Sigstore keyless signing. Verification checks the signature under the consumer's trust policy and recomputes the model files' hashes (OpenSSF AI/ML Security Working Group 2025). When Sigstore is used, a verifier can also check transparency-log inclusion and a signer can monitor unexpected use of its identity. Log inclusion makes a signing event discoverable; it does not make credential misuse or a false claim impossible.
An in-toto statement binds a typed predicate to subject artifact digests. SLSA 1.2 build provenance is one such predicate: it can record the builder platform, build type, parameters, resolved dependencies, and run details (SLSA Community 2025). Applying it to model training or conversion requires a training-specific build type that intentionally records code, data, base-model digests, and hyperparameters. An authenticated attestation remains a claim. The verifier must compare the builder identity, source, build type, and parameters with preconfigured expectations. If provenance references hardware evidence, the verifier must separately validate the attestation chain and measurement policy described in Chapter 60.
CycloneDX introduced ML-BOM support in version 1.5. It can encode a producer-declared inventory of models, datasets, dependencies, parameters, and lineage (OWASP CycloneDX 2023). A signature can authenticate and integrity-protect that bill of materials, but it cannot make an omitted or incorrect entry true. A model card serves another purpose: intended uses, evaluated conditions, limitations, and relevant groups (Mitchell et al. 2019). Keep inventory, provenance, and deployment evidence linked rather than treating any one document as a certificate.
Learned behavior is also a supply-chain risk
Valid bytes can still implement unwanted conditional behavior. The strongest evidence is experimental and must retain its scope. Sleeper Agents deliberately trained proof-of-concept models with conditional backdoors, including a year trigger that produced secure code in a stated 2023 context and vulnerable code in 2024. The behaviors persisted under the evaluated supervised fine-tuning, reinforcement learning, and adversarial training interventions (Hubinger et al. 2024). Those interventions were not one universal safety pipeline, and the paper did not estimate how likely such a threat is in a real release.
Training data is another boundary. The archival IEEE Symposium on Security and Privacy version of Poisoning Web-Scale Training Datasets is Practical showed two ways to change content later fetched through mutable URLs. For about sixty dollars, the researchers estimated that they could have controlled the content returned by 0.01 percent of the URLs in LAION-400M or COYO-700M (Carlini et al. 2024). They did not use that experiment to train a poisoned model or demonstrate a resulting behavioral backdoor.
A 2025 preprint tested an experiment-specific denial-of-service backdoor in
models from 600M to 13B parameters trained on 6B to 260B tokens. In that setup,
250 poisoned documents made the <SUDO> trigger produce gibberish with similar
success across the tested scales, while results still varied with training
conditions (Souly et al. 2025). The result suggests that, for this attack
class, adding more clean data alone may not increase the required poison count.
It does not establish a universal constant for other triggers, objectives,
architectures, or training procedures.
Distribution identity can fail before any of those checks. PoisonGPT was an
educational 2023 demonstration that edited one association in GPT-J-6B and
published it under EleuterAI, one letter away from EleutherAI. The report
showed similar performance on its limited ToxiGen comparison while changing the
targeted fact; it reported no victims or downstream propagation
(Huynh and Hardouin 2023). Namespace similarity is therefore a selection risk,
not proof that a model hub propagated a compromised artifact.
These studies justify threat-model-driven evaluation, not a claim that one scanner can certify weights as clean. Test known and plausible triggers, compare the exact release with trusted baselines, inspect data and transformation provenance, and monitor deployed behavior. Behavioral evaluation can reveal a tested failure, but it cannot certify the absence of an unknown trigger.
Quarantine, verify, promote
Downloading directly into a production cache collapses acquisition and approval into one step. Use a promotion pipeline instead:
- Resolve the approved source, exact release revision, required artifact roles, license record, canonical manifest, expected signer, and trust policy.
- Download into a no-execute quarantine. Mount source blobs read-only, deny runtime access, and keep partial downloads under temporary names.
- Enforce the path allowlist. Reject an absolute path, traversal, link escape, unexpected file, missing shard, duplicate tensor mapping, excessive compressed or expanded size, and an unsupported media type.
- Verify every size and digest, then verify signatures, transparency evidence, and provenance against explicit identities and builder expectations. A valid signature from an unapproved signer still fails.
- Select the narrowest loader. Refuse remote code by default. If an unsafe source format or custom conversion is unavoidable, use a disposable sandbox with no secrets or network, read-only input, a fresh output directory, and CPU, memory, disk, and time resource limits.
- Inspect tensor names, shapes, dtypes, shard coverage, tokenizer assignments, and template compatibility before allocation. Then run an offline smoke test using the pinned runtime.
- Run behavioral evaluation on the exact bundle and runtime. For a derived artifact, compare quality, safety, numerical drift, latency, and memory with the approved parent.
- Write the internal manifest, lineage, ML-BOM, evaluation record, reviewer, and expiry triggers. Sign the internal release and promote it atomically to an immutable internal registry.
Promotion is not permanent. Subscribe to security advisories, preserve the source and derived manifests, monitor signer and builder revocation, and define which cache entries and deployments must be withdrawn when evidence changes. An upstream deletion should not erase the approved rollback copy, while a revoked artifact must not remain usable merely because a cache still has it.
The serving plane should receive an internal manifest digest, not a public repository name. Chapter 31 can then start workers without fetching mutable network content or executing repository code during startup. The rollout and rollback records in Chapter 89 must bind model, tokenizer, template, runtime, and adapters to that same promoted digest. If any component changes, the deployment is a new candidate and returns through the promotion gate.
No generally accepted procedure certifies that arbitrary model weights contain no hidden trigger. Static and representation-based detectors can find some backdoor classes under stated assumptions; behavioral tests can expose triggers they exercise. Neither result covers an unknown trigger chosen to avoid the test distribution. The defensible statement is therefore scoped: record which detectors and evaluations ran, their threat model, and what remained untested.
Registry concentration is also best treated as a recoverability question rather than a label. If the primary host disappears or a mutable reference moves, can the organization reconstruct the exact bundle from independent storage, verify it from the canonical manifest, authenticate its signing and provenance records, recover the governing terms, and reproduce its acceptance evidence? A mirror without these records improves availability but not auditability. A signed manifest without retained blobs improves identity but not recovery.
From artifact to service
The artifact boundary turns “download this model” into a reviewable operation. Pin the whole release, verify an exact manifest, isolate unsafe parsing and conversion, give every derived artifact new lineage, keep signatures and provenance within their claims, and evaluate the behavior that byte checks cannot see. A promoted artifact is a deployment input, not a safety certificate.
Further reading
- PyTorch Contributors, “Serialization semantics” (current archive layout and the security boundary of torch.load with weights_only), 2026. docs.pytorch.orgPyTorch documents its ZIP64 checkpoint layout, the weights-only unpickler default introduced in 2.6, and the denial-of-service and possible memory-corruption risks that remain with untrusted artifacts.
- Safetensors Project, “Safetensors format specification” (pinned format specification: header length, JSON tensor metadata, and raw byte buffer), 2026. github.comThe pinned safetensors specification defines an eight-byte header length, JSON tensor descriptors, and a contiguous byte buffer without pickle-style callable reconstruction.
- Dahlgren et al., “Safetensors Library Security Assessment” (time-bounded external review of the safetensors implementation and conversion tooling), 2023. huggingface.coThe audit found no critical code-execution path in the reviewed loader after reported validation, overflow, parser, and conversion issues were addressed, while explicitly not claiming proof of absence.
- ggml Project, “GGUF file format specification” (pinned GGUF v3 specification covering metadata, tensor descriptors, quantized types, sharding, and sidecars), 2026. github.comGGUF is an extensible inference container for GGML-based runtimes, designed for fast loading and commonly self-contained, while the current specification also supports shards and sidecars.
- Hugging Face, “Download files from the Hub” (official snapshot and revision-pinning behavior for model repositories), 2026. huggingface.coThe Hub client can resolve a repository snapshot by branch, tag, pull request, or full commit hash and download either the whole snapshot or a filtered file set.
- Open Container Initiative, “OCI Distribution Specification” (content-type-agnostic manifests, descriptors, digests, tags, blobs, and referrers), 2025. github.comThe OCI distribution protocol separates mutable tags from digest-addressed manifests and blobs, with descriptors binding media type, size, and content digest.
- OpenSSF AI/ML Security Working Group, “OpenSSF Model Signing Specification” (detached signed manifests for model file paths and digests, with key, certificate, and Sigstore modes), 2025. github.comOMS signs a detached manifest of model paths and digests; verification still requires a trust policy binding the signing credential to an approved identity.
- SLSA Community, “SLSA v1.2 Build Provenance” (in-toto predicate for builder, build type, parameters, resolved dependencies, and run details), 2025. slsa.devSLSA build provenance binds authenticated claims about a builder, build definition, resolved inputs, and run details to output artifact digests; consumers must verify those claims against expectations.
- OWASP CycloneDX, “Machine Learning Bill of Materials (ML-BOM)” (producer-declared inventory support introduced in CycloneDX 1.5), 2023. cyclonedx.orgCycloneDX ML-BOM can encode declared models, datasets, dependencies, parameters, model-card fields, and lineage, but it does not validate the truth or completeness of those declarations.
- Mitchell et al., “Model Cards for Model Reporting” (structured reporting of intended use, evaluation conditions, and limitations), 2019. doi.orgModel cards report intended uses, evaluation conditions, limitations, and performance across relevant conditions and groups.
- Hubinger et al., “Sleeper Agents: Training Deceptive LLMs that Persist Through Safety Training” (deliberately implanted proof-of-concept backdoors tested under specified interventions), 2024. arXiv:2401.05566Some deliberately trained conditional backdoors in the tested models persisted through supervised fine-tuning, reinforcement learning, and adversarial training, demonstrating a proof of possibility rather than estimating natural prevalence.
- Carlini et al., “Poisoning Web-Scale Training Datasets is Practical” (mutable-URL and snapshot-front-running attacks on web-scale dataset acquisition), 2024. doi.orgThe paper shows how mutable URLs and dataset snapshots can be manipulated; its roughly sixty-dollar estimate concerns controlling content returned by 0.01 percent of URLs in two image-text datasets, not a demonstrated model backdoor.
- Souly et al., “Poisoning Attacks on LLMs Require a Near-constant Number of Poison Samples” (narrow denial-of-service trigger studied across 600M–13B models and 6B–260B-token datasets), 2025. arXiv:2510.07192Across the tested scales, 250 poisoned documents produced similar success for a narrow trigger-to-gibberish objective; the result is conditional on that attack and training setup.
Comments
Log in to comment