Hugging Face Model Supply Chain Risk: Pickle Backdoors
How Hugging Face model supply chain risk works: pickle backdoors, the Transformers RCE CVE cluster, why the Hub scanner misses them, and what cuts risk.
Hugging Face model supply chain risk comes down to one uncomfortable fact: for a large slice of the models on the Hub, from_pretrained() is pickle.load() with extra steps, and pickle.load() runs arbitrary code by design. Download a PyTorch checkpoint, load it, and any Python the author embedded in the serialized object runs in your process, with your credentials, on your GPU box. The Hub scans for this, but the scanner is best-effort and has been bypassed in the wild. If your pipeline pulls weights by name from a public hub and loads them without a format gate, treat every one of those loads as remote code execution you have chosen to trust.
Why pickle is the problem
Pickle is Python’s default serialization format and the default for PyTorch model weights. It is not a data format in the way JSON is; it is a small stack machine. During unpickling, opcodes like GLOBAL/STACK_GLOBAL import modules and REDUCE calls a function with attacker-supplied arguments. Hugging Face’s own pickle scanning documentation walks through the exploit primitive: reference __builtin__.exec, hand it a string of Python, and the unpickler runs it. The most common weaponization is the __reduce__ method on a custom class, which lets the author specify exactly what callable and arguments fire at load time.
This is not theoretical. In February 2024, JFrog documented roughly 100 malicious models on the Hub carrying genuine payloads: reverse shells that spawned /bin/sh or PowerShell and dialed out to attacker infrastructure, embedded via __reduce__ in PyTorch files. PyTorch checkpoints made up the bulk of the malicious set, with TensorFlow/Keras a distant second. Academic measurement backs the pattern up. The Models Are Codes study monitored more than 705,000 models over three months and found 91 malicious ones, 76 using pickle variants and 15 abusing Keras Lambda layers, with payloads spanning remote control, browser-credential theft, and system reconnaissance. The absolute count is small against the size of the Hub, but the loading pattern is universal: one poisoned artifact in your dependency graph is enough.
The Transformers CVE cluster: loading something runs code
The malicious-upload problem has a library-side twin. The most dangerous transformers advisories are a cluster fixed in version 4.48.0, all sharing one root cause: deserialization of untrusted data reaching code execution.
- CVE-2024-11392 (CVSS 8.8): deserialization of untrusted data in configuration file handling, from lack of proper validation of user-supplied data, allowing code execution in the context of the current user. Affects versions prior to 4.48.0.
- CVE-2024-11393 (CVSS 8.8): MaskFormer model deserialization RCE, allowing remote attackers to execute arbitrary code on affected installations. Fixed in 4.48.0.
- CVE-2024-11394 (CVSS 8.8): Trax model deserialization RCE, same shape, same fix version.
- CVE-2023-6730 (CVSS 8.8): the earlier entry in the same family, deserialization of untrusted data in
huggingface/transformersprior to 4.36.
That this exact bug class produced a fix in 4.36 and then again across 4.48 is the supply-chain lesson in miniature: the library keeps finding new model and config types whose loading path trusts data it should not. What makes these worse than a generic library bug is the delivery mechanism. The Hub is built around from_pretrained pointing at a repository you did not author. A config or model artifact is exactly the “untrusted data” these CVEs deserialize. The exploit does not need to compromise your network; it needs you to download a model, which is the entire point of the platform.
The pattern is still producing CVEs. CVE-2026-1839 (CVSS 7.8, published April 2026) is a code-execution vulnerability in the Transformers Trainer class: its _load_rng_state() method calls torch.load() without weights_only=True, so a malicious checkpoint file (an rng_state.pth dropped into a resumed training run) can execute code on load. Resolved in v5.0.0rc3. This is the PyTorch torch.load problem wearing a Transformers hat, and proof that the two ecosystems’ deserialization risks compound rather than stay in their lanes.
The other bucket: ReDoS is denial of service, not code execution
A second, less alarming cluster is regular-expression denial of service in tokenizers. These hang a worker on a crafted input; they do not run code.
- CVE-2024-12720 (CVSS 7.5): ReDoS in
tokenization_nougat_fast.py, fixed before 4.48.0. - CVE-2025-1194 (CVSS 6.5): ReDoS in
tokenization_gpt_neox_japanese.py, fixed before 4.50.0. - CVE-2025-2099 (CVSS 7.5): ReDoS in the
preprocess_string()function oftransformers.testing_utils, affecting 4.48.3 and earlier.
Triage differs sharply between the buckets. A ReDoS in a tokenizer you do not load (most teams use a handful of model families, not the Nougat or GPT-NeoX-Japanese tokenizers) is not applicable to your deployment, even at CVSS 7.5. The deserialization CVEs apply the moment you from_pretrained an untrusted repo. The score does not capture that gap; your model inventory does. That distinction is the whole argument in reading an ML CVE beyond the CVSS score.
The scanner is not a quarantine
The Hub runs two scans on every uploaded file: a ClamAV antivirus pass and a pickle import scan built on pickletools.genops, which reads opcodes without executing them and surfaces the list of imports referenced in the file. Suspicious imports get highlighted on the model page. That is genuinely useful for triage, and the docs are refreshingly blunt that it is “not 100% foolproof.”
The gap defenders keep missing: a flag is a label, not a gate. When the scanner marks a file unsafe, Hugging Face does not block or quarantine the download. The file stays live and you can pull it anyway. The scan is advisory. Nothing in the default from_pretrained() path stops you from loading a model the Hub has already flagged.
Worse, the scanner itself is attackable, and it has its own CVE history.
- CVE-2025-10155 is a critical-severity bypass in
picklescanup to and including 0.0.30. The scanning logic keyed on file extension: hand it a plain pickle with a PyTorch extension like.bin, the PyTorch-specific parser fails, and the scanner returned an error instead of falling back to standard pickle analysis. A malicious payload in a.binsailed through undetected. Fixed in 0.0.31. - CVE-2025-1944 (CVSS 6.5, CWE-345) describes a ZIP archive manipulation attack against
picklescanbefore 0.0.23: by altering the filename in the ZIP header while keeping the original in the directory listing, an attacker makes the scanner raise aBadZipFileerror and crash, yet PyTorch’s more forgiving ZIP implementation still loads the model. The payload bypasses detection entirely.
Both bugs fail open, which is exactly the class of defect that recurs. Track these disclosures the way you track any dependency CVE; incident trackers like ai-alert.org log model-hub and scanner vulnerabilities as they land.
The false-positive rate cuts the other way too. Public analysis of Hub models flagged unsafe by pickle scanning found the large majority were benign, with imports that look dangerous but are not. When most alerts are noise, humans stop reading them, and the one real backdoor gets waved through with the rest.
What actually reduces exposure
You do not need to solve serialization security to close most of this risk. You need to stop loading arbitrary pickle.
- Prefer
safetensorsand refuse pickle where you can. The safetensors format stores tensor data and metadata only: no opcodes, noREDUCE, no code path to execution. Setuse_safetensors=Trueonfrom_pretrained()so the load fails loudly instead of silently falling back to a.bin. For models that ship both formats, this is a one-line control that removes the entire attack class. - Upgrade past the fix lines. Transformers 4.48.0 clears the 11392/11393/11394/12720 cluster, 4.50.0+ clears CVE-2025-1194, and the
Trainercheckpoint fix (CVE-2026-1839) lands in the v5 line. The 4.36 line is far too old. - Pin, do not float. Pull models by immutable commit hash (the
revisionargument), not by branch or bare name. A name resolves to whatever the currentmainpoints at, which the author, or someone who compromised the author’s token, can change under you. A pinned SHA cannot be swapped. Record the hash you scanned and reject mismatches at load time. - Scan before load, and keep the scanner patched. Run
picklescanor Trail of Bits’ficklingagainst any pickle-format artifact in CI, and keep the scanner itself current. A scanner on 0.0.30 is a scanner with a known bypass. Treat a flagged import as a block in your pipeline, not a label a data scientist can ignore. - Load untrusted models in a sandbox. If you must load a pickle-format model you do not fully trust, do it in a network-isolated container with no credentials mounted and egress denied by default. A reverse shell that cannot reach its C2 and cannot read a token is a much smaller problem. The offensive side of that threat model is covered at aisec.blog.
- Inventory which tokenizers and model families you actually load. It turns the ReDoS CVEs from a recurring fire drill into a quick “not applicable” most of the time.
- Generate an SBOM for models, not just code. Your dependency inventory should include the exact model artifacts, versions, and hashes you deploy, so that when the next scanner CVE or poisoned-model disclosure lands, you can answer “are we affected” in minutes instead of days.
- Map these to OWASP LLM Supply Chain. Tracking them under a single OWASP LLM supply-chain label keeps the recurring pattern visible instead of looking like unrelated one-offs.
The uncomfortable framing for platform teams: any place your infrastructure calls torch.load on a file it fetched from the internet is a supply-chain code-execution sink, and the vendor’s scanner is a smoke detector, not a firewall. The Hub made model sharing frictionless, and the friction it removed was exactly the friction that used to make running a stranger’s code hard. Format enforcement, pinning, and sandboxing are cheap. A backdoor with your cloud credentials is not.
See also
- PyTorch security: notable CVEs and how to harden your loading path
- Unsafe model deserialization: the pickle problem behind ML CVEs
- Reading an ML library CVE beyond the CVSS score
- Model file format security: pickle, safetensors, GGUF
- Best AI supply chain security tools — the scanners and AIBOM tooling that enforce the controls above
Sources
- Pickle Scanning — Hugging Face Hub documentation
- Data Scientists Targeted by Malicious Hugging Face ML Models with Silent Backdoor (JFrog)
- CVE-2025-10155 — picklescan bypass via file extension mismatch (NVD)
- Models Are Codes: Towards Measuring Malicious Code Poisoning Attacks on Pre-trained Model Hubs (arXiv)
- CVE-2024-11392: Transformers config deserialization RCE — NVD
- CVE-2024-11393: Transformers MaskFormer deserialization RCE — NVD
- CVE-2023-6730: Transformers deserialization of untrusted data — NVD
- CVE-2024-12720: Transformers ReDoS (Nougat tokenizer) — NVD
- CVE-2025-1944: picklescan ZIP archive manipulation bypass — NVD
ML CVEs — in your inbox
CVEs in ML libraries, frameworks, and the AI/ML supply chain. Sent only when there is something worth sending.
No spam. Unsubscribe anytime.
Related
Model File Format Security: Pickle, Safetensors, GGUF
Which model formats can execute code when loaded, which only crash, and which are inert. A format-by-format comparison built from the verified CVE record.
Malicious Model File Detection: Auditing ML Models
Pickle serialization flaws, PickleScan bypass CVEs, and a practical detection stack for teams pulling models from public repositories like Hugging Face.
TensorFlow Security Vulnerabilities 2026: CVEs and Supply Chain
A breakdown of the top TensorFlow security vulnerabilities in 2026: CVE-2025-49655, CVE-2025-12058, DoS flaws in 2.18.0, and supply chain risk.