{"record":{"id":"b8f578e8e4deeb44","repo":"deepset-ai/haystack","slug":"refusing-to-deserialize-handle-it-resolves-to-b8f578","errorCode":null,"errorMessage":"Refusing to deserialize '{handle}': it resolves to an import primitive that can load arbitrary modules (equivalent to the blocked builtin '__import__'), which is a gateway to code execution. 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 an import primitive that can load arbitrary modules \\(equivalent to the blocked builtin '__import__'\\), which is a gateway to code execution\\. 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":518,"sourceCode":"    Reject `resolved` if it is an import primitive that is unsafe to resolve from serialized data.\n\n    Used by the callable-resolution path (`deserialize_callable`) as a companion to\n    :func:`_check_not_denied_builtin`. It blocks the non-builtin import primitives in\n    :data:`_DENIED_CALLABLE_OBJECTS` / :data:`_DENIED_CALLABLE_QUALNAMES` (e.g.\n    `importlib.import_module`, `haystack.utils.type_serialization.thread_safe_import`), which are\n    functionally equivalent to the already-denied builtin `__import__` and can load any module as a\n    gateway to code execution. 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    \"\"\"\n    if _is_unsafe_deserialization():\n        return\n    ident = (getattr(resolved, \"__module__\", \"\"), getattr(resolved, \"__qualname__\", \"\"))\n    if any(resolved is denied for denied in _DENIED_CALLABLE_OBJECTS) or ident in _DENIED_CALLABLE_QUALNAMES:\n        raise DeserializationError(\n            f\"Refusing to deserialize '{handle}': it resolves to an import primitive that can load \"\n            f\"arbitrary modules (equivalent to the blocked builtin '__import__'), which is a gateway \"\n            f\"to code execution. If you trust the source of this data, load it with unsafe=True to \"\n            f\"bypass deserialization safety checks.\"\n        )\n\n\ndef _check_builtin_is_type(resolved: object, handle: str) -> None:\n    \"\"\"\n    Reject a `builtins` member resolved in a type/class context that is not a `type`.\n\n    Used by `deserialize_type` and `import_class_by_name`, which resolve type annotations and class\n    references — always classes. Requiring the resolved `builtins` member to be a `type` lets every\n    builtin type through (e.g. `str`, `memoryview`) while rejecting every builtin *function* (e.g.\n    `eval`, `exec`, `getattr`), with no denylist to maintain. Bypassed in `unsafe=True` mode.\n\n    :param resolved:\n        The object resolved from the serialized handle.","sourceCodeStart":500,"sourceCodeEnd":536,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/core/serialization_security.py#L500-L536","documentation":"Deserialization refuses to restore a callable whose handle resolves to an import primitive (e.g. importlib.import_module or builtins.__import__) because such a callable can load arbitrary modules and execute code. Haystack blocks known dangerous callables when loading serialized pipelines. If you trust the data source, re-load with unsafe=True.","triggerScenarios":"Deserializing pipeline YAML/JSON whose callable handle (e.g. an init_parameters entry) resolves via deserialize_callable to an object in _DENIED_CALLABLE_OBJECTS or a denied (module, qualname) pair such as importlib.__init__.import_module.","commonSituations":"Hand-edited or third-party serialized pipeline files that reference import functions; pipelines exported from older/other versions; tampered or untrusted pipeline payloads.","solutions":["Inspect the serialized handle and replace the import-primitive reference with a direct reference to the class/function you actually need","If the data source is trusted, load with deserializer.loads(data, unsafe=True)","Remove or sanitize the offending entry in the serialized file"],"exampleFix":"// before: serialized handle references an import primitive\n\"init_parameters\": {\"type\": \"importlib.import_module\", ...}\n// after: reference the concrete class directly, or load trusted data unsafely\n\"init_parameters\": {\"type\": \"haystack.components.builders.prompt_builder.PromptBuilder\"}\ndata = Pipeline.loads(yaml_str, unsafe=True)  # only if source is trusted","handlingStrategy":"validation","validationCode":"import re\nDENIED = (\"importlib.import_module\", \"builtins.__import__\", \"__builtin__.__import__\")\ndef uses_import_primitive(serialized_yaml: str) -> bool:\n    return any(h in serialized_yaml for h in DENIED)","typeGuard":"def is_safe_handle(handle: str) -> bool:\n    root = handle.split(\".\")[0]\n    return root not in {\"importlib\", \"builtins\", \"__builtin__\", \"os\", \"subprocess\"}","tryCatchPattern":"from haystack.core.errors import DeserializationError\ntry:\n    pipe = Pipeline.loads(yaml_str)\nexcept DeserializationError as e:\n    if \"import primitive\" in str(e):\n        pipe = Pipeline.loads(yaml_str, unsafe=True)  # only for trusted sources\n    else:\n        raise","preventionTips":["Never deserialize pipelines from untrusted sources","Prefer explicit component class references over import functions in serialized data","Review serialized files before loading hand-edited YAML","Use unsafe=True only after auditing the payload"],"tags":["serialization","security","deserialization"],"backgroundTag":"unsafe-deserialization-blocked","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}