{"record":{"id":"92dfbe21907da1db","repo":"deepset-ai/haystack","slug":"refusing-to-deserialize-a-conditionalrouter-with-c","errorCode":null,"errorMessage":"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).","messagePattern":"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\\)\\.","errorType":"exception","errorClass":"DeserializationError","httpStatus":null,"severity":"error","filePath":"haystack/components/routers/conditional_router.py","lineNumber":380,"sourceCode":"        :param data:\n            The dictionary to deserialize from.\n        :returns:\n            The deserialized component.\n        \"\"\"\n        init_params = data.get(\"init_parameters\", {})\n\n        # `unsafe=True` swaps the Jinja sandbox for a NativeEnvironment that executes arbitrary code.\n        # Honor it from serialized data only when the whole pipeline is being loaded in unsafe mode;\n        # otherwise a hostile pipeline could disable the sandbox on its own in default safe mode.\n        if init_params.get(\"unsafe\") and not _is_unsafe_deserialization():\n            raise DeserializationError(\n                \"Refusing to deserialize a ConditionalRouter with unsafe=True while loading in safe mode. \"\n                \"If you trust the source of this data, load it with Pipeline.load(..., unsafe=True).\"\n            )\n\n        custom_filters = init_params.get(\"custom_filters\", {})\n        if custom_filters and not _is_unsafe_deserialization():\n            raise DeserializationError(\n                \"Refusing to deserialize a ConditionalRouter with custom filters while loading in safe mode. \"\n                \"Custom filters are arbitrary callables that can execute during pipeline loading. \"\n                \"If you trust the source of this data, load it with Pipeline.load(..., unsafe=True).\"\n            )\n\n        routes = init_params.get(\"routes\")\n        for route in routes:\n            # output_type needs to be deserialized from a string to a type\n            if isinstance(route[\"output_type\"], list):\n                route[\"output_type\"] = [deserialize_type(t) for t in route[\"output_type\"]]\n            else:\n                route[\"output_type\"] = deserialize_type(route[\"output_type\"])\n\n        # Since the custom_filters are typed as optional in the init signature, we catch the\n        # case where they are not present in the serialized data and set them to an empty dict.\n        if custom_filters is not None:\n            for name, filter_func in custom_filters.items():\n                init_params[\"custom_filters\"][name] = deserialize_callable(filter_func) if filter_func else None","sourceCodeStart":362,"sourceCodeEnd":398,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/components/routers/conditional_router.py#L362-L398","documentation":"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.","triggerScenarios":"Pipeline.load()/from_dict() on YAML/JSON whose init_parameters.custom_filters is non-empty, without unsafe=True on the load call.","commonSituations":"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.","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."],"exampleFix":"// before\npipe = Pipeline.loads(saved_yaml)\n// after\npipe = Pipeline.loads(saved_yaml, unsafe=True)  # custom_filters require explicit trust","handlingStrategy":"validation","validationCode":"data = yaml.safe_load(open(\"pipe.yaml\"))\nfor c in data[\"components\"].values():\n    if c.get(\"init_parameters\", {}).get(\"custom_filters\"):\n        raise RuntimeError(\"pipeline declares custom_filters; load with unsafe=True only if trusted\")","typeGuard":"def has_custom_filters(component_data: dict) -> bool:\n    return bool(component_data.get(\"init_parameters\", {}).get(\"custom_filters\"))","tryCatchPattern":"try:\n    pipe = Pipeline.load(\"pipe.yaml\")\nexcept DeserializationError:\n    if audited(\"pipe.yaml\"):\n        pipe = Pipeline.load(\"pipe.yaml\", unsafe=True)\n    else:\n        raise","preventionTips":["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."],"tags":["deserialization","security","jinja","safe-mode"],"backgroundTag":"unsafe-deserialization-blocked","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}