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 None

View on GitHub (pinned to e318778c9b)

Solutions

  1. If the source is trusted, load with Pipeline.load(..., unsafe=True) or Pipeline.loads(..., unsafe=True).
  2. Remove custom_filters from the serialized pipeline and register/import them in your own code before running.
  3. 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

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


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/92dfbe21907da1db. Report an issue: GitHub.