{"record":{"id":"3fbb8c9d1474bcff","repo":"BerriAI/litellm","slug":"cannot-route-sensitive-data-without-a-session-id","errorCode":null,"errorMessage":"Cannot route sensitive data without a session_id. Ensure the request includes a session_id in metadata or headers.","messagePattern":"Cannot route sensitive data without a session_id\\. Ensure the request includes a session_id in metadata or headers\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/integrations/custom_guardrail.py","lineNumber":274,"sourceCode":"        to route to an on-premise model instead of blocking.\n\n        The exception will reroute this request to the specified model. When\n        sticky_session_routing is enabled (the default), it also stores the\n        routing decision so subsequent requests in this session reuse the model.\n\n        Args:\n            route_to_model: The model to route this request (and session) to\n            request_data: The original request data dictionary\n            detection_info: Optional non-sensitive detection metadata (e.g. matched\n                entity types, rule ids, scores). This is surfaced in request metadata\n                and logs, so it must not contain the raw detected sensitive values.\n\n        Raises:\n            SensitiveDataRouteException: Always raises to trigger rerouting\n        \"\"\"\n        session_id: Final = self._get_session_id_from_request_data(request_data)\n        if not session_id:\n            raise ValueError(\n                \"Cannot route sensitive data without a session_id. \"\n                \"Ensure the request includes a session_id in metadata or headers.\"\n            )\n\n        raise SensitiveDataRouteException(\n            route_to_model=route_to_model,\n            session_id=session_id,\n            guardrail_name=self.guardrail_name,\n            detection_info=detection_info,\n            sticky_session_routing=self.sticky_session_routing,\n        )\n\n    def _get_session_id_from_request_data(self, request_data: dict[str, Any]) -> str | None:\n        \"\"\"Extract session_id from request data.\"\"\"\n        return get_session_id_from_request_data(request_data)\n\n    @staticmethod\n    def _scanned_text_hash(text: str) -> str:","sourceCodeStart":256,"sourceCodeEnd":292,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/integrations/custom_guardrail.py#L256-L292","documentation":"CustomGuardrail.raise_sensitive_data_route_exception() tries to build a SensitiveDataRouteException so the proxy can reroute the request to a safer model, but rerouting requires a session_id to keep the user's session sticky on the target model. The method calls _get_session_id_from_request_data() and, when it finds no session_id in the request's metadata or headers, raises this ValueError. Internally the proxy normally catches it and converts it to a blocking GuardrailRaisedException (error 361), but if you call this method directly you get the raw ValueError.","triggerScenarios":"A guardrail with sensitive_data_route_to_model set detects PII and calls raise_sensitive_data_route_exception on a request where _get_session_id_from_request_data(request_data) returns None — i.e. no 'session_id' key in metadata (or litellm_metadata) and no x-litellm-session-id style header. Also hit when a custom guardrail subclass invokes this API from its own hooks on raw request dicts that never passed through the proxy session layer.","commonSituations":"Running bedrock/presidio/pii guardrails in 'route' mode via the LiteLLM proxy while clients call /chat/completions directly without a session_id; teams migrating from mask-on-detect to reroute-on-detect configs without adding session tracking; SDK calls that pass metadata but omit session_id.","solutions":["Include a session_id in the request: add 'session_id' to the 'metadata' field of the request body, or send it as a header the proxy copies into metadata","If you call raise_sensitive_data_route_exception yourself, pass request_data that carries metadata.session_id (e.g. request_data['metadata']['session_id'] = uuid)","If you cannot guarantee session_ids, remove sensitive_data_route_to_model / should_route_on_sensitive_data so the guardrail masks or blocks instead of routing"],"exampleFix":"# before\nresponse = litellm.completion(\n    model=\"gpt-4o\",\n    messages=[{\"role\": \"user\", \"content\": \"my SSN is 123-45-6789\"}],\n    metadata={\"guardrails\": [\"presidio pii mask\"]},\n)\n\n# after\nresponse = litellm.completion(\n    model=\"gpt-4o\",\n    messages=[{\"role\": \"user\", \"content\": \"my SSN is 123-45-6789\"}],\n    metadata={\"guardrails\": [\"presidio pii mask\"], \"session_id\": \"user-abc-session-1\"},\n)","handlingStrategy":"validation","validationCode":"def has_session_id(request_data: dict) -> bool:\n    metadata = request_data.get(\"metadata\") or {}\n    return bool(metadata.get(\"session_id\"))\n\nif guardrail.should_route_on_sensitive_data():\n    assert has_session_id(request_data), \"session_id required for sensitive-data routing\"","typeGuard":null,"tryCatchPattern":"try:\n    guardrail.raise_sensitive_data_route_exception(model, request_data, info)\nexcept ValueError as e:\n    # no session: decide explicitly — block, mask, or attach a session and retry\n    handle_missing_session(request_data, str(e))","preventionTips":["Standardize on always sending metadata.session_id with every proxied request","Wrap guardrail route calls in a helper that injects a session_id before invoking them","In tests, assert _get_session_id_from_request_data(request_data) is truthy before exercising route-mode guardrails"],"tags":["guardrail","pii","routing","session","litellm-proxy"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}