# The PyTorch checkpoint container is itself a security signal: CVE-2025-32434 and the legacy load path

> Source: <https://dev.to/munimv2/the-pytorch-checkpoint-container-is-itself-a-security-signal-cve-2025-32434-and-the-legacy-load-56d3>
> Published: 2026-10-29 14:47:03+00:00

Most pickle scanners answer one question about a model file: *does the pickle inside it import something dangerous?* That is the right question, but it skips a quieter one that turned out to matter a great deal in 2025: **which container is the pickle sitting in, and what does `torch.load` do with that container?**

`torch.load` has two deserialisation paths:

`torch.save` writes a ZIP archive whose `archive/data.pkl` member is the object graph. The restricted unpickler that `weights_only=True` installs is wired into this path.`pytorch_model.bin` often takes — is handled by `_legacy_load`.
For years the community advice was: *to load an untrusted checkpoint, pass `weights_only=True`.* That was supposed to swap in a restricted unpickler that only rebuilds tensors and refuses arbitrary callables.

[CVE-2025-32434](https://github.com/advisories/ghsa-53q9-r3pm-6pq6) (published 2025-04-17, CWE-502, CVSS 9.3/9.8, torch ≤ 2.5.1, fixed in 2.6.0) is the hole in that advice. On affected versions, `_legacy_load` did **not** apply the restricted unpickler. So `torch.load(path, weights_only=True)` — the exact call people reached for to be safe — still executed whatever callable a **legacy-format** file named. The modern zip path was fine; the legacy path was not. PyTorch 2.6.0 fixed the path and flipped the default to `weights_only=True`.

The practical takeaway: on vulnerable torch, the *container format* decided whether "safe mode" was actually safe. A dangerous import in a legacy file was reachable under `weights_only=True`; the same import in a modern-zip file needed `weights_only=False`. The container is a signal, not just packaging.

I added `pytorch_checkpoint_scan.py` to the open-source [keras-model-scan](https://github.com/munimtechnologies/keras-model-scan) toolkit. It is static and dependency-free: it never imports `torch`, never extracts an archive member to disk, and never unpickles or executes anything. It reads the bytes, recognises the container (zip vs tar vs bare pickle) with `zipfile`/` tarfile`, pulls the embedded pickle(s) out, and disassembles their opcodes with the standard library's `pickletools`. Then it reports with the container in mind:

`torch.load(weights_only=True)` on torch ≤ 2.5.1.`torch.load(weights_only=False)`.

```
python pytorch_checkpoint_scan.py pytorch_model.bin model.pt
```

Exit `1` on any high finding, `0` otherwise (PTC010 prints but does not fail the build).

This is static source review, not exploitation. It flags imports and container shape; it does not prove a given file is exploitable and it runs no proof-of-concept. It reports the first dangerous import path per member, and nuanced top-level-package gadget classification (the `torch.*` gadget paths some denylists catch) lives in a separate module in the same repo. The point here is narrow and, I think, underserved: **treat the checkpoint container as part of the threat model, because `torch.load` did.**

*This post and the tool it describes were made with AI agents working in the name of MV2 of Munim, Inc. The work is open source under Apache-2.0. If you spot a mistake, the repo is the place to raise it.*
