Unsafe Model Deserialization: The Pickle Problem Behind ML CVEs
Loading a model file can execute arbitrary code. This is the single most repeated vulnerability class in the ML supply chain — the real CVEs, why the fixes keep arriving late, and what actually mitigates it.
Almost every serious vulnerability in the model-distribution supply chain reduces to the same root cause: a deserialization routine that executes code while it is reconstructing an object. It is CWE-502, it has been understood for decades, and it keeps producing ML CVEs because the serialization format the ecosystem standardized on was never safe to point at untrusted data — and the entire model-sharing culture is built on pointing it at untrusted data.
Why the default serialization primitive is the wrong one for model files
The Python documentation states the problem plainly: the relevant module is not secure, and you should only deserialize data you trust. Deserialization here is not a parser; it is a small stack-based virtual machine. A REDUCE opcode calls a callable with arguments taken from the byte stream, and a malicious object’s reduction method can return something equivalent to (os.system, ("command",)). Loading such a file runs the command. No memory corruption, no exploit chain — the format does exactly what it was designed to do.
PyTorch’s default .pt/.pth checkpoints, older joblib and scikit-learn artifacts, and many community model dumps use this format under the hood. When a practitioner downloads a checkpoint from a random repository and calls torch.load(path), they are running whatever the producer embedded in it. Trail of Bits demonstrated full weaponization of this in 2021, including payloads that survive re-serialization, and the technique has not aged.
The CVEs keep arriving — even after the “fix”
The instructive recent example is CVE-2025-32434 ↗. PyTorch had long recommended torch.load(..., weights_only=True) as the safe path, and a great deal of guidance treated that flag as the mitigation. CVE-2025-32434 showed that on PyTorch prior to 2.6.0, weights_only=True could still be bypassed to achieve remote code execution. The control everyone was told to rely on did not fully hold; the real fix was an upgrade plus making weights_only=True the default in 2.6.0.
This is the pattern worth internalizing: a deserialization mitigation that is merely a flag on a fundamentally unsafe loader is one parser bug away from failing, and the CVE that proves it tends to arrive years after the advice solidified. The same shape recurs across the ecosystem. Keras .h5/SavedModel files have carried code-execution issues through Lambda layers and deserialized configs. numpy.load with allow_pickle=True is a CWE-502 sink and was the basis of CVE-2019-6446 ↗. Each is independently scored, each gets its own NVD entry, and a defender tracking only “PyTorch CVEs” or only “numpy CVEs” sees fragments of one structural problem.
Reading these CVEs correctly
A deserialization CVE in an ML library should be triaged against one question: does my pipeline ever load a model artifact whose bytes I did not produce and verify? If the answer is yes — fine-tuning community checkpoints, loading user-uploaded models, pulling from a public hub at runtime — the practical severity is closer to unauthenticated RCE on the inference host than whatever generic CVSS vector the entry carries. If every artifact is built in-house from trusted source and stored in an integrity-checked registry, the same CVE may be largely inert for you. The score will not tell you which case you are in; your architecture does.
What actually mitigates it
- Change the format, not the flag. Prefer
safetensorsfor weights. It is a non-executable format: a header plus raw tensor bytes, with no callable invocation during load. This removes the sink rather than guarding it. - Treat model files as untrusted code by default. Scan artifacts before loading. Hugging Face runs scanning on the Hub and surfaces results;
picklescanandfickling(Trail of Bits) can flag dangerous opcodes in CI before a file ever reachestorch.load. - Pin and verify provenance. Pull models by digest, not floating tags. Record the hash that was scanned and reject anything that does not match at load time. Content-addressed registry storage makes “did these bytes change” answerable.
- Sandbox the loader. If you must load legacy artifacts in the unsafe format, do it in a locked-down, network-isolated process with a minimal filesystem view, so a
REDUCEpayload lands somewhere disposable. - Keep loaders current and re-test assumptions. CVE-2025-32434 is the reminder that “we set
weights_only=True” is a claim with an expiry date. Track the loader’s CVEs specifically and upgrade, rather than trusting a flag whose guarantees a future parser bug can revoke.
The deserialization CVE is not going away, because the incentive that created it — frictionless model sharing — is not going away. The defenders who handle it well stop treating each entry as a new surprise and start treating “we deserialize artifacts we did not produce” as a standing exposure that the next CVE merely re-confirms.
Sources
ML CVEs — in your inbox
CVEs in ML libraries, frameworks, and the AI/ML supply chain. — delivered when there's something worth your inbox.
No spam. Unsubscribe anytime.
Related
PyTorch Security: Notable CVEs and How to Harden Your Loading Path
PyTorch's most consequential CVEs cluster around one thing — loading a model file that runs code. A walk through the verified entries, what each actually requires to exploit, and the hardening that holds.
Hugging Face Transformers & Hub: Supply-Chain Risks and Real Advisories
The Hugging Face ecosystem is the npm of machine learning — and it carries the same supply-chain exposure. A tour of verified Transformers CVEs and what they reveal about trusting models, configs, and the tooling meant to protect you.
trust_remote_code and the ML Orchestration CVE Class
A second family of ML supply-chain CVEs has nothing to do with model weights and everything to do with the glue: transformers' trust_remote_code, langchain expression surfaces, and template injection in orchestration libraries.