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

  1. Remove the offending serialized reference; it should never appear in legitimate pipeline data.
  2. If the data truly comes from a trusted source, load with Pipeline.load(..., unsafe=True).
  3. Check whether your own to_dict() accidentally serialized an internal helper (e.g. a lambda/imported function) as a callable handle.
  4. 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

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


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/6353b84ca7b5fbce. Report an issue: GitHub.