{"record":{"id":"6353b84ca7b5fbce","repo":"deepset-ai/haystack","slug":"refusing-to-deserialize-handle-it-resolves-to","errorCode":null,"errorMessage":"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.","messagePattern":"Refusing to deserialize '(.+?)': it resolves to '(.+?)', 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\\.","errorType":"exception","errorClass":"DeserializationError","httpStatus":null,"severity":"critical","filePath":"haystack/core/serialization_security.py","lineNumber":261,"sourceCode":"    See :func:`_is_deserialization_internal` for what that covers.\n    Used by the resolution paths (`deserialize_callable`, `_import_class_by_name`) as a companion to\n    the builtin and import-primitive denylists. It refuses the allowlist-administration function, the\n    resolution helpers, and the mutable allowlist/context state — all of which live in (or are\n    reachable through) the allowlisted `haystack` namespace and would otherwise be resolvable from\n    serialized data. Bypassed in `unsafe=True` mode, which disables all safety checks.\n\n    :param resolved:\n        The object resolved from the serialized handle.\n    :param handle:\n        The original serialized handle, used only for the error message.\n    :raises DeserializationError:\n        If `resolved` is part of the deserialization control plane.\n    \"\"\"\n    if _is_unsafe_deserialization():\n        return\n    if _is_deserialization_internal(resolved):\n        name = getattr(resolved, \"__qualname__\", None) or getattr(resolved, \"__name__\", None) or repr(resolved)\n        raise DeserializationError(\n            f\"Refusing to deserialize '{handle}': it resolves to '{name}', which is part of Haystack's \"\n            f\"deserialization control plane (its allowlist administration, mutable allowlist/context state, \"\n            f\"or a resolution helper) and must never be produced by deserializing untrusted data — doing so \"\n            f\"would let the data operate the deserialization allowlist against itself. If you trust the \"\n            f\"source of this data, load it with unsafe=True to bypass deserialization safety checks.\"\n        )\n\n\n# Non-dunder attribute names that still expose an object's internals — the frame/code/closure\n# accessors on functions, generators, coroutines and async generators. Dunder names (`__globals__`,\n# `__dict__`, `__class__`, `__builtins__`, `__subclasses__`, ...) are matched separately by the\n# `__` prefix; these have no such prefix and must be listed explicitly.\n_UNSAFE_TRAVERSAL_ATTRS: frozenset[str] = frozenset(\n    {\n        \"gi_frame\",\n        \"gi_code\",\n        \"gi_yieldfrom\",\n        \"cr_frame\",","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/core/serialization_security.py#L243-L279","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npipeline_dict[\"components\"][\"x\"][\"init_parameters\"][\"fn\"] = \"haystack.core.serialization_security.allow_deserialization_module\"\n// after (trusted data only)\nPipeline.loads(json.dumps(pipeline_dict), unsafe=True)","handlingStrategy":"try-catch","validationCode":"import json\ndef references_security_internals(serialized: str) -> bool:\n    return \"serialization_security\" in serialized or \"allow_deserialization_module\" in serialized","typeGuard":null,"tryCatchPattern":"from haystack.core.errors import DeserializationError\ntry:\n    pipe = Pipeline.load(path)\nexcept DeserializationError as e:\n    if \"control plane\" in str(e):\n        raise  # do not auto-bypass; treat as untrusted/compromised input\nraise","preventionTips":["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"],"tags":["security","deserialization","haystack"],"backgroundTag":"unsafe-deserialization-control-plane","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}