Skip to content

fix: redaction headers ignored when sent via proxy#20740

Open
tsachis wants to merge 2 commits intoBerriAI:mainfrom
tsachis:fix/redact-header-proxy-fallback
Open

fix: redaction headers ignored when sent via proxy#20740
tsachis wants to merge 2 commits intoBerriAI:mainfrom
tsachis:fix/redact-header-proxy-fallback

Conversation

@tsachis
Copy link
Contributor

@tsachis tsachis commented Feb 9, 2026

Summary

  • Fix x-litellm-enable-message-redaction and litellm-enable-message-redaction headers being silently ignored when requests go through the proxy
  • Add fallback in should_redact_message_logging() to read from metadata when litellm_metadata is None
  • litellm-disable-message-redaction header is also fixed by this change

Root cause

get_litellm_params() always includes "litellm_metadata" as a key (even when None), so get_metadata_variable_name_from_kwargs() always returns "litellm_metadata". The redaction function then reads None instead of the actual metadata dict that contains the headers.

Fix

In should_redact_message_logging(), when the resolved metadata field is not a dict (i.e. None), fall back to litellm_params.get("metadata", {}).

Fixes #20739

Test plan

  • Added 8 unit tests in tests/test_litellm/litellm_core_utils/test_redact_messages.py covering:
    • Enable redaction via x-litellm-enable-message-redaction header (proxy flow)
    • Enable redaction via litellm-enable-message-redaction header (proxy flow)
    • Disable redaction via litellm-disable-message-redaction header (proxy flow)
    • Headers in litellm_metadata (SDK direct call)
    • No headers defaults to global setting
    • Dynamic params precedence
  • Existing tests pass: test_redact_msgs_from_logs, test_redact_msgs_from_logs_with_dynamic_params

🤖 Generated with Claude Code

When requests go through the proxy, `litellm_params["litellm_metadata"]`
is always set (even when `None`), so `get_metadata_variable_name_from_kwargs`
always returns "litellm_metadata". The redaction code then reads `None`
instead of the actual metadata dict that contains the headers.

Add a fallback to read from `metadata` when `litellm_metadata` is not a
dict, so `x-litellm-enable-message-redaction` and related headers work
correctly in the proxy flow.

Fixes BerriAI#20739

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Feb 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Feb 9, 2026 1:41pm

Request Review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 9, 2026

Greptile Overview

Greptile Summary

This PR fixes message-redaction headers being ignored in the proxy flow by updating should_redact_message_logging() to fall back to litellm_params["metadata"] when the resolved metadata field (often litellm_metadata) is present but not a dict (e.g., None). It also adds unit tests covering proxy vs SDK-direct metadata locations and precedence between dynamic params, headers, and global settings.

The change fits into the existing redaction decision path by ensuring request headers are actually read from the proxy-populated metadata structure, so the existing header-based enable/disable behavior works consistently across call paths.

Confidence Score: 4/5

  • This PR is close to safe to merge, but there is a small correctness risk around non-dict metadata values causing runtime errors.
  • The core fix is minimal and well-covered by unit tests, but the new fallback path can still leave metadata as a non-dict if litellm_params["metadata"] is also None/non-dict, which would make subsequent .get() calls crash in that edge case.
  • litellm/litellm_core_utils/redact_messages.py

Important Files Changed

Filename Overview
litellm/litellm_core_utils/redact_messages.py Adds a fallback in should_redact_message_logging() to read headers from metadata when litellm_metadata is present but None, fixing proxy requests that previously ignored redaction headers.
tests/test_litellm/litellm_core_utils/test_redact_messages.py Adds unit tests covering proxy vs SDK metadata locations and precedence between dynamic params, headers, and global setting for message redaction.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Proxy as LiteLLM Proxy
    participant Redact as should_redact_message_logging

    Client->>Proxy: Request + headers
    Note over Proxy: Proxy builds model_call_details
    Proxy->>Redact: model_call_details
    Redact->>Redact: litellm_params = model_call_details["litellm_params"]
    Redact->>Redact: metadata_field = get_metadata_variable_name_from_kwargs(litellm_params)
    Redact->>Redact: metadata = litellm_params[metadata_field]
    alt metadata is dict
        Redact->>Redact: request_headers = metadata["headers"]
    else metadata is None/non-dict
        Note over Redact: Fix: fallback to litellm_params["metadata"]
        Redact->>Redact: metadata = litellm_params["metadata"]
        Redact->>Redact: request_headers = metadata["headers"]
    end
    Redact-->>Proxy: boolean (redact?)
    Proxy-->>Client: Response (logging redacted per decision)
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines 140 to +143

metadata_field = get_metadata_variable_name_from_kwargs(litellm_params)
metadata = litellm_params.get(metadata_field, {})

if not isinstance(metadata, dict):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-dict metadata fallback
If litellm_params[metadata_field] is present but not a dict (e.g. None), the code falls back to litellm_params["metadata"] without re-validating it’s a dict. If metadata is also None/non-dict, metadata.get("headers") will raise. Consider normalizing after the fallback (e.g., metadata = metadata if isinstance(metadata, dict) else {}) so the function always handles non-dict inputs safely.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Fixed in 37f9bb7 — added a second isinstance check after the fallback so metadata is always normalized to {} when both fields are non-dict. Also added tests for the case where both litellm_metadata and metadata are None.

After falling back from litellm_metadata to metadata, ensure the value
is always a dict so .get("headers") never raises on None/non-dict inputs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: x-litellm-enable-message-redaction header ignored when sent via proxy

2 participants