Serialisation turns an object into bytes; deserialisation turns bytes back into an object. The problem is that some formats can describe how to construct an object, which means the data can specify code to run.
Python pickle
Pickle's format includes instructions for calling arbitrary callables during reconstruction. Anyone who can supply the bytes can execute code as your process.
This isn't a bug in pickle. It's documented behaviour, and the standard library says plainly that it isn't secure against maliciously constructed data.
The fix is to use a different format:
JSON describes values, not construction. There's nothing to exploit because there's no mechanism to express it.
If you genuinely need to move Python objects between trusted services, sign the payload with HMAC and verify before unpickling. But "trusted" has to mean genuinely trusted. A cache, a queue or a session store that anyone can write to is not.
YAML
Full YAML supports type tags that instantiate arbitrary Python objects. safe_load restricts it to plain data types. Recent PyYAML versions warn or require an explicit loader, but older code is everywhere.
Other languages
Node. No native equivalent, and JSON.parse is safe. Risk comes from libraries that deserialise richer formats, node-serialize is the well-known one. And from prototype pollution, where a crafted __proto__ key in JSON corrupts object prototypes. Different mechanism, similar consequences.
Java. Native serialisation with a long history of gadget-chain exploits. Avoid ObjectInputStream on untrusted input; prefer JSON with an explicitly typed mapper.
PHP. unserialize() on user input, exploitable through magic methods. Use json_decode.
The pattern holds across all of them: the safe format is the one that can only express data.
Where it hides
Rarely a pickle.loads(request.body) sitting in a request handler. More often:
Session data. Frameworks that serialise sessions into cookies or Redis. If the store is writable or the cookie isn't signed, that's an entry point.
Caches. Objects cached as pickled bytes in Redis or memcached, where anyone with network access to the cache can write.
Message queues. Tasks serialised into a queue. Celery's default is JSON now, but older configurations used pickle.
File uploads. Model files,
.npyarrays, and anything ML-adjacent, many of those formats are pickle underneath.
That last one matters more than it used to. Loading a model from an untrusted source is the same problem in a different costume.
What a scanner will tell you
The direct cases are findable. pickle.loads, yaml.load without a safe loader and unserialize are distinctive calls with traceable inputs.
How findable depends on the tool. In our RealVuln benchmark, insecure deserialisation is the class with the widest measured gap of any we broke out: LLM-based scanners recalled 100% of the labelled instances, rule-based tools 57%. Even on a bug with a named function at the centre of it, better than two in five went unreported by the pattern matchers.
The indirect route is harder again. A cache or session backend configured to use pickle in a settings file, several layers from where untrusted data enters, has no vulnerable-looking line at all. Grep your configuration for serialiser settings as well as your application code.