deepset-ai/haystack · error · DeserializationError
Refusing to deserialize a ConditionalRouter with custom filt
Error message
Refusing to deserialize a ConditionalRouter with custom filters while loading in safe mode. Custom filters are arbitrary callables that can execute during pipeline loading. If you trust the source of this data, load it with Pipeline.load(..., unsafe=True).
What it means
The serialized ConditionalRouter declares custom_filters, which are arbitrary Python callables that get re-imported and can execute arbitrary code during pipeline loading. In safe mode (no unsafe=True on Pipeline.load), Haystack raises DeserializationError to prevent code execution from untrusted data.
Source
Thrown at haystack/components/routers/conditional_router.py:380
:param data:
The dictionary to deserialize from.
:returns:
The deserialized component.
"""
init_params = data.get("init_parameters", {})
# `unsafe=True` swaps the Jinja sandbox for a NativeEnvironment that executes arbitrary code.
# Honor it from serialized data only when the whole pipeline is being loaded in unsafe mode;
# otherwise a hostile pipeline could disable the sandbox on its own in default safe mode.
if init_params.get("unsafe") and not _is_unsafe_deserialization():
raise DeserializationError(
"Refusing to deserialize a ConditionalRouter with unsafe=True while loading in safe mode. "
"If you trust the source of this data, load it with Pipeline.load(..., unsafe=True)."
)
custom_filters = init_params.get("custom_filters", {})
if custom_filters and not _is_unsafe_deserialization():
raise DeserializationError(
"Refusing to deserialize a ConditionalRouter with custom filters while loading in safe mode. "
"Custom filters are arbitrary callables that can execute during pipeline loading. "
"If you trust the source of this data, load it with Pipeline.load(..., unsafe=True)."
)
routes = init_params.get("routes")
for route in routes:
# output_type needs to be deserialized from a string to a type
if isinstance(route["output_type"], list):
route["output_type"] = [deserialize_type(t) for t in route["output_type"]]
else:
route["output_type"] = deserialize_type(route["output_type"])
# Since the custom_filters are typed as optional in the init signature, we catch the
# case where they are not present in the serialized data and set them to an empty dict.
if custom_filters is not None:
for name, filter_func in custom_filters.items():
init_params["custom_filters"][name] = deserialize_callable(filter_func) if filter_func else NoneView on GitHub (pinned to e318778c9b)
Solutions
- If the source is trusted, load with Pipeline.load(..., unsafe=True) or Pipeline.loads(..., unsafe=True).
- Remove custom_filters from the serialized pipeline and register/import them in your own code before running.
- Audit the callable paths listed under custom_filters in the YAML before trusting them.
Example fix
// before pipe = Pipeline.loads(saved_yaml) // after pipe = Pipeline.loads(saved_yaml, unsafe=True) # custom_filters require explicit trust
Defensive patterns
Strategy: validation
Validate before calling
data = yaml.safe_load(open("pipe.yaml"))
for c in data["components"].values():
if c.get("init_parameters", {}).get("custom_filters"):
raise RuntimeError("pipeline declares custom_filters; load with unsafe=True only if trusted") Type guard
def has_custom_filters(component_data: dict) -> bool:
return bool(component_data.get("init_parameters", {}).get("custom_filters")) Try / catch
try:
pipe = Pipeline.load("pipe.yaml")
except DeserializationError:
if audited("pipe.yaml"):
pipe = Pipeline.load("pipe.yaml", unsafe=True)
else:
raise Prevention
- Register custom filters in application code instead of relying on serialized custom_filters.
- Audit callable import paths in the YAML's custom_filters before trusting a pipeline.
- Only pass unsafe=True to Pipeline.load for sources you control.
When it happens
Trigger: Pipeline.load()/from_dict() on YAML/JSON whose init_parameters.custom_filters is non-empty, without unsafe=True on the load call.
Common situations: Pipelines saved with custom Jinja filters (e.g. a date-format filter) shared via repos, downloads, or chatbots; picking up teammate pipelines with filters registered at serialization time.
Related errors
- Refusing to deserialize a ConditionalRouter with unsafe=True
- Refusing to deserialize an OutputAdapter with unsafe=True wh
- Refusing to deserialize an OutputAdapter with custom filters
- Refusing to deserialize '{handle}': it resolves to '{name}',
- Refusing to deserialize '{handle}': it traverses into the in
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/92dfbe21907da1db.
Report an issue: GitHub.