deepset-ai/haystack · critical · DeserializationError
Refusing to deserialize '{handle}': it resolves to an import
Error message
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. What it means
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.
Source
Thrown at haystack/core/serialization_security.py:518
Reject `resolved` if it is an import primitive that is unsafe to resolve from serialized data.
Used by the callable-resolution path (`deserialize_callable`) as a companion to
:func:`_check_not_denied_builtin`. It blocks the non-builtin import primitives in
:data:`_DENIED_CALLABLE_OBJECTS` / :data:`_DENIED_CALLABLE_QUALNAMES` (e.g.
`importlib.import_module`, `haystack.utils.type_serialization.thread_safe_import`), which are
functionally equivalent to the already-denied builtin `__import__` and can load any module as a
gateway to code execution. 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.
"""
if _is_unsafe_deserialization():
return
ident = (getattr(resolved, "__module__", ""), getattr(resolved, "__qualname__", ""))
if any(resolved is denied for denied in _DENIED_CALLABLE_OBJECTS) or ident in _DENIED_CALLABLE_QUALNAMES:
raise DeserializationError(
f"Refusing to deserialize '{handle}': it resolves to an import primitive that can load "
f"arbitrary modules (equivalent to the blocked builtin '__import__'), which is a gateway "
f"to code execution. If you trust the source of this data, load it with unsafe=True to "
f"bypass deserialization safety checks."
)
def _check_builtin_is_type(resolved: object, handle: str) -> None:
"""
Reject a `builtins` member resolved in a type/class context that is not a `type`.
Used by `deserialize_type` and `import_class_by_name`, which resolve type annotations and class
references — always classes. Requiring the resolved `builtins` member to be a `type` lets every
builtin type through (e.g. `str`, `memoryview`) while rejecting every builtin *function* (e.g.
`eval`, `exec`, `getattr`), with no denylist to maintain. Bypassed in `unsafe=True` mode.
:param resolved:
The object resolved from the serialized handle.View on GitHub (pinned to e318778c9b)
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
Example fix
// before: serialized handle references an import primitive
"init_parameters": {"type": "importlib.import_module", ...}
// after: reference the concrete class directly, or load trusted data unsafely
"init_parameters": {"type": "haystack.components.builders.prompt_builder.PromptBuilder"}
data = Pipeline.loads(yaml_str, unsafe=True) # only if source is trusted Defensive patterns
Strategy: validation
Validate before calling
import re
DENIED = ("importlib.import_module", "builtins.__import__", "__builtin__.__import__")
def uses_import_primitive(serialized_yaml: str) -> bool:
return any(h in serialized_yaml for h in DENIED) Type guard
def is_safe_handle(handle: str) -> bool:
root = handle.split(".")[0]
return root not in {"importlib", "builtins", "__builtin__", "os", "subprocess"} Try / catch
from haystack.core.errors import DeserializationError
try:
pipe = Pipeline.loads(yaml_str)
except DeserializationError as e:
if "import primitive" in str(e):
pipe = Pipeline.loads(yaml_str, unsafe=True) # only for trusted sources
else:
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: Hand-edited or third-party serialized pipeline files that reference import functions; pipelines exported from older/other versions; tampered or untrusted pipeline payloads.
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/b8f578e8e4deeb44.
Report an issue: GitHub.