deepset-ai/haystack · critical · DeserializationError

Refusing to deserialize an OutputAdapter with custom filters

Error message

Refusing to deserialize an OutputAdapter 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

OutputAdapter.from_dict rejects serialized pipelines that carry custom_filters, because custom filters are arbitrary Python callables embedded in serialized data and would execute during pipeline loading — a code-execution vector when loading untrusted files. Safe mode refuses; only unsafe loading accepts them.

Source

Thrown at haystack/components/converters/output_adapter.py:187

        :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 an OutputAdapter 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 an OutputAdapter 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)."
            )

        init_params["output_type"] = deserialize_type(init_params["output_type"])

        if custom_filters:
            init_params["custom_filters"] = {
                name: deserialize_callable(filter_func) if filter_func else None
                for name, filter_func in custom_filters.items()
            }
        return default_from_dict(cls, data)

View on GitHub (pinned to e318778c9b)

Solutions

  1. Load with Pipeline.loads(data, unsafe=True) only if you trust the pipeline source
  2. Recreate the OutputAdapter in code with the filters registered programmatically instead of loading them from the file
  3. Strip custom_filters from the YAML and re-add filters at runtime after loading

Example fix

// before
pipeline = Pipeline.loads(data_with_filters)
// after
pipeline = Pipeline.loads(sanitized_data)
adapter = pipeline.get_component("my_adapter")
adapter.custom_filters["my_filter"] = my_filter  # re-register locally
Defensive patterns

Strategy: try-catch

Validate before calling

import yaml
def has_custom_filters(path):
    data = yaml.safe_load(open(path))
    return any(
        c.get("type") == "OutputAdapter" and c.get("init_parameters", {}).get("custom_filters")
        for c in data.get("components", []).values()
    )

Try / catch

try:
    pipeline = Pipeline.load(path)
except DeserializationError as e:
    if "custom filters" in str(e):
        pipeline = rebuild_adapter_in_code(path)  # register filters locally
    else:
        raise

Prevention

When it happens

Trigger: Pipeline.load/loads in default safe mode on a pipeline file whose OutputAdapter init_parameters include a non-empty custom_filters dict.

Common situations: Sharing pipelines between teams where one side registered custom Jinja filters; stored pipeline artifacts that reference local filter functions; CI loading contributed pipelines.

Related errors


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