{"record":{"id":"ea42591ee7720839","repo":"BerriAI/litellm","slug":"microsoft-purview-dlp-upstream-policy-evaluation","errorCode":null,"errorMessage":"Microsoft Purview DLP: upstream policy evaluation failed","messagePattern":"Microsoft Purview DLP: upstream policy evaluation failed","errorType":"http","errorClass":"HTTPException","httpStatus":502,"severity":"error","filePath":"litellm/proxy/guardrails/guardrail_hooks/microsoft_purview/purview_dlp.py","lineNumber":155,"sourceCode":"        except HTTPException:\n            status = \"guardrail_failed_to_respond\"\n            raise\n        except httpx.HTTPStatusError as exc:\n            # Preserve the upstream Graph API status code (e.g. 429, 503) so\n            # callers can distinguish a transient infrastructure error from a\n            # DLP policy block (signaled separately as HTTP 400 below) and can\n            # implement retry-after handling on rate limits.  401/403 upstream\n            # responses indicate a proxy-side credential / consent problem the\n            # caller can do nothing about, so they are mapped to 502.\n            status = \"guardrail_failed_to_respond\"\n            if block_on_violation:\n                upstream_status: Final = exc.response.status_code\n                client_status: Final = 502 if upstream_status in (401, 403) else upstream_status\n                headers: dict[str, str] | None = None\n                retry_after: Final = exc.response.headers.get(\"retry-after\")\n                if retry_after:\n                    headers = {\"Retry-After\": retry_after}\n                raise HTTPException(\n                    status_code=client_status,\n                    detail={\n                        \"error\": \"Microsoft Purview DLP: upstream policy evaluation failed\",\n                        \"activity\": activity,\n                        \"upstream_status\": upstream_status,\n                        \"exception\": str(exc),\n                    },\n                    headers=headers,\n                ) from exc\n            verbose_proxy_logger.warning(\n                \"Purview DLP: API/network error in logging-only mode (not re-raised): %s\",\n                exc,\n            )\n        except Exception as exc:\n            status = \"guardrail_failed_to_respond\"\n            if block_on_violation:\n                raise HTTPException(\n                    status_code=400,","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/guardrails/guardrail_hooks/microsoft_purview/purview_dlp.py#L137-L173","documentation":"Runtime HTTPException raised from the Purview guardrail hook when the upstream Microsoft Purview DLP policy-evaluation call returns an HTTP error status (httpx.HTTPStatusError). LiteLLM maps upstream 401/403 to client-facing 502 (a proxy-side credential/consent problem the caller cannot fix), passes other upstream statuses through, and forwards any Retry-After header. It only raises when block_on_violation/fail-closed mode is on; in logging-only mode the same failure is just logged and the request proceeds.","triggerScenarios":"Expired Entra client secret or wrong tenant/client_id → Purview returns 401 → client sees 502; missing Purview consent/DLP permissions → 403 → 502; Purview throttling → 429 passes through with Retry-After; Purview 5xx or 400 passes through verbatim","commonSituations":"Secret rotated in Azure but not in config.yaml; Purview DLP policies not onboarded or the app lacks the required API permissions; network egress blocked so the token or evaluation call fails; intermittent 429s under load","solutions":["For 502 responses: re-check tenant_id/client_id/client_secret in the guardrail config, rotate the secret if expired, and verify the app registration's Purview API permissions + admin consent","For 429: honor the forwarded Retry-After header, add jittered client-side retry, and reduce request rate or scale Purview quota","For passthrough 5xx: check Azure status / retry idempotent requests","Temporarily set block_on_violation/log-only mode to keep traffic flowing while diagnosing, then re-enable fail-closed"],"exampleFix":"# before (client, no retry-aware handling)\nresp = client.chat.completions.create(model=\"gpt-4o\", messages=msgs)\n\n# after: retry on 429/5xx honoring Retry-After\nimport time, httpx\nfor attempt in range(5):\n    try:\n        resp = client.chat.completions.create(model=\"gpt-4o\", messages=msgs)\n        break\n    except httpx.HTTPStatusError as e:\n        sc = e.response.status_code\n        if sc in (429, 500, 502, 503) and attempt < 4:\n            time.sleep(float(e.response.headers.get(\"retry-after\", 2 ** attempt)))\n            continue\n        raise","handlingStrategy":"retry","validationCode":"# Before relying on Purview at runtime, smoke-test the credential path once at startup\nimport httpx\n\ndef purview_credentials_reachable(cfg) -> bool:\n    # token fetch exercises tenant/client/secret without touching DLP policy\n    r = httpx.post(\n        f\"https://login.microsoftonline.com/{cfg['tenant_id']}/oauth2/v2.0/token\",\n        data={\"client_id\": cfg[\"client_id\"], \"client_secret\": cfg[\"client_secret\"],\n              \"grant_type\": \"client_credentials\", \"scope\": \"https://purview.azure.net/.default\"},\n    )\n    return r.status_code == 200","typeGuard":null,"tryCatchPattern":"# Client-side: classify by mapped status\n# 502  -> upstream 401/403, proxy credential problem: do NOT retry, alert operator\n# 429  -> retry honoring Retry-After (the proxy forwards it)\n# 5xx  -> retry with backoff\nfor attempt in range(5):\n    try:\n        out = call_llm(payload)\n        break\n    except httpx.HTTPStatusError as e:\n        sc = e.response.status_code\n        if sc == 502 and \"Purview\" in e.response.text:\n            alert(\"purview credentials broken\"); raise\n        if sc in (429, 500, 502, 503) and attempt < 4:\n            time.sleep(float(e.response.headers.get(\"retry-after\", 2 ** attempt))); continue\n        raise","preventionTips":["Monitor Purview guardrail failure rates by upstream_status; alert on 401/403->502 immediately","Keep the Entra secret in a rotation pipeline with expiry alerts well before it lapses","Decide explicitly per deployment whether guardrail failure should block (fail closed) or log (logging-only) and test that path"],"tags":["microsoft-purview","guardrails","azure-ad","upstream","http","retry-after"],"backgroundTag":"upstream-api-error","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}