{"record":{"id":"78897cd2928b2498","repo":"BerriAI/litellm","slug":"microsoft-purview-dlp-no-proxy-authenticated-user","errorCode":null,"errorMessage":"Microsoft Purview DLP: No proxy-authenticated user identity; bind user_id to the API key (caller-supplied metadata cannot be used for blocking DLP)","messagePattern":"Microsoft Purview DLP: No proxy-authenticated user identity; bind user_id to the API key \\(caller-supplied metadata cannot be used for blocking DLP\\)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"litellm/proxy/guardrails/guardrail_hooks/microsoft_purview/purview_dlp.py","lineNumber":360,"sourceCode":"        user_api_key_dict: Any,\n    ) -> str:\n        \"\"\"Resolve user ID for blocking (pre_call / post_call) DLP hooks.\n\n        Uses only trusted proxy-authenticated sources (``_resolve_trusted_user_id``).\n        Caller-supplied ``UserAPIKeyAuth.end_user_id`` (from request ``user``,\n        ``metadata.user_id``, ``safety_identifier``, etc.) and\n        ``metadata[user_id_field]`` are rejected (fail closed) because they can\n        impersonate another Entra user's Purview policy.\n\n        Raises ``HTTPException`` when no API-key-bound ``user_id`` exists or when\n        only caller-influenceable identity fields are available (fail closed).\n        \"\"\"\n        trusted_id: Final = self._resolve_trusted_user_id(data, user_api_key_dict)\n        if trusted_id:\n            return trusted_id\n\n        if self._resolve_user_id(data, user_api_key_dict):\n            raise HTTPException(\n                status_code=400,\n                detail={\n                    \"error\": (\n                        \"Microsoft Purview DLP: No proxy-authenticated user identity; \"\n                        \"bind user_id to the API key (caller-supplied metadata cannot \"\n                        \"be used for blocking DLP)\"\n                    ),\n                },\n            )\n\n        raise HTTPException(\n            status_code=400,\n            detail={\n                \"error\": (\n                    \"Microsoft Purview DLP: No proxy-authenticated user identity; \"\n                    \"bind user_id to the API key for blocking DLP\"\n                ),\n            },","sourceCodeStart":342,"sourceCodeEnd":378,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/guardrails/guardrail_hooks/microsoft_purview/purview_dlp.py#L342-L378","documentation":"Fail-closed HTTPException from Purview blocking-mode identity resolution. A user identity WAS found, but only via caller-influenceable fields (metadata.user_id, safety_identifier, per-request metadata[user_id_field]). Because those let any caller impersonate another Entra user's Purview policy, blocking DLP refuses them and demands an identity bound to the API key on the proxy side.","triggerScenarios":"Client sends 'user' or metadata.user_id in the /chat/completions body while the virtual key used has no bound user; per-request user param set but the key was created without user_id; only header-injected metadata supplies identity","commonSituations":"Migrating from another guardrail that trusted request-supplied user ids; teams relying on 'user' field for attribution suddenly adding Purview; SSO-less deployments where every caller shares one admin key","solutions":["Bind the user to the virtual key at key-creation time: POST /key/generate with user_id (or /key/update for existing keys)","With SSO/OIDC auth enabled, send requests with the logged-in session so user identity comes from proxy authentication, not the body","After binding, stop relying on body-supplied user fields for these requests — they are deliberately ignored for blocking DLP"],"exampleFix":"# before: identity only in request body\nclient = OpenAI(api_key=VIRTUAL_KEY)\nclient.chat.completions.create(model=\"gpt-4o\", messages=msgs, user=\"alice@corp.com\")\n\n# after: bind identity to the key, send plain request\n# curl -X POST $PROXY/key/generate -H \"Authorization: Bearer $ADMIN\" \\\n#   -d '{\"user_id\": \"alice@corp.com\", \"models\": [\"gpt-4o\"], ...}'\nclient = OpenAI(api_key=ALICE_BOUND_KEY)\nclient.chat.completions.create(model=\"gpt-4o\", messages=msgs)","handlingStrategy":"validation","validationCode":"# Admin-side precheck: does this virtual key carry a bound user?\nimport httpx\n\ndef key_has_bound_user(proxy_url: str, key: str, admin_key: str) -> bool:\n    info = httpx.get(\n        f\"{proxy_url}/key/info\", params={\"key\": key},\n        headers={\"Authorization\": f\"Bearer {admin_key}\"},\n    ).json()\n    return bool(info.get(\"key_info\", {}).get(\"user_id\"))","typeGuard":null,"tryCatchPattern":"try:\n    r = guarded_client.chat.completions.create(**params)\nexcept BadRequestError as e:\n    if \"bind user_id to the API key\" in str(e):\n        # stop sending body-level user ids; provision a bound key instead\n        raise ProvisioningError(\"route requires key-bound identity\")\n    raise","preventionTips":["Provision per-user virtual keys via /key/generate with user_id as part of onboarding, not as a reaction to this error","Never rely on request-body user/metadata fields for identity on guarded routes — they are intentionally distrusted","With SSO enabled, prefer session-authenticated calls so identity comes from proxy auth"],"tags":["microsoft-purview","guardrails","identity","authentication","fail-closed","impersonation"],"backgroundTag":"untrusted-identity-rejected","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}