Jitsumi

Python Pickle Files: Why Loading an Unknown Model Can Run Code

An unknown Python .pkl model can execute code when loaded. Follow a tested harmless example, learn safe inspection, and compare skops, Safetensors, JSON and PyTorch weights_only.

Original editorial illustration of a neural-network model capsule being inspected beside a protective shield, representing the risks of untrusted pickle files.

A Python .pkl file can do more than restore data. Loading a pickle file you do not trust can execute code on your computer, even when the download is described as a pretrained machine-learning model. The practical rule is to treat pickle-based artifacts as executable material: verify their origin and choose a constrained format when you can.

This guide is for Python developers, students and anyone downloading model checkpoints. It covers standard-library pickle, scikit-learn persistence, and PyTorch weights. The key distinction is between inspecting a downloaded file and actually deserialising it. Claims and primary documentation were reviewed on 4 November 2026.

Why a model file can behave like a program

The Python pickle reference warns that malicious pickle data can execute arbitrary code during unpickling. A pickle stream contains instructions for reconstructing Python objects, including references to functions and classes. It is more powerful than a passive list of numbers. The risk is designed into how object reconstruction works, not simply the result of a malformed file.

A filename such as classifier.pkl or pipeline.joblib tells you little about what will happen when another program reads it. Libraries may call pickle indirectly; scikit-learn's official persistence guidance specifically warns that joblib and cloudpickle have the same untrusted-loading risk. Renaming a file or switching between these pickle-based loaders does not establish a trust boundary.

The specific danger occurs when the application unpickles bytes, not merely when Windows or Linux displays a filename. Downloading a suspicious file is still a reason for caution, but it is different from executing its reconstruction operations. Likewise, a .pt or .pth extension does not by itself guarantee the absence of pickle-based processing.

Prove the behaviour with a harmless local example

The following demonstration uses only Python's standard library. It constructs the bytes itself and makes print() the only action performed while loading. Save it as pickle_demo.py and review the entire script before running python pickle_demo.py. Never replace the locally generated payload with an unknown downloaded file.

import pickle
import pickletools

class Demonstration:
    def __reduce__(self):
        return (print, ("DEMO: code ran during unpickling",))

payload = pickle.dumps(Demonstration(), protocol=4)

print("Inspecting the bytes, without executing:")
pickletools.dis(payload)
print("About to unpickle:")
result = pickle.loads(payload)
print("Returned value:", result)

The message DEMO: code ran during unpickling appears after About to unpickle:. The returned object is None, because print() returns None. The interesting part is __reduce__: it tells pickle which callable to invoke and which arguments to supply while reconstructing the object. The callable is harmless here; there is no reason to assume a stranger's file would choose one.

What was actually tested: A substantially identical protocol-4 example was executed locally with Python 3.13.5. The constructed payload was 77 bytes, pickletools.dis showed STACK_GLOBAL and REDUCE, and the benign print statement ran at pickle.loads. That proves this mechanism on that interpreter; it is not a benchmark or a security audit of scikit-learn or PyTorch.

Inspect without loading, but know the limits

Python documents the `pickletools` command-line interface as a safer way to disassemble unknown pickle files because it does not execute their bytecode. If there is a legitimate reason to investigate, use a copy, maintain file-size limits and prefer a dedicated analysis environment.

# Passive disassembly; does not reconstruct the object:
python -m pickletools downloaded-model.pkl

The output is a sequence of opcodes, not the restored estimator. You might spot referenced classes or function names, but an uneventful disassembly is not a certificate of safety. A static listing cannot establish all effects of reconstructing complex objects. In particular, do not use python -m pickle downloaded-model.pkl for inspection: that Python module command actually unpickles the argument.

A virtual environment separates installed Python packages; it does not stop the process from accessing your home directory, network, tokens or personal files. For unknown artifacts, the right response is often to avoid loading them altogether and request a different export.

Four questions before trusting a model

Who supplied it? Identify the publisher, exact repository release and file revision through an authenticated channel. A random mirror or forwarded attachment offers much weaker provenance than an official maintainer release.

Do the bytes match? Compare a trusted release hash or authenticated signature when available. A digest matching a value supplied by the same potentially malicious sender does not establish safety. Jitsumi's published SHA-256 download verification guide walks through the integrity check and clarifies that you still must trust the publisher.

What loader will run? Trace the real function called by the notebook or application. pickle.load(), joblib.load(), torch.load() and custom wrappers have different parameters and defaults. Check the installed version, not a screenshot from an older tutorial.

Do you need a Python object at all? If the job is to exchange plain values or serve inference, you may be able to use JSON, ONNX or a tensor-specific format rather than reconstructing a full Python object graph. Choosing a simpler representation can avoid a whole class of deserialisation hazards.

Safer format choices, and what they cannot promise

Format or loader

Appropriate use

Important trade-off

JSON

Interchange simple structured data

Decoding itself does not execute pickle-style object instructions, but huge or invalid inputs still need limits and validation.

pickle / joblib / cloudpickle

Trusted Python objects in a controlled environment

Arbitrary code execution is possible during untrusted loading; dependencies and versions matter.

skops.io

Supported scikit-learn estimators with explicit type review

Restricts deserialisation but still needs informed trust decisions and compatible packages.

ONNX

Portable inference for supported models

Not every estimator can be converted; validate and isolate execution.

Safetensors

Store supported tensor weights without pickle objects

Weights only; repository code, dependencies and downstream inference remain separate risks.

PyTorch weights_only=True

Read compatible tensor state dictionaries

Restricted object reconstruction, not universal protection against denial of service or memory issues.

Python's comparison of pickle with JSON notes that decoding untrusted JSON does not in itself create an arbitrary code-execution vulnerability. But its JSON parser documentation still warns that hostile input sizes can consume excessive CPU and memory. Data-only formats still need schemas, limits and error handling.

scikit-learn's model-persistence decision guide suggests skops.io if you need a Python model object but want to inspect unknown types before trusting them. ONNX can serve many compatible models without rebuilding the original estimator, but conversion is not universal and may change the operational environment. scikit-learn also warns that loading saved estimators across different versions is unsupported.

PyTorch: weights_only is a useful restriction, not a free pass

According to the PyTorch 2.15 serialization documentation, torch.save() and torch.load() use pickle for Python object serialisation. Since PyTorch 2.6, torch.load() defaults to weights_only=True when no pickle_module is supplied. That mode uses a restricted unpickler for plain tensor state dictionaries and some primitive types, without arbitrary dynamic imports.

# Use only with a checkpoint whose source you have checked.
import torch

state_dict = torch.load(
    "verified-state-dict.pt",
    map_location="cpu",
    weights_only=True,
)
# Apply the state dictionary to an architecture from trusted code.

This is a documented pattern, not a suggestion to load a questionable model merely for experimentation. PyTorch cautions that weights_only=True reduces the remote-code-execution attack surface but does not prevent every denial-of-service or memory-related problem. Some old checkpoints containing custom classes will fail under these restrictions.

An error message may suggest that setting weights_only=False would permit a legacy full-model object to load. Do not disable the restriction just to make an error disappear. That may execute arbitrary code. PyTorch supports explicit allowlisting of globals, but only use it for classes and functions you have independently reviewed and trust.

The Hugging Face Safetensors guide describes a tensor file format designed to avoid arbitrary pickle object reconstruction. It is a useful choice for supported tensor weights, but it does not validate the Python model architecture or any installation scripts shipped beside those weights.

A careful workflow for a downloaded model

1. Read the notebook before running it. Find imports, installation commands, remote code toggles and model-loading calls. Avoid blindly using Run All on notebooks from unfamiliar repositories.

2. Confirm the origin and dependencies. Use the maintainer's official project or registry, record the exact artifact version and verify any independently published digest. Ask for the original export code if provenance is unclear.

3. Identify the actual serialisation method. A suffix is not an API contract. Trace whether the loader uses pickle, joblib, restricted PyTorch weights, Safetensors or a custom function. Review the arguments and library version.

4. Choose a constrained alternative when practical. Simple values can move to JSON with schema checks. Supported scikit-learn models may be exported using skops.io or ONNX; supported neural-network weights may use Safetensors or an eligible restricted state dictionary. Keep the originating code trusted.

5. Separate passive analysis from execution. pickletools provides insight without loading; it is not a definitive scanner. If deeper reverse engineering is necessary, use properly isolated systems without production secrets and with strict resource limits.

6. Validate compatibility. After any format change, check schema, tensor shapes, prediction outputs and pinned dependencies on representative inputs. This guide did not run model-conversion performance or accuracy benchmarks.

What if you already loaded an unfamiliar .pkl?

An untrusted artifact that loaded without errors may still have executed code. Stop using the environment, preserve relevant logs when appropriate, evaluate which files and credentials the process could access, and follow an incident-response procedure. For high-risk exposure, rotate accessible credentials and restore a trusted environment as indicated by the investigation. Removing the original .pkl file does not undo actions that may already have occurred.

If the file was merely downloaded and never passed into an unpickling operation, the pickle-specific execution mechanism described here has not occurred. However, other installers or scripts from the same repository may have been run; review those separately.

Is every pickle file malicious?

No. It is a normal Python serialisation feature used safely in trusted workflows. The problem is allowing untrusted bytes to influence Python object reconstruction. A familiar file extension alone does not prove that a particular file is trustworthy.

Can antivirus or pickletools prove a model is safe?

No single scan or disassembly proves that arbitrary object reconstruction will be harmless. Detection tools can miss unfamiliar payloads and static analysis has limits. Verified provenance and formats with smaller execution capabilities are the stronger default.

The rule to keep: Treat a pickle-based model like executable material. Verify who created it, choose constrained data formats where possible, and unpickle only artifacts you genuinely trust.

Subscribe

Get new articles by email. Unsubscribe with one click.

Share this post

Newsletter

New posts from Jitsumi, by email

One email when a new post goes up. Nothing else, and you can leave with one click.