MCPSafe.io
RegistryThreatsMethodologyDocsPricingScanSign in
MCPSafe.io

Security checks for MCP servers — public packages and private repos, fast or deep.

Legal

Privacy PolicyCookie PolicyTerms of ServiceSecurity disclosure

Resources

State of MCP SecuritySupportSystem statusMade in Germany 🇩🇪

© 2026 MCPSafe. All rights reserved.

GDPR — Privacy Policy
← Threat Catalog

Server Implementation

Insecure deserialization

CRITICALAIVSS 9.5CWE: CWE-502OWASP: LLM05Agentic: T02Rule: MCP-061

A tool calls `pickle.loads`, `yaml.load`, `marshal.loads`, or an equivalent on bytes it did not itself produce, letting the payload construct arbitrary objects and execute arbitrary code.

What it is

Some serialization formats (pickle, Java serialization, unsafe YAML) are not just data formats — they are tiny programs that the deserializer executes while rebuilding the object graph. Calling `pickle.loads(untrusted_bytes)` is equivalent to calling `exec(untrusted_bytes)` with extra steps. The common shapes in MCP are a cache tool that `pickle.load`s a file the model specified, or a YAML-config tool that uses the default (unsafe) loader.

Why it matters for MCP

MCP servers often accept file paths or blob references from the model and round-trip them through a cache or queue. Each round-trip is a chance to slip in an attacker-controlled payload. Because deserialization runs before any tool-level validation, a single poisoned cache entry becomes remote code execution inside the server process with no further interaction required.

Vulnerable example

example.py
1
@server.tool()
2
def load_session(path: str) -> dict:
3
    # pickle.loads on caller-supplied bytes — any .pkl payload = RCE
4
    return pickle.loads(Path(path).read_bytes())

Secure example

example.py
1
@server.tool()
2
def load_session(path: str) -> dict:
3
    # JSON cannot express arbitrary Python objects, so it cannot execute code.
4
    return json.loads(Path(path).read_text())
5
6
# For YAML, use safe_load; never yaml.load(...) with the default loader.
7
def load_config(path: str) -> dict:
8
    return yaml.safe_load(Path(path).read_text())

How MCPSafe detects this

We flag `pickle.load(s)`, `cPickle.load(s)`, `marshal.load(s)`, `yaml.load` without a `Loader=SafeLoader` kwarg, `jsonpickle.decode`, and Node.js `node-serialize.unserialize`. We do not flag `json.loads` or `yaml.safe_load`.

See the full threat catalog for every documented detection.

Framework alignment

OWASP LLM Top-10 (2025)
LLM05 — Improper Output Handling
OWASP Agentic AI Top-10
T02 — Tool Misuse
AIVSS v0.5
9.5 (CRITICAL)AIVSS:1.0/S:CRITICAL/AV:N/AU:L/BR:H/CD:D

Illustrative CVEs

CVEs of the same CWE class. Not MCP-specific, but exemplify the failure mode MCPSafe detects.

  • CVE-2017-5638 — Apache Struts OGNL deserialization — led to Equifax breach
  • CVE-2023-32315 — Openfire — classic untrusted-deserialization RCE

Further reading

  • CWE-502: Deserialization of Untrusted Data
  • OWASP Deserialization Cheat Sheet

Scan an MCP server for this issue

MCPSafe runs this check — and every other rule in the catalog — on any MCP server you paste in.

Scan now