Understanding Pickle files: How they work, risks, and safe inspection

Python provides several ways to save objects to disk, including Pickle, which is probably one of the most convenient ... and also one of the most misunderstood. Some people discover it when they want to store a machine learning model, cache complex data, or transfer objects between processes.
The problem is that Pickle is not a simple data format like JSON. A .pkl file can contain instructions allowing Python to reconstruct complex objects and, in some cases, trigger arbitrary code execution during deserialization.
We will see how Pickle works, why it presents security risks (which I was unaware of the first time I used it), and how to inspect a Pickle file more safely before loading it.
What is Pickle?
The standard pickle module allows you to serialize a Python object, meaning that it transforms it into a sequence of bytes that can be stored in a file or sent over a network.
Simple example:
import pickle
data = {
"name": "Alice",
"scores": [10, 20, 30],
"active": True
}
with open("data.pkl", "wb") as f:
pickle.dump(data, f)
Then, to read the file again:
with open("data.pkl", "rb") as f:
restored = pickle.load(f)
print(restored)
Unlike JSON, Pickle can handle:
- tuples;
- sets (
set); - custom objects;
- some functions and classes;
- recursive structures.
This flexibility explains its popularity in the Python ecosystem.
How does Pickle work?
A Pickle file contains a series of opcodes, meaning internal instructions interpreted by Python to recreate the serialized object. It is therefore not a text format readable by humans.
Let's create a small file:
import pickle
pickle.dump([1, 2, 3], open("numbers.pkl", "wb"))
The content looks like unreadable binary data. However, Python provides the pickletools tool to display the internal instructions:
import pickletools
with open("numbers.pkl", "rb") as f:
pickletools.dis(f)
You will get something like this:
0: \x80 PROTO 4
2: ] EMPTY_LIST
3: ( MARK
4: K BININT1 1
6: K BININT1 2
8: K BININT1 3
10: e APPENDS
11: . STOP
We can then see that Pickle works like a specialized interpreter for object reconstruction commands.
Why Is Pickle dangerous?
The official Python documentation is very clear:
Warning
The
picklemodule is not secure. Only unpickle data you trust.
The problem comes from the fact that Pickle can call arbitrary functions during the deserialization process. An attacker can therefore create a file that executes code when pickle.load() is called.
Here is a deliberately simplified example:
import pickle
import os
class Evil:
def __reduce__(self):
return (os.system, ("echo hacked",))
payload = pickle.dumps(Evil())
When this payload is loaded:
pickle.loads(payload)
the os.system() function is executed. In a real-world context, this could be a much more dangerous command... and that is where things go wrong!
Real-world cases where Pickle appears
You will frequently encounter .pkl or .pickle files in:
- scikit-learn (
model.pkl); - joblib (which relies on Pickle internally);
- some deep learning frameworks;
- cache systems;
- Jupyter notebooks;
- data analysis tools.
Downloading a pre-trained model from the Internet and opening it directly with pickle.load() is therefore equivalent to executing Python code provided by a third party.
Static inspection with pickletools
The first step is to avoid immediately deserializing the file. Instead, use pickletools.dis() to examine its opcodes.
Inspection script:
import pickletools
from pathlib import Path
def inspect_pickle(path):
with open(path, "rb") as f:
pickletools.dis(f)
inspect_pickle("suspicious.pkl")
This allows you to identify suspicious instructions such as:
GLOBAL;REDUCE;STACK_GLOBAL;- references to system modules (
os,subprocess, etc.).
This method is much safer than loading the file with pickle.load(), because pickletools.dis() only analyzes and displays the opcodes without reconstructing the serialized objects.
However, it should not be considered an absolute security guarantee: a malicious file could still be designed to consume a large amount of resources or exploit a potential bug in the Python parser. The purpose of pickletools is mainly to provide static inspection of a Pickle file before any deserialization takes place.
Creating a restricted unpickler
If you absolutely need to read a potentially untrusted file, you can limit the allowed classes.
Example:
import pickle
import builtins
class RestrictedUnpickler(pickle.Unpickler):
ALLOWED = {
"builtins": {
"dict",
"list",
"tuple",
"set",
"str",
"int",
"float",
"bool"
}
}
def find_class(self, module, name):
if module in self.ALLOWED and name in self.ALLOWED[module]:
return getattr(builtins, name)
raise pickle.UnpicklingError(
f"Forbidden class: {module}.{name}"
)
def safe_load(path):
with open(path, "rb") as f:
return RestrictedUnpickler(f).load()
This approach greatly reduces the risk of malicious payloads relying on importing unauthorized classes or functions.
Pickle vs JSON
| Criteria | Pickle | JSON |
|---|---|---|
| Human-readable | ❌ | ✅ |
| Compatible with other languages | ❌ | ✅ |
| Support for complex Python objects | ✅ | ❌ |
| Possible code execution | ⚠️ Yes | ❌ |
| Recommended for external data | ❌ | ✅ |
For data exchanged between applications, JSON is generally the best choice. Pickle is mainly suited for internal Python objects and controlled environments.
Best practices
Do
- Use Pickle only between trusted components.
- Sign or verify the integrity of sensitive files.
- Inspect unknown files with
pickletools. - Prefer JSON, MessagePack, or Protocol Buffers for external exchanges.
You can also use our online Pickle viewer!
Avoid
- Using
pickle.load()on a file downloaded from the Internet. - Storing publicly accessible Pickle files without integrity checks.
- Assuming that a
.pklfile is "just data".
Example of a minimal inspection tool
Here is a small practical utility:
import pickletools
import sys
def analyze(path):
print(f"Analyzing {path}\n")
with open(path, "rb") as f:
data = f.read()
print(f"Size: {len(data)} bytes\n")
pickletools.dis(data)
if __name__ == "__main__":
analyze(sys.argv[1])
Usage:
python inspect_pickle.py model.pkl
This is a good starting point for understanding what a Pickle file actually contains before any deserialization.
Conclusion
Pickle is a very practical tool for serializing complex Python objects, but this power comes with a cost: a Pickle file can execute arbitrary code when loaded. Understanding the difference between data serialization and executable object reconstruction helps prevent an entire category of vulnerabilities that are often underestimated in Python projects.



Laisser un commentaire