Every serialization format sits somewhere on a spectrum from 'pure data' to 'can reconstruct arbitrary objects.' This lesson covers the general principle that determines a format's safety with untrusted input, building the judgment needed to evaluate any serialization library, not just the specific ones this curriculum has already covered.
1The Real Distinction: What the Format's Data Model Can Express
This curriculum has now covered two format-specific instances of the same underlying safety issue: yaml.load() without a safe Loader (in the YAML lesson) can construct arbitrary Python objects from tags embedded in the input, a genuine security risk. json.loads(), by contrast, has no equivalent risk at all, for *any* input, no matter how maliciously crafted ā and the reason isn't that JSON's implementation happens to be more carefully written; it's that JSON's data model itself only has the vocabulary to represent strings, numbers, booleans, null, arrays, and objects (nested key-value mappings). There is no syntax in JSON, none whatsoever, capable of expressing 'construct an instance of this specific Python class' or 'execute this code as a side effect of parsing.'
This is the general principle worth internalizing well beyond just YAML and JSON specifically: a serialization format's safety for untrusted input is determined by what its data model can express, not by how carefully a specific parsing library happens to be implemented. A format limited to representing plain data ā values, without any mechanism for encoding executable instructions or arbitrary type construction ā is safe for untrusted input *by construction*, regardless of how adversarial the input is crafted to be. A format whose data model *can* express object construction (YAML's tags, pickle's opcodes, covered in the next lesson) carries an inherent risk that no amount of 'being careful' with the input can fully eliminate ā only a genuinely restricted parsing mode (like yaml.safe_load()) or avoiding the format entirely for untrusted input can.
This reframing turns 'is this serialization library safe?' from a question requiring deep, format-specific expertise into a general, transferable question you can ask about *any* serialization library, familiar or not: does its underlying format have any way to represent 'construct this object' or 'run this code', beyond plain data values?
import json
# JSON's data model has no concept of 'run this code' or 'construct this class'
data = json.loads(untrusted_input)
# Worst case: data is malformed, or contains unexpected/wrong values --
# it can NEVER cause code execution, structurally, no matter what the input containsStructurally cannot express object construction ā safe by construction
2A Transferable Question for Any New, Unfamiliar Library
The practical value of this reframing is that it equips you to evaluate a serialization library you've never encountered before, without needing to have already memorized its specific, documented CVE history. Encountering a new library ā say, a specialized binary serialization format for a particular domain ā the question to ask directly, before using it on any untrusted input, is precisely: does deserializing with this library's default (or only) mode have any documented capability to construct arbitrary types, execute callback/hook functions during parsing, or otherwise go beyond producing plain, inert data values?
Many serialization libraries, aware of exactly this concern, explicitly document a 'safe mode' or equivalent restricted parsing function specifically for untrusted input ā yaml.safe_load() being the example already covered in depth, but this pattern recurs across the ecosystem (some serialization libraries offer an explicit allow_pickle=False-style flag, for instance, precisely to disable a more powerful but riskier capability when it isn't needed). Looking specifically for this kind of documented, deliberate safety distinction is a strong, positive signal that the library's authors have thought carefully about this exact risk.
The absence of any such distinction ā a library whose only deserialization function is also its most powerful, object-constructing one, with no restricted alternative offered at all ā is itself a meaningful red flag when evaluating whether to use that library on data from a source you don't fully and permanently trust, worth investigating further or avoiding for that specific use case before adopting it.
# yaml.load() (WITHOUT safe Loader) can be instructed, via YAML tags,
# to construct arbitrary Python objects -- a capability JSON's format
# simply does not have a way to express AT ALL
# This is a difference in what the FORMAT ITSELF can represent,
# not just a difference in how carefully a specific library was writtenA strong signal the authors deliberately addressed this exact risk
3Matching the Format to the Actual Trust Level of the Data
The practical decision this framework leads to: for data whose origin is genuinely untrusted (user uploads, third-party webhooks, any external API response, anything crossing a genuine system boundary), default to formats whose data model is limited to plain data ā JSON, CSV, yaml.safe_load(), plain XML parsing ā where this entire class of vulnerability simply cannot apply, structurally, regardless of how carefully or carelessly the parsing code itself is written elsewhere.
For data that is genuinely, permanently internal ā never crossing a trust boundary, generated and consumed entirely by your own code, on your own infrastructure ā a more powerful, object-preserving format (pickle, covered next, or an equivalent) can be a reasonable, deliberate choice specifically because you control both ends of the serialization, and the risk this lesson covers only materializes when *untrusted* input reaches a deserializer capable of more than plain data. The danger isn't the powerful format existing at all; it's applying a powerful, object-constructing format to data whose origin and trustworthiness you don't fully control.
This is the same 'match the tool to the actual trust boundary' discipline this curriculum has applied consistently ā to YAML loading, to ZIP extraction, to file path validation ā now generalized into a reusable principle for evaluating any serialization decision: identify where the data genuinely comes from, and choose a format whose capabilities match (never exceed) what that actual trust level warrants.
# Safe for untrusted input (data-only formats):
# json, csv, plain XML/YAML parsing (via safe_load)
# UNSAFE for untrusted input (can express object construction):
# pickle, yaml.load() without SafeLoader, and similarly-capable
# 'deserialize into arbitrary types' librariesFully-trusted, internal-only data ā richer formats can be reasonable
4Step-by-Step Breakdown
The same question ā 'can this format reconstruct arbitrary Python objects, or only plain data' ā determines whether a serialization library is safe for untrusted input, no matter which specific library you're evaluating.
JSON can ONLY represent plain data -- strings, numbers, booleans, lists, objects. There is NO way to encode 'construct an arbitrary Python object' in JSON's format itself.
Checkpoint: Can json.loads() on malicious, untrusted input ever cause arbitrary code execution?
- āNo ā JSON's data model has no way to express 'construct this object' or 'run this code' at all, so this class of attack is structurally impossible regardless of the input's content
- āIt depends on the specific content of the malicious input
Some formats (like unrestricted YAML, covered earlier, and pickle, covered next) CAN encode 'construct this arbitrary object' -- and that capability is exactly the risk.
The general question to ask about ANY serialization library before using it on untrusted input: can this format's DATA MODEL express arbitrary object construction?
Checkpoint: What is the single general question to ask when evaluating whether an unfamiliar serialization library is safe for untrusted input?
- āCan this format's data model express instructions to construct arbitrary objects (or execute code), or is it limited to representing plain data?
- āIs the library popular and widely used?
This general framework sets up the next lesson's deep dive into pickle specifically ā the format that sits at the most dangerous end of this spectrum.
Parse Real Untrusted JSON Safely. Finish safe_parse(): JSON's data model can never express arbitrary code execution.
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported (via server-side Python execution).
Fully supported (via server-side Python execution).
Fully supported (via server-side Python execution).
Fully supported (via server-side Python execution).
Best Practices
Ask 'can this format's data model express object construction' for any new serialization library before trusting it with untrusted input
This transferable question determines safety independent of a library's specific implementation history, letting you evaluate unfamiliar libraries with the same rigor as well-documented ones like YAML.
Default to plain-data-only formats (JSON, CSV, safe_load-style YAML) for anything crossing a genuine trust boundary
These formats make the entire 'arbitrary object construction from untrusted input' vulnerability class structurally impossible, regardless of how the input is crafted.
Frequent Bugs
Adopting a new, unfamiliar serialization library for untrusted input without first checking whether its data model can express arbitrary object construction, only discovering the risk after a specific documented vulnerability is found later.
Before using any serialization library on untrusted input, explicitly verify whether its default deserialization capability is limited to plain data, or check for a documented restricted/safe parsing mode if it can express more.
Real-World Examples
Evaluating a New Serialization Library for a Webhook Handler
A team is choosing a serialization format for a new webhook endpoint receiving data from a third-party, untrusted source, and needs to decide between a few candidate libraries.
# Evaluation checklist for each candidate library:
# 1. Does its DATA MODEL support representing arbitrary object construction?
# 2. If yes, does it offer a documented, restricted 'safe' mode?
# 3. If no safe mode exists and the data model can express object
# construction, DO NOT use it for this untrusted webhook data --
# choose JSON, or a format limited to plain data instead