All posts

Field guide

Insecure deserialization in AI code: how pickle and yaml.load run code

Turning data back into objects sounds harmless, but some loaders can do more than build data, they can run code while they parse. AI tools reach for the familiar ones, pickle and the full YAML loader, on input they do not control. Here is why that is remote code execution and the one-word fix.

Last updated

Deserialization is just turning stored or transmitted data back into objects your program can use. The catch is that some formats can encode instructions to construct objects, and constructing them can run code. When the data is something you wrote, that is fine. When it comes from a user, an attacker controls those instructions, so parsing their payload executes whatever they put in it.

The two loaders AI tools reach for

Python's pickle and PyYAML's full loader are the common culprits, because they are the examples that appear most in training data and both work perfectly on well-formed input. The assistant writes the familiar call, it parses correctly in the demo, and nothing signals that the same call becomes a foothold the moment the input is hostile.

# both of these can run code from a crafted payload
import pickle
obj = pickle.loads(request.data)

import yaml
config = yaml.load(request.data)   # full loader

A YAML document can carry a tag that tells the full loader to call a callable while parsing, and a pickle stream can encode object construction that runs code. Either way, loading attacker-controlled bytes is enough. The concept in full is in what insecure deserialization is.

Want to see exactly what Prbl flags? Watch it scan a demo app, no repo or account needed.

See a live scan →

The fix: use a data-only loader

Never deserialize untrusted input with something that can execute code. For YAML, call yaml.safe_load, which only builds plain data types. For data interchange in general, prefer JSON. Never unpickle data you did not create, because pickle has no safe mode for untrusted input.

# YAML: the safe loader builds only plain data
import yaml
config = yaml.safe_load(request.data)

# interchange: JSON carries no code-construction behavior
import json
obj = json.loads(request.data)

The change is small and the exposure disappears, because a data-only loader has nothing for the payload to hijack. The full walkthroughs are in the fixes for unsafe YAML deserialization and insecure pickle deserialization.

Frequently asked questions

Why does loading data run code at all?

Because some serialization formats encode more than values. They can encode instructions to construct objects, which may call code as part of rebuilding them. Python's pickle and PyYAML's full loader both do this. When the data is trusted, that power is convenient. When the data comes from a user, the attacker controls those instructions, so parsing their payload runs whatever they chose. The bug is using a code-capable loader on input you do not control.

Is yaml.load really dangerous if I only parse config files?

On truly trusted config on disk that you wrote, the risk is low. The problem is that the same call often ends up on paths that read user input, like an import feature or a request body, and there it becomes remote code execution. Because the fix is free, switching to yaml.safe_load everywhere is the safer habit than trying to track which call sites are trusted.

What is the safe alternative?

Use a data-only format and loader. For YAML, call yaml.safe_load, which only produces plain types like dicts, lists, strings, and numbers and cannot construct arbitrary objects. For data interchange in general, prefer JSON, which carries no code-construction behavior. Never unpickle data you did not create yourself, since pickle has no safe mode for untrusted input.

How would an attacker even reach this?

Anywhere your app turns external bytes into objects: a request body, an uploaded file, a message from a queue, a cookie or token that stores a serialized object. If any of those feed a code-capable loader, a crafted payload runs on your server. The tell to look for is pickle.loads or yaml.load, or any deserializer, sitting on data that originated outside your own code.

Find code-capable loaders on untrusted input

A deserializer sitting on request data has a recognizable shape, and a scan flags it with the file and line, so you can switch to a safe loader before the payload arrives. Run a free scan and see whether anything in your app deserializes input unsafely.

Prbl scans your live app or your codebase for exactly the kinds of issues above.

Insecure Deserialization in AI Code: How pickle and yaml.load Run Code: Prbl