šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

Python Pickle Security Risks

pickle.loads() on untrusted data is one of the most well-documented remote code execution vectors in the Python ecosystem — understand exactly why, and what to use instead.

⚔ Total XP: 0|šŸ’» python XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Core logic.

Quick Quiz //

What does __reduce__ let a malicious pickled payload do during unpickling?


šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

pickle is Python's own native object serialization format, and it's genuinely useful for exactly what it's designed for: serializing arbitrary Python objects between trusted parts of your own system. It is also one of the most consistently documented sources of remote code execution vulnerabilities in real Python software, whenever that boundary is crossed.

1Why pickle Is Powerful: Full Object Graph Serialization

pickle is Python's own, native serialization format, and its core capability genuinely is powerful and useful: unlike JSON, which can only represent plain values, pickle.dumps() can serialize essentially *any* Python object — a class instance with its full attribute state, nested custom objects, even certain function and class references — and pickle.loads() reconstructs that exact object graph, fully rehydrated, on the other end. This makes pickle genuinely valuable for legitimate, specific use cases: caching a complex computed Python object to disk between runs of your own script, or passing arbitrary Python objects between processes via multiprocessing (which uses pickle internally, as covered in the Multiprocessing lesson).

This capability is a direct, severe instance of the 'object-construction format' category from the previous lesson's general framework — pickle's entire design purpose *is* reconstructing arbitrary objects from serialized data, which is precisely the capability that lesson identified as inherently risky for untrusted input. Pickle isn't a plain-data format that happens to have a dangerous edge case; object reconstruction is its fundamental, core, by-design behavior.

Understanding pickle's genuine, legitimate power is important precisely because it explains why the format exists and continues to be used at all, despite the severe risk covered next — the risk isn't a design flaw or an oversight; it's the direct, unavoidable consequence of the exact capability that makes pickle useful for its actual, legitimate purposes.

āœ•
—
+
import pickle

class User:
    def __init__(self, name):
        self.name = name

user = User("Ada")
data = pickle.dumps(user)     # serializes the FULL object, not just plain data
restored = pickle.loads(data)  # reconstructs an actual User instance
print(restored.name)  # 'Ada'
localhost:3000
Pickle's Real Power
pickle.loads(pickle.dumps(user))
Full object reconstruction — genuinely useful for trusted, internal data

2__reduce__: The Exact Mechanism Behind the Vulnerability

__reduce__, a method any class can define, is pickle's own, entirely legitimate mechanism for controlling exactly *how* an instance of that class gets reconstructed during unpickling — it returns a tuple of (callable, arguments), and pickle's actual reconstruction process is, precisely, calling that callable with those arguments. This is genuinely useful for classes with complex internal state that can't be reconstructed simply by restoring attribute values directly (a class wrapping a file handle or a network connection, for instance, might need __reduce__ to specify how to properly re-establish that resource on the receiving end).

The vulnerability is a direct, unavoidable consequence of this exact, legitimate mechanism: __reduce__ can return *any* callable — there's no restriction requiring it to be 'safe' or limited to the defining class's own methods. return (os.system, ("echo 'this could be ANY command'",)) is entirely valid, well-formed pickle protocol usage from pickle's own perspective; pickle.loads() faithfully does exactly what the serialized data instructs it to do, which is calling os.system with the attacker's chosen argument — genuine, unrestricted code execution on whatever machine calls pickle.loads() on that data.

This is why the vulnerability isn't a bug that a future pickle update could patch — it's the direct, necessary consequence of __reduce__'s legitimate design purpose. Fixing it would require removing the exact capability (arbitrary callable invocation during reconstruction) that makes pickle's legitimate use cases possible in the first place, which is precisely why no 'safe mode' for pickle exists or can meaningfully exist.

āœ•
—
+
import pickle, os

class MaliciousPayload:
    def __reduce__(self):
        # __reduce__ tells pickle HOW to reconstruct this object --
        # here, it tells pickle to call os.system('rm -rf ~') INSTEAD
        return (os.system, ("echo 'this could be ANY command'",))

payload = pickle.dumps(MaliciousPayload())
# pickle.loads(payload) -- would ACTUALLY RUN that command. This is not theoretical.
localhost:3000
The Exact Attack Mechanism
__reduce__ → (os.system, (command,))
Legitimate mechanism, weaponized — pickle faithfully executes it either way

3The Unconditional Rule, and What to Use Instead

Given that pickle's core capability *is* arbitrary callable invocation during reconstruction, and no restricted-but-still-functional 'safe mode' exists (unlike YAML's safe_load), the rule is genuinely unconditional and without practical exception: never call `pickle.loads()` (or `pickle.load()`) on data from any source you don't fully, permanently, and completely trust — no user upload, no network request body, no external API response, no data received from a source outside your own directly-controlled system, ever.

Pickle remains entirely appropriate for its genuine, legitimate use cases: data your own code both writes *and* reads, never crossing any external trust boundary at all — caching a complex Python object between runs of a script only you control, or multiprocessing's internal use of it to pass objects between worker processes you yourself spawned. The risk materializes specifically and only when unpickling crosses a genuine trust boundary; pickle used entirely within a system you fully control carries no more inherent risk than any other internal data structure.

For any scenario needing to serialize data that *might* cross a trust boundary — even one you currently believe is fully trusted, since trust boundaries have a way of shifting as systems evolve — default to a plain-data format (JSON, or a purpose-built binary format like msgpack or Protocol Buffers, both of which are limited to plain-data representation, not arbitrary object reconstruction) instead. The engineering discipline this establishes: treat pickle as a tool for a genuinely narrow, specific use case (trusted, internal-only round-tripping), not a general-purpose serialization default, ever.

āœ•
—
+
# NEVER do this on external, untrusted, or user-provided data:
# pickle.loads(request.data)
# pickle.loads(uploaded_file.read())

# Pickle is appropriate ONLY for data your OWN code both wrote AND
# will read, never crossing any external trust boundary at all
localhost:3000
The Unconditional Rule
pickle.loads() only on data your OWN code wrote and reads
Never on anything crossing a trust boundary — no exceptions

4Step-by-Step Breakdown

pickle.loads(untrusted_bytes) can execute arbitrary code on your machine — not hypothetically, but as pickle's actual, documented, by-design behavior. This is the single most important security fact to know about the Python standard library.

pickle.dumps()/loads() can serialize and reconstruct ARBITRARY Python objects -- classes, instances, nearly anything -- which is exactly its power AND its danger.

Because pickle can reconstruct ARBITRARY objects, it can be instructed to call ANY callable during deserialization -- including os.system.

Checkpoint: What does __reduce__ let a malicious pickled payload do during unpickling?

  • →Specify ANY callable (like os.system) and its arguments to be invoked as part of reconstructing the object -- genuine, arbitrary code execution
  • →Only specify which attribute VALUES the reconstructed object should have

The rule, without exception: NEVER call pickle.loads() on data from any source you don't fully and permanently trust -- there is no 'safe_load' equivalent for pickle.

Checkpoint: Is there a "safe_load"-equivalent restricted mode for pickle, the way yaml.safe_load() exists for YAML?

  • →No — pickle's core design IS arbitrary object reconstruction; there is no restricted mode that preserves its functionality while eliminating this risk
  • →Yes — pickle.safe_loads() provides the same protection yaml.safe_load() does

Pickle demonstrates the object-construction-format risk from the previous lesson in its most concrete, severe form; Dependency Security closes this section by looking at risk from a different angle entirely — the packages you depend on, not the data you parse.

Round-Trip a Real Pickled Object. Finish round_trip_user(): pickle reconstructs arbitrary Python objects, not just plain data.

Level Up šŸš€

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported (via server-side Python execution).

FirefoxSupported

Fully supported (via server-side Python execution).

SafariSupported

Fully supported (via server-side Python execution).

EdgeSupported

Fully supported (via server-side Python execution).

Best Practices

Never call pickle.loads()/pickle.load() on data from any external, untrusted, or user-provided source, without exception

There is no restricted 'safe mode' for pickle the way yaml.safe_load() exists for YAML — the risk is inherent to pickle's core design, not a configurable option that can be disabled while keeping the format functional.

Reserve pickle exclusively for data your own code both writes and reads, entirely within a trusted system boundary

This is pickle's genuine, legitimate use case — for anything that might ever cross an external trust boundary, use a plain-data format (JSON, msgpack, Protocol Buffers) instead.

Frequent Bugs

THE BUG

Using pickle to serialize data that will be received from an external source (a user upload, a network request, a message queue consumed from outside the immediate system), creating a remote code execution vulnerability the moment untrusted data reaches pickle.loads().

THE FIX

Replace pickle with a plain-data format (JSON, msgpack) for any serialization scenario where the data could ever originate from, or be tampered with by, a source outside your own directly-controlled system.

Real-World Examples

Replacing an Unsafe Pickle-Based Cache With JSON

A caching layer was originally built using pickle to store complex Python objects, but a security review discovers cache entries could theoretically be influenced by external input, requiring a switch to a safe alternative.

# Before: unsafe if cache entries could ever be influenced externally
import pickle
cached_data = pickle.loads(redis_client.get(cache_key))

# After: safe regardless of how the cache is populated
import json
cached_data = json.loads(redis_client.get(cache_key))
# Requires the cached objects to be representable as plain JSON data --
# a worthwhile constraint given the alternative's severity

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Using pickle.loads() to deserialize data received from any source outside the immediate, fully-trusted codebase (a user upload, an API request body, a message from an external queue), creating a remote code execution vulnerability.

# Dangerous: untrusted data reaches pickle.loads() import pickle data = pickle.loads(request.body) # remote code execution risk # Safe: plain-data format, structurally cannot execute code import json data = json.loads(request.body)

The Solution //

Replace pickle with a plain-data serialization format (JSON, msgpack) for any data that could originate from, or be tampered with by, a source outside your own directly-controlled, trusted system.

Lesson Glossary

[01]pickle

Python's native object serialization format, capable of representing arbitrary Python objects, including full class instances.

Code Preview
// pickle context

[02]__reduce__

A method controlling how an object is reconstructed during unpickling, returning a (callable, arguments) tuple that pickle invokes.

Code Preview
// __reduce__ context

[03]Remote code execution (RCE)

A vulnerability class where an attacker can cause arbitrary code to execute on a target system, which unpickling untrusted data directly enables.

Code Preview
// Remote code execution (RCE) context

[04]msgpack

A binary serialization format limited to representing plain data, offering a safe, compact alternative to pickle for untrusted or cross-boundary data.

Code Preview
// msgpack context

Continue Learning