deepset-ai/haystack · critical · DeserializationError
Refusing to deserialize '{handle}': it resolves to '{name}',
Error message
Refusing to deserialize '{handle}': it resolves to '{name}', which is part of Haystack's deserialization control plane (its allowlist administration, mutable allowlist/context state, or a resolution helper) and must never be produced by deserializing untrusted data — doing so would let the data operate the deserialization allowlist against itself. If you trust the source of this data, load it with unsafe=True to bypass deserialization safety checks. What it means
Haystack's deserialization security layer blocks serialized data that resolves to internal control-plane objects: functions or state that administer the module allowlist or deserialization context (e.g. allow_deserialization_module or resolution helpers). Deserializing untrusted data into these would let an attacker manipulate the allowlist itself. Raise with unsafe=True only for trusted data.
Source
Thrown at haystack/core/serialization_security.py:261
See :func:`_is_deserialization_internal` for what that covers.
Used by the resolution paths (`deserialize_callable`, `_import_class_by_name`) as a companion to
the builtin and import-primitive denylists. It refuses the allowlist-administration function, the
resolution helpers, and the mutable allowlist/context state — all of which live in (or are
reachable through) the allowlisted `haystack` namespace and would otherwise be resolvable from
serialized data. Bypassed in `unsafe=True` mode, which disables all safety checks.
:param resolved:
The object resolved from the serialized handle.
:param handle:
The original serialized handle, used only for the error message.
:raises DeserializationError:
If `resolved` is part of the deserialization control plane.
"""
if _is_unsafe_deserialization():
return
if _is_deserialization_internal(resolved):
name = getattr(resolved, "__qualname__", None) or getattr(resolved, "__name__", None) or repr(resolved)
raise DeserializationError(
f"Refusing to deserialize '{handle}': it resolves to '{name}', which is part of Haystack's "
f"deserialization control plane (its allowlist administration, mutable allowlist/context state, "
f"or a resolution helper) and must never be produced by deserializing untrusted data — doing so "
f"would let the data operate the deserialization allowlist against itself. If you trust the "
f"source of this data, load it with unsafe=True to bypass deserialization safety checks."
)
# Non-dunder attribute names that still expose an object's internals — the frame/code/closure
# accessors on functions, generators, coroutines and async generators. Dunder names (`__globals__`,
# `__dict__`, `__class__`, `__builtins__`, `__subclasses__`, ...) are matched separately by the
# `__` prefix; these have no such prefix and must be listed explicitly.
_UNSAFE_TRAVERSAL_ATTRS: frozenset[str] = frozenset(
{
"gi_frame",
"gi_code",
"gi_yieldfrom",
"cr_frame",View on GitHub (pinned to e318778c9b)
Solutions
- Remove the offending serialized reference; it should never appear in legitimate pipeline data.
- If the data truly comes from a trusted source, load with Pipeline.load(..., unsafe=True).
- Check whether your own to_dict() accidentally serialized an internal helper (e.g. a lambda/imported function) as a callable handle.
- Audit the pipeline file's provenance before considering unsafe=True.
Example fix
// before pipeline_dict["components"]["x"]["init_parameters"]["fn"] = "haystack.core.serialization_security.allow_deserialization_module" // after (trusted data only) Pipeline.loads(json.dumps(pipeline_dict), unsafe=True)
Defensive patterns
Strategy: try-catch
Validate before calling
import json
def references_security_internals(serialized: str) -> bool:
return "serialization_security" in serialized or "allow_deserialization_module" in serialized Try / catch
from haystack.core.errors import DeserializationError
try:
pipe = Pipeline.load(path)
except DeserializationError as e:
if "control plane" in str(e):
raise # do not auto-bypass; treat as untrusted/compromised input
raise Prevention
- Never load pipeline files from untrusted sources with unsafe=True
- Treat this error as evidence of tampering, not a config bug
- Ensure your own components never serialize security-module callables
- Audit third-party pipeline YAML before loading
When it happens
Trigger: deserialize_callable() or _import_class_by_name() resolving a handle from untrusted serialized data to a security-internal function/state attribute of haystack.core.serialization_security.
Common situations: Loading a pipeline file from an untrusted source crafted to reach the allowlist API; re-serializing after importing security internals into your own module namespace; fuzzing/pen-testing pipelines.
Related errors
- Refusing to deserialize an OutputAdapter with unsafe=True wh
- Refusing to deserialize an OutputAdapter with custom filters
- Refusing to deserialize a ConditionalRouter with unsafe=True
- Refusing to deserialize a ConditionalRouter with custom filt
- Couldn't deserialize component '{name}' of class '{component
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/6353b84ca7b5fbce.
Report an issue: GitHub.