Skip to main content

Microsoft Purview Guardrail

LiteLLM supports Microsoft Purview DLP policies via the Microsoft Graph processContent API.

Supported modes​

ModeWhat it does
pre_callEvaluates the user prompt against DLP policies before the LLM call. Blocks if a restrictAccess/block policy action fires.
post_callEvaluates the LLM response against DLP policies. Blocks if a restrictAccess/block policy action fires.
logging_onlySends both prompt and response to Purview for audit. Never blocks the request.

Prerequisites​

  1. An Entra app registration with the following Microsoft Graph application permissions:

    • InformationProtectionPolicy.Read.All
    • ProtectionScopes.Compute.User
    • Content.Process.User
  2. A DLP policy in Microsoft Purview targeting your app registration's client_id as a Protected App. Without an active policy, policyActions in the API response will always be empty.

  3. Entra user object IDs — the LiteLLM API key (or JWT) used for the request must have a user_id bound to it that is the Entra object ID of the end-user (not a username or email). Blocking hooks (pre_call / post_call) reject the request with HTTP 400 when the key has no bound user ID; the logging-only hook skips the audit instead.

Quick Start​

1. Register your app in Entra​

# Create app registration and note the appId (client_id) and tenantId
az ad app create --display-name "LiteLLM-Purview"
az ad sp create --id <appId>

# Create a client secret
az ad app credential reset --id <appId> --append

Grant the permissions listed above in the Azure portal under App registrations → API permissions, then Grant admin consent.

2. Define the guardrail in config.yaml​

model_list:
- model_name: gpt-5.6-terra
litellm_params:
model: openai/gpt-5.6-terra
api_key: os.environ/OPENAI_API_KEY

guardrails:
- guardrail_name: purview-prompt-dlp
litellm_params:
guardrail: microsoft_purview
mode: pre_call
api_key: os.environ/AZURE_CLIENT_SECRET # client_secret
tenant_id: os.environ/AZURE_TENANT_ID
client_id: os.environ/AZURE_CLIENT_ID
default_on: true

3. Start LiteLLM Gateway​

litellm --config config.yaml --detailed_debug

4. Test request​

Send the request with a LiteLLM key whose bound user_id is the Entra object ID of the end-user (see User ID resolution). metadata.user_id is shown for reference but is not used to pick the Purview identity:

curl -X POST http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $LITELLM_API_KEY" \
-d '{
"model": "gpt-5.6-terra",
"messages": [{"role": "user", "content": "Hello, what is the capital of France?"}],
"metadata": {"user_id": "<entra-user-object-id>"}
}'

When a DLP policy blocks the request, LiteLLM returns:

{
"error": {
"status_code": 400,
"message": {
"error": "Microsoft Purview DLP: Content blocked by policy",
"activity": "uploadText"
}
}
}

Supported Params​

ParamTypeRequiredDescription
guardrailstrYesMust be "microsoft_purview"
modestrYespre_call, post_call, or logging_only
api_keystrYesEntra app client secret (can use os.environ/VAR)
tenant_idstrYesEntra tenant ID
client_idstrYesEntra app registration client ID (also used as the Protected App identifier in Purview)
default_onboolNoRun this guardrail for every request. Default: false
purview_app_namestrNoApp name reported to Purview in processContent. Default: "LiteLLM"
user_id_fieldstrNoCaller-supplied metadata field. Not used as the identity sent to Purview; only detected to return a more specific error message (see User ID resolution). Default: "user_id"

User ID resolution​

The Entra object ID used for Purview protectionScopes / processContent comes only from user_api_key_dict.user_id, the user bound to the LiteLLM API key or JWT. Caller-influenceable sources (user_api_key_dict.end_user_id, metadata["user_api_key_user_id"], and metadata[user_id_field]) are intentionally ignored so a client cannot run the DLP check under another Entra user's policy.

If the key has no bound user_id, the blocking hooks (pre_call / post_call) fail closed and reject the request with HTTP 400. When only caller-supplied identity fields are present, the error message notes that caller-supplied metadata cannot be used for blocking DLP.

The logging-only hook reads the proxy-injected user_api_key_user_id from litellm_params.metadata (populated from the same key-bound user_id). If it is absent, the audit is skipped rather than misattributed.

Enabling per request​

When default_on: false, you can opt individual requests in or out:

curl -X POST http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $LITELLM_API_KEY" \
-d '{
"model": "gpt-5.6-terra",
"messages": [{"role": "user", "content": "Hello"}],
"guardrails": ["purview-prompt-dlp"],
"metadata": {"user_id": "<entra-user-object-id>"}
}'

How it works​

  1. Token acquisition — The guardrail uses the OAuth2 client credentials grant to get a Microsoft Graph bearer token. Tokens are cached until 60 seconds before expiry.

  2. Protection scope computation — Before each DLP check, the guardrail calls protectionScopes/compute for the user to retrieve the ETag representing current policy state. Results are cached per user for 1 hour (per Microsoft's recommendation). If the processContent response indicates policies have changed (protectionScopeState: modified), the cache is invalidated.

  3. Content evaluation — The guardrail calls processContent with the text and an activityMetadata.activity of uploadText (prompts) or downloadText (responses). Chat (/v1/chat/completions): pre-call concatenates string content from every message (all roles); post-call uses assistant message.content from every chat choice when n > 1. Legacy text completions (/v1/completions): pre-call uses the prompt field (string or list of strings; token-id-only prompts are skipped); post-call uses text from every TextChoices entry.

  4. Block decision — If any policyActions entry has @odata.type containing restrictAccessAction and restrictionAction: "block", the guardrail raises an HTTP 400.

  5. Audit logging — In all modes, guardrail results are recorded in metadata.standard_logging_guardrail_information and flow to configured observability backends (Langfuse, Datadog, OTEL, etc.).

Further Reading​