Join our Newsletter — 33% off our NHI Course
Home› Guides› Model Supply Chain Guide: Malicious Models, Pickle Files…
Guide Software & AI Supply Chain

Model Supply Chain Guide: Malicious Models, Pickle Files and Safetensors

PDF for newsletter subscribers · free to read online

← All guides
By Lalit Choda, NHI Mgmt Group Updated 24 October 2026 13 min read
On this page

Loading a model file is often the same as running code, and most teams still download model weights from public hubs with less scrutiny than they give an npm package. The default PyTorch format for years was pickle, which can import and call arbitrary Python during deserialisation, and researchers have found malicious models on public hubs that open a remote shell the moment they are loaded. This guide covers the model artefact itself: which file formats can execute code, how safe loading options in PyTorch, Keras and Transformers work and where they stop, what hub scanning catches, and how to build an intake pipeline with pinning, signing and an internal registry. It is for ML platform, application security and supply chain teams. Inventory and AI-BOM practice is covered in the AI Supply Chain and AI-BOM Guide, and behavioural backdoors in weights in the AI Data Poisoning and Backdoors Guide.

Key takeaways

  • Pickle is code, not data. Python's own documentation says the pickle module is not secure and that malicious pickle data can execute arbitrary code during unpickling.
  • Safetensors removes the execution path for weights. It stores tensors only, so prefer it for every model you download or publish, and convert legacy checkpoints in an isolated job.
  • Safe loading flags help but are not a sandbox. PyTorch 2.6 and later default to weights_only=True, and Keras defaults to safe_mode=True, yet both have documented limits and both can be switched off by a single argument.
  • Hub scanning is advisory. Hugging Face scans every commit and highlights suspicious pickle imports, but states its scan is not foolproof, and flagged files can still be downloaded.
  • Provenance needs pinning and signatures. Pin every production model to a commit hash and a file digest, mirror it into an internal registry, and verify signatures where the publisher provides them.

What sits in a model supply chain

A model you deploy is a bundle of artefacts from several parties, each of which can be tampered with. OWASP's LLM03:2025 Supply Chain entry lists vulnerable pre-trained models, weak model provenance (model cards give no guarantee of origin), malicious LoRA adapters, collaborative model merge and conversion services, and traditional package vulnerabilities as distinct risks.

ArtefactTypical filesHow it can hurt youPrimary control
Weightspytorch_model.bin, .pt, .pth, .ckpt, .safetensors, .keras, .h5Code execution on load (pickle, Keras Lambda); tampered parametersCode-free formats, safe loading, digest pinning
Modelling codePython files loaded with trust_remote_code=TrueArbitrary code by design, changes with every commitReview and pin, or disallow
Adapters and mergesLoRA and PEFT adapter weights, merged checkpointsMalicious adapter alters a trusted base model (OWASP LLM03)Same intake as base weights
Tokenisers and configstokenizer.json, config.json, generation configsAltered behaviour; usually data, but loaders varyDigest pinning
LibrariesPyTorch, Transformers, serving frameworks, convertersClassic dependency compromiseSoftware supply chain controls
Publisher accountHub user or organisation, its tokensAccount takeover replaces a trusted modelPinning; publisher token hygiene

Classic dependency risk is covered in the Software Supply Chain Security Guide; the rest of this page deals with the model-specific rows.

Why pickle-based model files execute code

Pickle is a stream of opcodes for a small virtual machine, not a passive data layout. Hugging Face's pickle documentation identifies the opcodes that matter: GLOBAL and STACK_GLOBAL import an object by module and name, and REDUCE calls it with arguments. An attacker who controls the file can therefore import a built-in that runs code and call it while the rest of the file still looks like ordinary weights. The same mechanism underlies insecure deserialisation in any language; see the Insecure Deserialisation Guide.

  • It was the default: PyTorch checkpoints saved with torch.save (pytorch_model.bin, .pt) are pickle inside a zip, so a large share of older Hub models are pickle.
  • It has been abused on public hubs: JFrog reported in February 2024 that it had found around 100 malicious models on Hugging Face, mostly PyTorch and some Keras, including one whose payload opened a reverse shell to a remote host when loaded with torch.load().
  • Allowlisting the unpickler is hard: Python documents overriding Unpickler.find_class() to forbid or restrict globals, but warns you must be careful what you allow. Hugging Face notes a custom unpickler alone is not a sufficient fix, citing a CTF bypass.
  • Other formats execute code too: Protect AI's Hub scanner looks for Keras Lambda layer exploits as well as pickle, and Keras documents that loading with safe_mode=False can trigger arbitrary code execution.

Model file formats compared

FormatCan execute code on load?NotesPolicy
Pickle checkpoints (.bin, .pt, .pth, .ckpt, .pkl)Yes, unless loaded with a restricted unpicklerDefault PyTorch format before safetensors; weights_only=True restricts itBlock from untrusted sources; convert in isolation
Safetensors (.safetensors)No code path: tensors onlyZero-copy and lazy access with safe_open and get_slice; supported by Transformers, Diffusers, llama.cpp and othersPreferred default
Keras v3 (.keras)Only if safe_mode=False or custom objects are registeredZip of config, weights and optimiser state; safe_mode blocks unsafe lambda deserialisationAllow with safe_mode=True; review custom objects
Legacy Keras HDF5 (.h5)safe_mode applies only to Keras v3 formatKeras documents the flag for .keras onlyTreat as untrusted code; convert
Modelling code via trust_remote_codeYes, by designTransformers requires the flag to load custom architectures from a repositoryDisallow by default; review and pin by exception

Hugging Face's pickle guidance also suggests loading TensorFlow or Flax checkpoints into PyTorch with from_tf=True or from_flax=True where a repository offers them, and lists MsgPack, Protobuf, Cap'n Proto and Avro as alternative serialisation formats.

Safe loading controls and their limits

PyTorch

  • Default since 2.6: torch.load uses weights_only=True when no pickle_module is passed. The restricted unpickler only builds plain tensors and some primitive types needed for state_dicts and cannot import anything dynamically.
  • Documented limits: PyTorch states it does not protect against denial of service, that memory corruption is mitigated but may still be possible, and that loaded objects can still cause problems in downstream code.
  • Allowlisting: torch.serialization.add_safe_globals() adds globals process-wide, while the safe_globals() context manager limits the allowlist to one block; prefer the context manager. get_unsafe_globals_in_checkpoint() lists non-allowlisted globals by static analysis, and PyTorch notes it may miss types built dynamically.
  • Environment overrides: TORCH_FORCE_WEIGHTS_ONLY_LOAD=1 forces every call to weights_only=True; set it in serving images. TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD=1 does the opposite for calls that do not pass the argument; alert if it appears in any image or job definition.
  • The escape hatch: any code calling torch.load(..., weights_only=False) restores full pickle behaviour, and PyTorch's own error message warns this can lead to arbitrary code execution. Make it a code review and static analysis finding.

Keras, Transformers and plain Python

  • Keras: keras.saving.load_model(..., safe_mode=True) is the default and disallows unsafe lambda deserialisation for .keras files. Search code for safe_mode=False.
  • Transformers: trust_remote_code=True loads Python from the model repository. Treat each use as a third-party code dependency: review the files, pin the repository to a commit, and record the exception.
  • Plain pickle and joblib: scikit-learn and other classic ML models are often pickled. Python's advice is never to unpickle data that could have come from an untrusted source or been tampered with, and to consider hmac signing where you control both ends.

Choosing between controls

  • You control the publisher: publish safetensors only and sign it; consumers then need no unpickling at all.
  • Third-party model only in pickle: convert once in an isolated, network-less job, scan the result, publish the safetensors copy internally, and never load the original in production.
  • Old checkpoints that store a whole nn.Module: PyTorch recommends weights_only=False for these, so they should only ever come from your own trusted training runs. Re-save them as a state_dict to retire the exception.
  • Small teams: the cheapest effective control is a rule that production loads only safetensors from an internal mirror. Large organisations add an intake service with scanning, signing and an approval workflow.

Scanning and its limits

Scanning is a useful tripwire and a poor guarantee. Static scanners read the pickle opcode stream without executing it and flag imports that look dangerous; they can be evaded by imports not on the list, and they say nothing about behavioural backdoors in weights.

LayerWhat it doesWhat it misses
Hub malware scanScans files under 2 GB at each commit; ok or infected badge; repository warningAntivirus does not detect pickle exploits, per Hugging Face
Hub pickle import scanLists imports using pickletools.genops without executing; highlights suspicious onesBest-effort safe and unsafe lists; Hugging Face says it is not 100% foolproof
Third-party Hub scannersProtect AI Guardian and others add per-file reports on public repositoriesPrivate repositories and files outside the Hub; download is not blocked
Your intake scannerStatic pickle analysis, format allowlist, digest check, before mirroringNovel import chains; weight-level backdoors
Isolated load testLoad in a sandbox with no network and no credentials, watch for process and network activityTriggers that depend on environment or time
Behavioural evaluation and red teamingOWASP LLM03 recommends AI red teaming of third-party models for your use caseNarrow triggers designed to pass benchmarks

Provenance, signing and pinning

  • Pin to a commit, not a name: loading org/model without a revision follows the repository's default branch, so a compromised publisher token changes what you load. Pin a commit hash in code and in the AI-BOM.
  • Record file digests: store SHA-256 digests of each approved file and verify them at load time; D3FEND's File Hashing technique describes the control.
  • Verify signatures where available: the OpenSSF-associated model_signing project (sigstore/model-transparency) signs a model directory with Sigstore, keys, certificates or PKCS #11 hardware, produces a Sigstore bundle holding an in-toto statement of file digests, and on verify recomputes the hashes. With Sigstore, signing events land in a transparency log publishers can monitor.
  • Signed commits are not signed models: Hugging Face notes that GPG-signed commits guarantee origin, not safety.
  • Protect your own publishing identity: publish from CI with short-lived Trusted Publisher tokens rather than a stored write token, so a leaked CI secret cannot replace your models. See the Hugging Face Security Guide and the AI Model Provenance and Watermarking Guide.

Building a model intake pipeline

  1. Inventory what you load today: grep code and notebooks for from_pretrained, torch.load, pickle.load, joblib.load, load_model and trust_remote_code; record source, revision and format in the AI-BOM.
  2. Set the format policy: safetensors and Keras v3 with safe_mode=True allowed; pickle formats and remote code only by documented exception.
  3. Stand up an internal mirror: a model registry or artefact store that production can reach, with production egress to public hubs blocked.
  4. Intake job: download into an isolated runner with no credentials and no network after download, check format, run static scans, convert pickle to safetensors, compute digests, sign, and publish to the mirror.
  5. Audit mode first: for two to four weeks log every load that would be blocked (wrong format, unpinned revision, unsigned file) without blocking, and fix the top offenders.
  6. Enforce in rings: set TORCH_FORCE_WEIGHTS_ONLY_LOAD=1 and mirror-only loading in staging, then one production service, then all. Rollback is reverting the image variable or the egress rule for the affected service, with an exception ticket.
  7. Continuous checks: re-scan mirrored models when scanner rules update, and review exceptions quarterly.

Detection and measurement

  • Load-time logging: log model identifier, revision, file digest and loader arguments for every model load in serving and training jobs.
  • Runtime signals: alert on child processes (shells, interpreters) or new outbound connections from model-serving and notebook processes during load, which maps to ATT&CK T1059.006 Python and ATLAS AML.T0011.000 Unsafe AI Artifacts.
  • Code and config signals: CI rules that fail on weights_only=False, safe_mode=False, unreviewed trust_remote_code=True and TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD.

Metrics:

  • Percentage of production model loads from the internal mirror.
  • Percentage of production models in safetensors or another code-free format.
  • Percentage of model references pinned to a commit hash with a recorded digest.
  • Number of open trust_remote_code and pickle exceptions, and their age.
  • Median time from a scanner alert on a mirrored model to quarantine.

Framework mapping

Threat or controlMITRE ATLAS / ATT&CKNIST CSF 2.0NIST SP 800-53 Rev. 5
Malicious model on a public hubAML.T0058 Publish Poisoned Models; AML.T0010.003 AI Supply Chain Compromise: ModelID.RA-09SR-4 Provenance, SR-11 Component Authenticity
Code execution when loadingAML.T0011.000 Unsafe AI Artifacts; T1059.006 PythonPR.PS-05SI-3, CM-7
Compromised AI librariesAML.T0010.001 AI Software; T1195.002 Compromise Software Supply ChainGV.SC-07SR-3, SR-6, RA-5
Tampered weightsAML.T0018 Manipulate AI ModelID.RA-09SI-7
Integrity verificationAML.M0014 Verify AI Artifacts; AML.M0013 Code Signing; D3FEND D3-FH File HashingID.RA-09SI-7, SR-4
Model inventoryAML.M0016 Vulnerability ScanningID.AM-02, ID.AM-08CM-8

Common failure patterns

  • Trusting the hub badge: a model without a warning is treated as safe, although Hugging Face says its scan is not foolproof and JFrog showed flagged models could still be downloaded.
  • Silencing the error: a developer hits a weights_only unpickling error after a PyTorch upgrade and adds weights_only=False, reopening the code execution path for every checkpoint that code loads.
  • Remote code left on: trust_remote_code=True copied from a model card into production, unpinned, so the next commit to that repository runs in your serving environment.
  • Loading by name in production: no revision pin and no digest, so a publisher account takeover becomes your incident.
  • Notebooks as the soft spot: data scientists load experimental models on laptops or notebook servers that hold cloud credentials and Hub write tokens. Isolate experimentation from credentials. See the AI Coding Agents Security Guide for the same pattern with agents.
  • Converting in place: running the pickle to safetensors conversion on a build host with secrets, which executes the very payload you were trying to remove.

Practitioner checklist

  • Inventory every model load and record source, revision, format and digest in the AI-BOM.
  • Make safetensors the default format for models you publish and consume.
  • Set TORCH_FORCE_WEIGHTS_ONLY_LOAD=1 in serving images and fail CI on weights_only=False and safe_mode=False.
  • Disallow trust_remote_code except reviewed, pinned exceptions.
  • Serve production only from an internal model mirror and block egress to public hubs.
  • Run intake and conversion in an isolated runner with no credentials and no network.
  • Verify digests at load and signatures where publishers sign with model_signing.
  • Monitor model-serving processes for child processes and unexpected outbound connections.
  • Publish your own models from CI with short-lived tokens, never a stored write token.

Standards and references

Related NHI Mgmt Group resources: AI Supply Chain and AI-BOM Guide · Insecure Deserialisation Guide · Open-Weight Model Security Guide · Hugging Face Security Guide · SLSA and Build Provenance Guide

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Written and reviewed by Lalit Choda, NHI Mgmt Group. Last updated 24 October 2026.
NHI Mgmt Group — the #1 independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.