Safeguard
DevSecOps

Python Pickle Load: A Security Guide

Calling python pickle load on data you do not fully control can execute arbitrary code. Here is why, and what to use instead.

Karan Patel
Platform Engineer
5 min read

Never call python pickle load on data from an untrusted source, because unpickling is equivalent to running arbitrary code on your machine. The Python documentation itself carries this warning at the top of the pickle module page. When you deserialize a pickle stream, Python can be instructed to import modules and call functions, and an attacker who controls the bytes controls what runs.

This is not a bug in pickle. It is how the format works by design. pickle is a serialization protocol for trusted Python objects, and it was never meant to be a safe wire format for data crossing a trust boundary. Understanding that framing is the whole game.

Why pickle.load Is Dangerous

Pickle streams contain opcodes, and some of those opcodes tell the interpreter to construct objects by calling arbitrary callables. Any class can define a __reduce__ method that returns a callable and its arguments, and the unpickler will dutifully invoke it.

Here is the shape of the problem, using a deliberately harmless example:

import pickle

class Demo:
    def __reduce__(self):
        # The unpickler will call this on load.
        return (print, ("this code ran during unpickling",))

payload = pickle.dumps(Demo())

# Later, somewhere else:
pickle.loads(payload)   # prints the message -- code executed

Swap print for something like os.system and you have remote code execution. The victim never called the dangerous function directly. They only called pickle.loads, trusting the bytes. That is the trap: the danger is invisible at the call site.

Where This Bites in Real Applications

The classic footgun is caching. A team stores serialized objects in Redis or Memcached, then loads them back with pickle.load. If an attacker can write to that cache, or if the cache is shared across a trust boundary, they can plant a malicious payload.

Other common sinks include session data stored in cookies, machine learning model files distributed as pickles, message queue payloads, and anything loaded from an uploaded file. ML model sharing is a growing concern because .pkl and some .pt files are pickle-based, and downloading a model from an untrusted registry can execute code the moment you load it.

Safe Alternatives

The fix is almost always to stop using pickle across trust boundaries entirely.

For structured data, use JSON. It only represents data, never callables, so there is no code-execution vector:

import json

data = json.loads(request.body)   # data only, no code

For configuration or data with richer types, formats like MessagePack or Protocol Buffers give you compact binary encoding without the arbitrary-execution behavior. For scientific arrays, numpy.save with allow_pickle=False avoids the pickle path.

If you genuinely need to move Python objects and cannot change formats, cryptographically sign the payload with an HMAC and verify the signature before you ever call pickle.load. That way you only unpickle bytes you produced. This is a mitigation, not a cure, and it still fails if the signing key leaks.

Detecting Risky Usage

You can catch this in code review and in automation. A simple grep for pickle.load, pickle.loads, and cPickle across a repository will surface every call site to audit. Static analysis tools flag pickle deserialization as a known dangerous pattern.

Dependencies matter too, because a library you pull in may unpickle data on your behalf. An SCA tool such as Safeguard can flag packages with known unsafe-deserialization advisories, which is useful when the risky load call lives three layers down in a dependency rather than in your own code.

Building a Team Habit

Treat pickle like eval. Both take input and can turn it into running code, and both deserve the same reflexive suspicion. Add a lint rule that flags pickle usage in modules that touch network or user input, and require a comment justifying any exception. The Safeguard academy has deeper material on deserialization risks across languages, since Java, Ruby, and PHP all have their own versions of this exact class of bug.

The one-line takeaway: pickle is fine for data you created and will read back yourself in a trusted process, and dangerous everywhere else.

FAQ

Is pickle.loads safer than pickle.load?

No. pickle.load reads from a file object and pickle.loads reads from a bytes object, but both run the same unpickling machinery and both will execute embedded instructions. The source of the bytes is what matters, not which function you call.

Can I make pickle safe by restricting what it imports?

You can subclass Unpickler and override find_class to allow-list permitted classes, which reduces the attack surface. It is fragile and easy to get wrong, so prefer a data-only format like JSON whenever possible.

Are machine learning model files affected?

Yes. Many model formats are pickle-based, so loading a model from an untrusted source can execute code. Prefer safer formats like safetensors for weights, and only load models from sources you trust.

Does signing the payload fully solve the problem?

Signing with HMAC ensures you only unpickle data you created, which closes the untrusted-input vector. It does not help if your signing key is compromised, and it adds key-management overhead, so a non-executable format is still the better default.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.