The risky pattern is the same in every language: a deserializer that lets the data choose which types to build. The fix is also shared: use data only formats, fix the target type in code, and verify data before reading it. The calls to watch differ by language.
The common rule
OWASP's guidance is to avoid native deserialization of untrusted data. Where that is not possible, restrict which types can be created, bound resource use and verify the data before reading it. A deserializer can only instantiate types it knows about, so keep it from choosing the target type.
Python
- Risky:
pickle.loads, andyaml.loadwith the full loader. The Python docs say pickle can execute arbitrary code while loading, and PyYAML saysyaml.loadis as powerful aspickle.load. - Also named by OWASP:
jsonpickle.decodeon untrusted input. - Safer: JSON,
yaml.safe_load.
Details: Python pickle and untrusted data and PyYAML safe_load.
Java
- Risky:
ObjectInputStreamreadingSerializableobjects. Look forreadObject,readResolve,readExternal,readUnshared,XMLDecoderandXStream.fromXML. - Signs in traffic: the bytes
AC ED 00 05, Base64rO0or the content typeapplication/x-java-serialized-object. - Safer: JSON with explicit types, serialization filters with an allowlist and size limits where native serialization is unavoidable.
Details: Java allowlist filters.
.NET
- Risky:
BinaryFormatter, which Microsoft says is insecure and cannot be made secure. AlsoSoapFormatter,LosFormatter,NetDataContractSerializerandObjectStateFormatter. - Watch in Json.NET:
TypeNameHandling, which OWASP says to keep atNone, or use a customSerializationBinderas an allowlist. - Safer:
System.Text.Json,XmlSerializer,DataContractSerializer.
Details: BinaryFormatter and its replacements.
PHP
- Risky:
unserialize()on data from users. - Safer:
json_decode()andjson_encode()for data exchanged with users, per OWASP.
Three habits that carry across
- Fix the type in code. Deserialize into a specific data class, never into a type named by the payload.
- Sign when you control both ends. OWASP says to sign data before deserializing and reject messages without a verified signature. Signing proves origin, not safety, if the key or signer is compromised.
- Treat "internal" data as untrusted. Queues, caches and shared storage cross trust boundaries more often than people expect.
Finding it in your code
Search for the calls above, then trace each input back to its source. Dependencies matter too, since a library may deserialize on your behalf. Safeguard's SBOM and dependency scanning and reachability analysis help you see which libraries in your tree carry known deserialization advisories and whether your code reaches them. A free scan is at gold.safeguard.sh/scan.
Quick check
Is JSON always safe to deserialize?
It does not run code by default, but polymorphic type hints in some libraries can bring the risk back. Keep types fixed and validate the content.
Does signing remove the risk?
It prevents outsiders from tampering. It does not help if the signer is untrusted or the key leaks.
Sources
- Deserialization Cheat Sheet (OWASP)
- pickle (Python docs)
- PyYAML documentation
- BinaryFormatter security guide (Microsoft Learn)
Last reviewed: 19 October 2026.