{"record":{"id":"1edaf3276e255347","repo":"deepset-ai/haystack","slug":"refusing-to-deserialize-handle-it-resolves-to-1edaf3","errorCode":null,"errorMessage":"Refusing to deserialize '{handle}': it resolves to the builtin '{name}', which is blocked because it can be used to execute code, import modules, access the filesystem, or escape via attribute access. 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 the builtin '(.+?)', which is blocked because it can be used to execute code, import modules, access the filesystem, or escape via attribute access\\. 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":490,"sourceCode":"    Reject `resolved` if it is a builtin callable that is unsafe to resolve from serialized data.\n\n    Used by the callable-resolution path (`deserialize_callable`). Raises\n    :class:`DeserializationError` for the primitives in :data:`_DENIED_BUILTIN_NAMES`, which can\n    execute code, import modules, touch the filesystem, or escape via attribute/namespace access.\n    The block applies even though `builtins` is on the allowlist, because the allowlist is\n    module-granular. It is intentionally bypassed in `unsafe=True` mode, which disables all\n    deserialization safety checks by design.\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    if _is_denied_builtin(resolved):\n        name = getattr(resolved, \"__name__\", str(resolved))\n        raise DeserializationError(\n            f\"Refusing to deserialize '{handle}': it resolves to the builtin '{name}', which is \"\n            f\"blocked because it can be used to execute code, import modules, access the \"\n            f\"filesystem, or escape via attribute access. If you trust the source of this data, \"\n            f\"load it with unsafe=True to bypass deserialization safety checks.\"\n        )\n\n\ndef _check_not_denied_callable(resolved: object, handle: str) -> None:\n    \"\"\"\n    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","sourceCodeStart":472,"sourceCodeEnd":508,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/core/serialization_security.py#L472-L508","documentation":"Certain Python builtins (e.g. eval, exec, open, __import__, getattr) are denied during deserialization because resolving serialized handles to them enables code execution, filesystem access, or attribute-based escape. deserialize_callable() blocks them for untrusted data; trusted data can bypass with unsafe=True.","triggerScenarios":"Serialized callable strings such as 'builtins.eval', 'builtins.exec', 'builtins.open' appearing in untrusted pipeline data passed to deserialize_callable().","commonSituations":"Malicious pipeline files shared in the wild; pen-test payloads; mis-serialized configs where a callable was stored as a builtin name instead of a proper component method.","solutions":["Replace the builtin reference in the serialized data with the intended public callable/component.","If the data is trusted and the builtin is genuinely needed, load with unsafe=True.","Quarantine/inspect pipeline files from unknown sources before loading.","Ensure your components serialize callables as fully-qualified, non-builtin names."],"exampleFix":"// before\n\"callable\": \"builtins.eval\"\n\n// after (trusted data only)\nPipeline.loads(data, unsafe=True)  # or replace with the intended component callable","handlingStrategy":"try-catch","validationCode":"import re\nDENIED = {\"eval\", \"exec\", \"open\", \"__import__\", \"getattr\", \"compile\", \"input\"}\ndef references_denied_builtin(serialized: str) -> bool:\n    return any(f\"builtins.{n}\" in serialized or f\".{n}\" in serialized for n in DENIED)","typeGuard":null,"tryCatchPattern":"from haystack.core.errors import DeserializationError\ntry:\n    pipe = Pipeline.load(path)\nexcept DeserializationError as e:\n    if \"builtin\" in str(e):\n        raise  # verify data source before any unsafe=True bypass\nraise","preventionTips":["Never load untrusted pipeline data with unsafe=True","Treat builtin handles in serialized data as malicious or corrupt","Serialize callables as fully-qualified component methods, never builtins","Restrict pipeline file sources to trusted storage"],"tags":["security","deserialization","builtins-blocked"],"backgroundTag":"dangerous-builtin-deserialization-blocked","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}