BerriAI/litellm · error · ValueError

Azure Document Intelligence: rejected polling URL ({ssrf_err

Error message

Azure Document Intelligence: rejected polling URL ({ssrf_err})

What it means

Security guard (VERIA-51): the Operation-Location header returned by Azure must be same-origin with the original request URL, otherwise LiteLLM rejects the polling URL. Without this check, the Ocp-Apim-Subscription-Key header would be sent to whatever host appears in the attacker-controlled (or misconfigured-upstream) redirect, leaking the subscription key. An SSRFError from assert_same_origin is wrapped in this ValueError.

Source

Thrown at litellm/llms/azure_ai/ocr/document_intelligence/transformation.py:592

            if status == "succeeded":
                return response
            elif status == "running":
                # Wait before polling again
                retry_after = self._get_retry_after(response=response)
                await asyncio.sleep(retry_after)

    def _get_polling_target(self, raw_response: httpx.Response) -> tuple[str, dict[str, str]]:
        operation_url: Final = raw_response.headers.get("Operation-Location")
        if not operation_url:
            raise ValueError("Azure Document Intelligence returned 202 but no Operation-Location header found")

        # Reject cross-origin polling URLs — the auth headers
        # below would otherwise leak to whatever URL the upstream
        # (or an attacker-controlled upstream) returns. VERIA-51.
        try:
            assert_same_origin(operation_url, str(raw_response.request.url))
        except SSRFError as ssrf_err:
            raise ValueError(f"Azure Document Intelligence: rejected polling URL ({ssrf_err})")

        poll_headers = {"Ocp-Apim-Subscription-Key": raw_response.request.headers.get("Ocp-Apim-Subscription-Key", "")}
        return operation_url, poll_headers

    def _transform_completed_response(self, model: str, raw_response: httpx.Response) -> OCRResponse:
        """
        Transform a completed Azure Document Intelligence analyze operation
        into the Mistral OCR response shape, preserving Azure-native
        `analyzeResult` fields (`content`, `tables`, `keyValuePairs`) as
        top-level response fields.
        """
        operation: Final = AzureDocumentIntelligenceOperation.model_validate(raw_response.json())

        verbose_logger.debug("Azure Document Intelligence response status: %s", operation.status)

        if operation.status != "succeeded":
            raise ValueError(f"Azure Document Intelligence analysis failed with status: {operation.status}")

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Make the Operation-Location your endpoint returns use the exact same origin (scheme+host+port) as the analyze request URL — essential for mock servers.
  2. Point api_base directly at the real Azure resource so Azure's own same-origin header passes the check.
  3. Audit any intermediary that rewrites Location/Operation-Location headers and disable that rewrite.
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse

def polling_url_is_safe(analyze_url: str, operation_url: str) -> bool:
    a, b = urlparse(analyze_url), urlparse(operation_url)
    return (a.scheme, a.hostname, a.port) == (b.scheme, b.hostname, b.port)

Try / catch

try:
    resp = litellm.aocr_document(model=m, document=doc)
except ValueError as e:
    if "rejected polling URL" in str(e):
        raise SecurityError("cross-origin Operation-Location — check proxy/mock config") from e
    raise

Prevention

When it happens

Trigger: A DNS rebinding or header-injection attack making Operation-Location point at a foreign host; a misconfigured proxy rewriting the header to a different origin; pointing api_base at a mock/test server that echoes a cross-origin operation URL. Any host/port/scheme mismatch versus the original request URL triggers it.

Common situations: Local development with a mock DI server that returns absolute URLs on another port; a gateway that rewrites Location-family headers; genuinely malicious upstreams when api_base is user-controlled.

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/a20ae2cabb618889. Report an issue: GitHub.