{"record":{"id":"9bf1ac0144e8d571","repo":"BerriAI/litellm","slug":"microsoft-purview-dlp-content-blocked-by-policy","errorCode":null,"errorMessage":"Microsoft Purview DLP: Content blocked by policy","messagePattern":"Microsoft Purview DLP: Content blocked by policy","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"litellm/proxy/guardrails/guardrail_hooks/microsoft_purview/purview_dlp.py","lineNumber":197,"sourceCode":"                ) 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        finally:\n            end_time: Final = datetime.now()\n            self.add_standard_logging_guardrail_information_to_request_data(\n                guardrail_provider=self.guardrail_provider,\n                guardrail_json_response=response,\n                request_data=request_data,\n                guardrail_status=status,\n                start_time=start_time.timestamp(),\n                end_time=end_time.timestamp(),\n                duration=(end_time - start_time).total_seconds(),\n            )\n\n        if block_on_violation and status == \"guardrail_intervened\":\n            raise HTTPException(\n                status_code=400,\n                detail={\n                    \"error\": \"Microsoft Purview DLP: Content blocked by policy\",\n                    \"activity\": activity,\n                },\n            )\n\n        return response\n\n    @staticmethod\n    def _extract_responses_api_function_call_args(result: Any) -> list[str]:\n        \"\"\"Return tool-call argument strings from a ``ResponsesAPIResponse.output``.\n\n        ``ResponsesAPIResponse.output_text`` only aggregates ``output_text``\n        content blocks and ignores ``function_call`` items.  Model-generated\n        tool-call arguments can themselves contain sensitive data, so we\n        extract them explicitly to keep DLP coverage consistent with the\n        chat (``ModelResponse``) path.","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/guardrails/guardrail_hooks/microsoft_purview/purview_dlp.py#L179-L215","documentation":"Intentional policy block: the Purview DLP evaluation succeeded and returned 'guardrail_intervened' (matched sensitive content), and the guardrail runs with block_on_violation, so the proxy rejects the request/response with HTTP 400 before the LLM output (or after it, for post-call hooks) is delivered. This is the guardrail working as designed, not a malfunction.","triggerScenarios":"A pre-call request whose prompt/text contains content matching a Purview DLP policy (e.g. credit card numbers, classified strings); a post-call hook scanning the model response that trips a policy; guardrail configured with mode pre_call/post_call and default blocking semantics","commonSituations":"Internal apps red-teaming prompts with PII; a Purview admin tightened policies so previously-allowed traffic now trips; test suites sending synthetic sensitive data that matches default rules","solutions":["Retrieve the matched policy details from Purview (activity id is included in the 'activity' field of the error detail) and either sanitize the input or request a policy exception","If the content is legitimately allowed, have the Purview admin tune the DLP rule/threshold","If you only want visibility, configure the guardrail for logging-only instead of blocking — but understand that removes the protection","On the client: catch the 400 and surface a 'content blocked by data-loss-prevention policy' message to the end user"],"exampleFix":"# before: raw error surfaces to users\ntry:\n    r = client.chat.completions.create(**params)\nexcept Exception as e:\n    raise RuntimeError(str(e))\n\n# after: recognize the DLP block and degrade gracefully\nfrom openai import BadRequestError\ntry:\n    r = client.chat.completions.create(**params)\nexcept BadRequestError as e:\n    detail = e.response.json().get(\"detail\", {})\n    if isinstance(detail, dict) and detail.get(\"error\") == \"Microsoft Purview DLP: Content blocked by policy\":\n        return \"Your message was blocked by the data-loss-prevention policy.\"\n    raise","handlingStrategy":"try-catch","validationCode":"null  # policy evaluation is server-side; nothing meaningful to pre-validate without duplicating Purview rules","typeGuard":null,"tryCatchPattern":"from openai import BadRequestError\n\ntry:\n    r = client.chat.completions.create(**params)\nexcept BadRequestError as e:\n    body = e.response.json()[\"detail\"]\n    if body.get(\"error\") == \"Microsoft Purview DLP: Content blocked by policy\":\n        activity = body.get(\"activity\")  # correlation id for the Purview admin\n        return user_friendly_block_page(activity)\n    raise","preventionTips":["Map the 400 to a friendly 'content blocked by DLP' UX instead of surfacing raw error JSON","Log the activity id so users can reference it when requesting policy exceptions","Track block rates per key/team to catch over-broad policies early"],"tags":["microsoft-purview","dlp","content-filter","policy-block","http-400"],"backgroundTag":"content-blocked-by-policy","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}