Home / Tools / PyTorch Model Load Risk Analyzer

PyTorch Model Load Risk Analyzer

Paste a torch.load call and this tool resolves the effective weights_only value against your PyTorch version, rates the file source and format, and prints the corrected load line for your exact inputs. Everything runs in your browser. Nothing is uploaded and no file is opened.

What this tool does not do. It performs static analysis of the text you paste plus the four controls. It never opens, downloads, or unpickles a file, so it cannot tell you whether a specific checkpoint actually contains a malicious REDUCE opcode. Version buckets are coarse on purpose: the only boundaries that change behavior are 1.13 (the release that added weights_only) and 2.6 (the release that flipped its default and fixed CVE-2025-32434). Ratings for the trusted internal source assume your artifact store and CI integrity actually hold. Verify any CVE claim against the upstream advisory before you put it in a report.

Why pickle deserialization is code execution

A .pt, .pth, or .bin checkpoint is a zip archive whose payload is a Python pickle stream. Pickle is not a data format. It is a small stack language, and two of its opcodes exist specifically to rebuild arbitrary Python objects: GLOBAL imports a name from a module, and REDUCE calls a callable with a tuple of arguments.

Any class can control what those opcodes emit by defining __reduce__. The unpickler does not sandbox the result and does not wait for you to touch the object. The callable runs during torch.load itself, before a single tensor reaches your model:

# the entire attack, on the producer side
import os, torch

class Payload:
    def __reduce__(self):
        return (os.system, ("id",))

torch.save({"state_dict": Payload()}, "model.pt")

# on the victim side, this one line runs os.system("id")
torch.load("model.pt")            # weights_only=False before 2.6

Nothing here is a memory corruption bug or a parser flaw. It is pickle working exactly as documented, which is why the Python standard library warns against unpickling untrusted data and why the fix is never a patch to the checkpoint. The fix is to stop running the unrestricted unpickler.

What weights_only actually changes

weights_only=True swaps the standard unpickler for a restricted one that walks the same stream but refuses to resolve GLOBAL to anything outside a small allowlist of tensor and container types. An attacker payload that references os.system, builtins.eval, or any custom class hits an UnpicklingError instead of executing.

The cost is that it also rejects legitimate checkpoints. Anything that pickled an optimizer wrapper, a config dataclass, a numpy scalar, or a project-specific class will fail to load with the same error. The supported escape hatch is torch.serialization.add_safe_globals([...]), which extends the allowlist one class at a time. Adding a class you do not control back to the allowlist gives back exactly as much attack surface as that class can reach, so it is a decision, not a formality.

The version timeline that decides your default

PyTorch versionweights_only parameterDefault when unset
1.12 and olderDoes not existNo restricted mode at all
1.13 through 2.5AvailableFalse, so unrestricted pickle
2.6 and newerAvailableTrue, so restricted unpickler

The 2.6 flip is why an audit finding can be correct on one machine and wrong on the next. The same source line, unchanged, is arbitrary code execution on 2.5 and a hard UnpicklingError on 2.6. Pin the version in the report or the finding is not reproducible.

One more version detail matters for anything at or below 2.5.1: CVE-2025-32434 describes remote code execution through torch.load even when weights_only=True was set, and it is fixed in 2.6.0. That is why this tool refuses to call weights_only=True a clean Low on pre-2.6 versions fed by an untrusted source. The flag was intended as the boundary, and on those versions the boundary itself had a hole.

Why safetensors ends the argument

.safetensors is a header of JSON metadata followed by a flat block of raw tensor bytes. There is no opcode stream, no callable, and no way for the format to name a Python object, so a malicious file has no code execution primitive by design. It is loaded with safetensors.torch.load_file, not with torch.load. This does not mean any safetensors file is safe to trust in the wider sense. The weights themselves can still be backdoored so the model behaves badly on a trigger input, and parsers can still have ordinary memory safety bugs. It means the specific class of bug this page is about, load-time arbitrary code execution, is gone.

GGUF is the same shape of answer for a different ecosystem. It is a binary tensor container read by llama.cpp and gguf-py, not by torch.load, and it has no deserialization-to-callable primitive. Its real risk class is memory safety in the parser, so keeping that runtime patched is the control, not a flag.

Frameworks that call torch.load for you

The reason this keeps landing as a real bug bounty finding is that most consumers never write the vulnerable line. Trainer and inference frameworks, DeepSpeed among them, load checkpoints internally, so the weights_only decision is made inside a dependency you did not author and cannot fix by changing your own call site. When you triage a report or write one, trace the checkpoint path all the way down to the library that actually deserializes, then check the pinned version of that library against the timeline above. A wrapper that hardcodes weights_only=False to keep old checkpoints loading silently reintroduces the pre-2.6 behavior on a patched PyTorch, and that is the finding.