Model Pickle Scanner

Read a PyTorch checkpoint's pickle without running it

Input artifact: a checkpoint file. Drop a .pt, .bin, .ckpt or .pkl and this page disassembles the pickle opcode stream inside it, in your tab, with nothing uploaded and nothing executed. You get two things: the permanent opcode and import inventory (every GLOBAL and STACK_GLOBAL the file resolves, every REDUCE that calls one), and the answer people actually want since PyTorch 2.6 flipped weights_only to True by default: would torch.load(weights_only=True) accept this file, and which exact global or opcode is the blocker.

No upload No execution No external dependencies Streams by byte range, a 14 GB checkpoint is never read into memory

1. Load a file

Or start with a fixture. Everything below is computed from the bytes you give it.
Drop a checkpoint here
or press Enter / click to choose a file. Nothing leaves the tab.

Load sample

Samples are byte sequences built in this page. The first one is the exact proof-of-concept published in GHSA-9gvj-pp9x-gcfr. All example payload strings are inert placeholders.

Simulate torch.serialization.add_safe_globals

One dotted path per line, for example numpy.core.multiarray._reconstruct. These are added to the allowlist for the verdict below, exactly as add_safe_globals would. The four blocklisted modules cannot be re-allowed this way, and the tool shows that.

Nothing loaded yet
Drop a checkpoint above, or press one of the sample buttons to see the full output on a known payload.

6. Regression fixture: CVE-2025-71325

The incumbent scanner's published off-by-one, reproduced as a permanent test.

GHSA-9gvj-pp9x-gcfr describes a parsing bug in picklescan's _list_globals. Handling STACK_GLOBAL at op index n, the backwards scan for the two string operands looped range(1, n), which reaches op index n-1 down to 1 and never reaches index 0. The advisory's own words:

"The loop only consider the range from 1 to n-1 but forgets to consider the opcode at position 0. The correct range should be 0 to n-1."

In a protocol 0 or 1 pickle there is no PROTO opcode, so index 0 is a real operand. Put one of the two names there and the scan finds only one value, raises ValueError, and the scan errors out instead of reporting. This page reimplements all three behaviours from the published source and runs them on the advisory's exact 26-byte payload, so the hole cannot be inherited here without the test going red.

7. Reference snapshot (dated vendor data)

torch v2.13.0

Everything in this section is copied from PyTorch's source, not computed by this tool, and it is shown in a dashed amber shell for exactly that reason. The allowlist grows every release. If a row here is stale, the tool's logic is still correct and only this table needs refreshing. Read it as "the tables as of the pinned tag", never as "the truth forever".

pinned tag
v2.13.0
tag released
2026-07-08
transcribed
2026-08-09
source file
torch/_weights_only_unpickler.py

7a. Opcodes the weights_only unpickler implements

Read off the elif key[0] == ... chain in Unpickler.load(). Anything not on this list raises UnpicklingError("Unsupported operand {byte}"). This is why a file can be rejected with no dangerous import in it at all.

7b. Allowed globals

dotted pathfamilyhow torch builds it

7c. Modules that can never be allowlisted

From _blocklisted_modules: refused even if you pass them to add_safe_globals. Try it in the box in section 1.

8. How it works, and what it does not decide

Reading the file

Resolving imports

The weights_only decision

What this tool does not decide

These checks in torch's unpickler depend on runtime object types, which a static reader cannot know. If your file only trips one of these, this page will say "no blocker found" and torch will still refuse. That is a real gap and it is stated here rather than buried:

And the wider point: a file that weights_only=True accepts is not thereby a file you should trust with weights_only=False. The torch documentation is blunt about the general case: "torch.load() uses an unpickler under the hood. Never load data from an untrusted source."

Primary sources

9. Self-tests

Assertions over the pure functions, run in your browser.

Every fixture below was disassembled with Python's pickletools first, and the expected offsets are that output. The CVE row's expected offsets come from the advisory text itself.

not run