-
-
Notifications
You must be signed in to change notification settings - Fork 7.7k
fix: redaction headers ignored when sent via proxy #20740
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
2 commits merged into
BerriAI:litellm_oss_staging_02_10_2026
from
tsachis:fix/redact-header-proxy-fallback
Feb 10, 2026
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
145 changes: 145 additions & 0 deletions
145
tests/test_litellm/litellm_core_utils/test_redact_messages.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| """ | ||
| Tests for litellm.litellm_core_utils.redact_messages.should_redact_message_logging | ||
|
|
||
| Covers the proxy flow where headers arrive in litellm_params["metadata"]["headers"] | ||
| but litellm_params["litellm_metadata"] is None. | ||
| """ | ||
|
|
||
| import pytest | ||
|
|
||
| import litellm | ||
| from litellm.litellm_core_utils.redact_messages import should_redact_message_logging | ||
|
|
||
|
|
||
| @pytest.fixture(autouse=True) | ||
| def _reset_global_redaction(): | ||
| """Ensure the global setting is off for every test.""" | ||
| original = litellm.turn_off_message_logging | ||
| litellm.turn_off_message_logging = False | ||
| yield | ||
| litellm.turn_off_message_logging = original | ||
|
|
||
|
|
||
| def _make_model_call_details( | ||
| metadata_headers=None, | ||
| litellm_metadata=None, | ||
| metadata=None, | ||
| standard_callback_dynamic_params=None, | ||
| ): | ||
| """Build a model_call_details dict that mimics real proxy/SDK flows.""" | ||
| litellm_params = {} | ||
| if metadata is not None: | ||
| litellm_params["metadata"] = metadata | ||
| elif metadata_headers is not None: | ||
| litellm_params["metadata"] = {"headers": metadata_headers} | ||
| else: | ||
| litellm_params["metadata"] = {} | ||
|
|
||
| # get_litellm_params always sets this key (even when value is None) | ||
| litellm_params["litellm_metadata"] = litellm_metadata | ||
|
|
||
| details = {"litellm_params": litellm_params} | ||
| if standard_callback_dynamic_params is not None: | ||
| details["standard_callback_dynamic_params"] = standard_callback_dynamic_params | ||
| return details | ||
|
|
||
|
|
||
| class TestShouldRedactMessageLogging: | ||
| """Unit tests for should_redact_message_logging().""" | ||
|
|
||
| # ---- proxy flow: headers in metadata, litellm_metadata is None ---- | ||
|
|
||
| def test_enable_redaction_via_x_header_proxy_flow(self): | ||
| """x-litellm-enable-message-redaction header should enable redaction | ||
| even when litellm_metadata is None (proxy path).""" | ||
| details = _make_model_call_details( | ||
| metadata_headers={"x-litellm-enable-message-redaction": "true"}, | ||
| litellm_metadata=None, | ||
| ) | ||
| assert should_redact_message_logging(details) is True | ||
|
|
||
| def test_enable_redaction_via_old_header_proxy_flow(self): | ||
| """litellm-enable-message-redaction header should enable redaction | ||
| even when litellm_metadata is None (proxy path).""" | ||
| details = _make_model_call_details( | ||
| metadata_headers={"litellm-enable-message-redaction": "true"}, | ||
| litellm_metadata=None, | ||
| ) | ||
| assert should_redact_message_logging(details) is True | ||
|
|
||
| def test_disable_redaction_via_header_proxy_flow(self): | ||
| """litellm-disable-message-redaction should suppress redaction | ||
| even when global setting is on, and litellm_metadata is None.""" | ||
| litellm.turn_off_message_logging = True | ||
| details = _make_model_call_details( | ||
| metadata_headers={"litellm-disable-message-redaction": "true"}, | ||
| litellm_metadata=None, | ||
| ) | ||
| assert should_redact_message_logging(details) is False | ||
|
|
||
| # ---- SDK direct-call flow: headers in litellm_metadata ---- | ||
|
|
||
| def test_enable_redaction_via_header_in_litellm_metadata(self): | ||
| """Headers inside litellm_metadata (SDK direct call) should work.""" | ||
| details = _make_model_call_details( | ||
| litellm_metadata={"headers": {"x-litellm-enable-message-redaction": "true"}}, | ||
| ) | ||
| assert should_redact_message_logging(details) is True | ||
|
|
||
| # ---- no headers at all ---- | ||
|
|
||
| def test_no_headers_defaults_to_global_off(self): | ||
| """Without headers, falls back to global setting (False).""" | ||
| details = _make_model_call_details( | ||
| metadata_headers=None, | ||
| litellm_metadata=None, | ||
| ) | ||
| assert should_redact_message_logging(details) is False | ||
|
|
||
| def test_no_headers_global_on(self): | ||
| """Without headers, respects global turn_off_message_logging=True.""" | ||
| litellm.turn_off_message_logging = True | ||
| details = _make_model_call_details( | ||
| metadata_headers=None, | ||
| litellm_metadata=None, | ||
| ) | ||
| assert should_redact_message_logging(details) is True | ||
|
|
||
| # ---- dynamic params take precedence ---- | ||
|
|
||
| def test_dynamic_param_enables_redaction(self): | ||
| """Dynamic turn_off_message_logging=True should enable redaction.""" | ||
| details = _make_model_call_details( | ||
| metadata_headers={}, | ||
| litellm_metadata=None, | ||
| standard_callback_dynamic_params={"turn_off_message_logging": True}, | ||
| ) | ||
| assert should_redact_message_logging(details) is True | ||
|
|
||
| def test_dynamic_param_false_overrides_header(self): | ||
| """Dynamic turn_off_message_logging=False should take precedence over enable header.""" | ||
| details = _make_model_call_details( | ||
| metadata_headers={"x-litellm-enable-message-redaction": "true"}, | ||
| litellm_metadata=None, | ||
| standard_callback_dynamic_params={"turn_off_message_logging": False}, | ||
| ) | ||
| assert should_redact_message_logging(details) is False | ||
|
|
||
| # ---- non-dict metadata safety ---- | ||
|
|
||
| def test_both_metadata_fields_none(self): | ||
| """When both litellm_metadata and metadata are None, should not raise.""" | ||
| details = _make_model_call_details( | ||
| metadata=None, | ||
| litellm_metadata=None, | ||
| ) | ||
| assert should_redact_message_logging(details) is False | ||
|
|
||
| def test_both_metadata_fields_none_global_on(self): | ||
| """When both metadata fields are None but global is on, should still return True.""" | ||
| litellm.turn_off_message_logging = True | ||
| details = _make_model_call_details( | ||
| metadata=None, | ||
| litellm_metadata=None, | ||
| ) | ||
| assert should_redact_message_logging(details) is True |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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 tolitellm_params["metadata"]without re-validating it’s a dict. Ifmetadatais alsoNone/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.There was a problem hiding this comment.
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
isinstancecheck after the fallback sometadatais always normalized to{}when both fields are non-dict. Also added tests for the case where bothlitellm_metadataandmetadataareNone.