deepset-ai/haystack · critical · DeserializationError
Refusing to deserialize '{handle}': it resolves to the built
Error message
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. What it means
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.
Source
Thrown at haystack/core/serialization_security.py:490
Reject `resolved` if it is a builtin callable that is unsafe to resolve from serialized data.
Used by the callable-resolution path (`deserialize_callable`). Raises
:class:`DeserializationError` for the primitives in :data:`_DENIED_BUILTIN_NAMES`, which can
execute code, import modules, touch the filesystem, or escape via attribute/namespace access.
The block applies even though `builtins` is on the allowlist, because the allowlist is
module-granular. It is intentionally bypassed in `unsafe=True` mode, which disables all
deserialization safety checks by design.
: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
if _is_denied_builtin(resolved):
name = getattr(resolved, "__name__", str(resolved))
raise DeserializationError(
f"Refusing to deserialize '{handle}': it resolves to the builtin '{name}', which is "
f"blocked because it can be used to execute code, import modules, access the "
f"filesystem, or escape via attribute access. If you trust the source of this data, "
f"load it with unsafe=True to bypass deserialization safety checks."
)
def _check_not_denied_callable(resolved: object, handle: str) -> None:
"""
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.
View on GitHub (pinned to e318778c9b)
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.
Example fix
// before "callable": "builtins.eval" // after (trusted data only) Pipeline.loads(data, unsafe=True) # or replace with the intended component callable
Defensive patterns
Strategy: try-catch
Validate before calling
import re
DENIED = {"eval", "exec", "open", "__import__", "getattr", "compile", "input"}
def references_denied_builtin(serialized: str) -> bool:
return any(f"builtins.{n}" in serialized or f".{n}" in serialized for n in DENIED) Try / catch
from haystack.core.errors import DeserializationError
try:
pipe = Pipeline.load(path)
except DeserializationError as e:
if "builtin" in str(e):
raise # verify data source before any unsafe=True bypass
raise Prevention
- 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
When it happens
Trigger: Serialized callable strings such as 'builtins.eval', 'builtins.exec', 'builtins.open' appearing in untrusted pipeline data passed to deserialize_callable().
Common situations: 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.
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
- Refusing to deserialize '{handle}': it resolves to '{name}',
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/1edaf3276e255347.
Report an issue: GitHub.