Unsafe YAML deserialization occurs when an application parses untrusted YAML input using a loader that respects type tags. The parser instantiates objects defined in the file, calling their constructors. If the constructor executes code, the attacker gains remote code execution. You must disable tag resolution to stop this.
The Mechanics of Data Conversion
YAML serves as a human-readable data serialization standard. It structures information using indentation and key-value pairs. Applications use YAML for configuration files, data exchange, and state storage. The parser reads the text and converts it into native data structures. This process is called deserialization. The opposite process, converting objects to text, is serialization.
Deserialization becomes unsafe when the parser trusts the input to define object types. Standard YAML supports type tags. These tags tell the parser how to interpret a value. A tag like !!int forces a value to be treated as an integer. A tag like !!python/object tells a Python parser to instantiate a specific class. The parser does not just store data. It constructs objects in memory. This construction phase is where the vulnerability lives.
Stage 1: Input Acceptance
The application receives a YAML file or string. This input might come from a user upload, a configuration file, or an API payload. The application passes this raw text to a YAML library. The library begins parsing the document structure. It identifies keys, values, lists, and mappings. At this stage, the data is still just text. No code has executed yet.
The parser looks for special characters. It recognizes the ! symbol as a tag indicator. The parser builds an internal representation of the document tree. It does not yet know what objects to create. It only knows the structure and the tags attached to each node.
| Stage | What happens | Where it can be stopped |
|---|---|---|
| 1 | Parser reads raw text and identifies structure. | Reject input at the network or application layer before parsing. |
| 2 | Parser resolves type tags to specific classes. | Use a safe loader that ignores custom tags. |
| 3 | Parser instantiates objects and runs constructors. | Restrict the list of allowed classes during instantiation. |
| 4 | Application uses the deserialized objects. | Validate the data after deserialization is complete. |
Stage 2: Tag Resolution
The parser encounters a tag in the input. It maps this tag to a class in the programming language. For example, !!python/object/apply:os.system maps to the os.system function in Python. The parser checks if this class is available in the current environment. If the class exists, the parser prepares to create an instance of it.
This step relies on the parser’s configuration. Many libraries default to allowing all built-in types. Some allow all types found in the Python path. This is the critical failure point. The parser assumes that if a tag is present, the class is safe to instantiate. It does not evaluate the security implications of the class. It only checks for existence.
Stage 3: Object Instantiation
The parser creates the object. It calls the class constructor. If the class has a constructor that executes code, that code runs immediately. This happens before the application can validate the data. The code runs with the same privileges as the application. If the application runs as root, the code runs as root.
Imagine a YAML file contains !!python/object/apply:subprocess.call. The parser calls subprocess.call with the arguments provided in the YAML file. The arguments might be a command string like rm -rf /. The command executes instantly. The parser returns the result to the application. The application may never even look at the data. The damage is already done.
This stage exploits the concept of gadget chains. A gadget is a class whose constructor or method performs an action. An attacker chains multiple gadgets together. One gadget might open a file. Another might write to it. Another might execute the file. The YAML structure allows complex nesting. This enables the attacker to build a chain of actions.
Stage 4: Execution and Impact
The code executes. The attacker achieves their goal. This might be remote code execution, data exfiltration, or denial of service. The application continues running, unaware of the intrusion. The attacker has control over the process. They can install backdoors, steal credentials, or pivot to other systems.
The impact depends on the privileges of the application. If the application runs with limited privileges, the damage is contained. If it runs with high privileges, the attacker gains full control. The attacker can also exploit other vulnerabilities. For example, they might exploit Kubernetes misconfigurations to escape the container. They might exploit sudo vulnerabilities to escalate privileges on the host.
Interrupting the Attack Chain
You can interrupt the attack at several stages. The most effective point is during tag resolution. Use a safe loader that only allows basic types like strings, integers, and lists. Do not allow object instantiation. If you must support custom types, use a strict whitelist. Only allow classes that you have verified as safe.
Another point is input validation. Validate the YAML structure before parsing. Check for the presence of tags. Reject any file that contains ! or !!. This is not perfect. An attacker might encode the tag differently. But it blocks simple attacks. It also reduces the attack surface.
You can also isolate the parsing process. Run the parser in a sandbox. Limit its network access. Limit its file system access. If the parser executes code, the sandbox contains the damage. This is a defense-in-depth strategy. It does not prevent the attack, but it limits the impact.
Common Misconceptions
Many developers believe that YAML is safe because it is human-readable. They assume that if they can read it, it cannot contain hidden code. This is false. The tags are visible. But developers often ignore them. They focus on the data, not the structure. They do not realize that the structure triggers execution.
Another misconception is that only Python is vulnerable. YAML parsers in Java, Ruby, and other languages also support object instantiation. The specific tags differ, but the mechanism is the same. Any language that supports reflection or dynamic class loading is at risk. You must check your specific library’s documentation.
Review your dependencies. Many libraries have safe and unsafe loaders. Use the safe one. If the library does not have a safe mode, consider replacing it. Security should not be an afterthought.
Related Security Concerns
Unsafe YAML deserialization is part of a broader class of vulnerabilities. It shares traits with prototype pollution in JavaScript. Both exploit the way parsers handle input to manipulate object properties. Both can lead to remote code execution. Understanding one helps you understand the other.
It is also related to logging library vulnerabilities. If an application logs YAML data without sanitization, an attacker might inject tags into the log. If the log is parsed later, the tags execute. This is a secondary attack vector. It requires two flaws: injection and parsing. But it is a real risk.
The vulnerability appears in the OWASP Top 10. It falls under injection flaws. It is a reminder that data is not just data. It is code if the parser treats it as such. You must treat all input as untrusted. This includes configuration files, uploads, and API payloads.

Mitigation Strategies
Start by auditing your code. Find all places where YAML is parsed. Check the loader used. Replace unsafe loaders with safe ones. If you need custom types, implement a strict whitelist. Test your application with malicious YAML files. Use fuzzing to find edge cases.
Train your team. Developers need to understand how YAML works. They need to know that tags are dangerous. They need to know that default loaders are often unsafe. Security is a team effort. It requires awareness at every level.
Monitor your systems. Look for signs of deserialization attacks. Check for unexpected process execution. Check for network connections to unknown hosts. Use exposure management to identify assets that might be vulnerable. Reduce the attack surface by limiting access to configuration files.
Key takeaways
- YAML parsers often execute code during object construction, not after parsing is complete.
- Type tags in YAML files instruct the parser which class to instantiate, enabling arbitrary object creation.
- Default loaders in many languages are unsafe because they trust the input to define valid types.
Unsafe YAML deserialization executes code during parsing, not after. Always use safe loaders that restrict type tags to prevent arbitrary object instantiation.
Frequently asked questions
Is YAML inherently unsafe?
No. YAML is a data format. It becomes unsafe only when the parser allows it to define object types that execute code. Using safe loaders prevents this.
Can I just filter out the `!` character?
No. Attackers can use other syntax to define tags. Filtering characters is not a reliable defense. You must use a parser that restricts tag resolution.
Does JSON have this problem?
No. JSON does not support type tags. It only supports basic data types. It cannot instantiate objects directly from the text.
How do I check if my code is vulnerable?
Look for calls to YAML parsers. Check the arguments passed to the loader. If it allows custom tags or object instantiation, it is likely vulnerable.
How this guide was produced: written by the Patch Review editorial team with AI assistance, checked against the public references listed below, and reviewed when the facts change. See our editorial policy or report an error.







