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 (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 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.