Why yaml.load() is Unsafe Without SafeLoader in Python

Deserializing untrusted data with Python's PyYAML library using yaml.load() without specifying SafeLoader exposes applications to arbitrary code execution vulnerabilities. This article explains the technical mechanics behind how the default PyYAML loader handles custom Python tags, how an attacker can leverage this behavior to execute arbitrary commands, and how using SafeLoader or yaml.safe_load() completely eliminates the risk.

The Mechanics of Arbitrary Code Execution

YAML is not just a data-serialization format like JSON; the full YAML specification supports custom data types and tags that instruct the parser to reconstruct native objects. PyYAML natively implements this feature to serialize and deserialize complex Python objects, including class instances, module functions, and system calls.

When yaml.load() runs without Loader=yaml.SafeLoader, it uses loaders capable of interpreting Python-specific tags, such as !!python/object/apply:. When the parser encounters this tag, it resolves the specified Python module or callable and passes the provided arguments directly to it during parsing.

For example, an attacker can provide the following YAML payload:

!!python/object/apply:os.system ["id"]

When processed with an unsafe loader, the PyYAML parser imports the os module and executes os.system("id"). Because execution occurs during the parsing phase, any untrusted input passed to yaml.load() can run shell commands, alter files, or open reverse shells with the same permissions as the running application process.

The Function of SafeLoader

The yaml.SafeLoader class disables the resolution of dynamic Python tags. Instead of constructing arbitrary Python objects or invoking callables, SafeLoader strictly parses basic YAML types, converting documents only into native, non-executable data structures:

If a payload containing custom constructor tags like !!python/object or !!python/object/apply is fed to SafeLoader, the parser raises a ConstructorError and immediately halts execution, preventing malicious code from running.

How to Secure PyYAML Deserialization

To prevent Remote Code Execution (RCE) vulnerabilities, avoid raw calls to yaml.load() without an explicit safe loader.

The standard and recommended approach is to use yaml.safe_load():

import yaml

# Safe: parses standard types and rejects custom executable tags
data = yaml.safe_load(untrusted_yaml_string)

Alternatively, pass SafeLoader explicitly if using yaml.load():

import yaml

# Safe: explicitly defines SafeLoader
data = yaml.load(untrusted_yaml_string, Loader=yaml.SafeLoader)

Any YAML data received over a network, uploaded by users, or retrieved from untrusted storage should always be deserialized using yaml.safe_load() to maintain application security.