mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-21 00:21:49 +00:00
fix(batches): mask pre-signed request auth headers before raw-request logging
A pre-signed batch/file request (Mistral, Bedrock) carries its auth header inside the transformed request body, which pre_call logs verbatim into raw_request_typed_dict and raw-request callbacks, leaking the provider key. Mask the nested headers channel before handing the request to pre_call. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
4e4008cea9
commit
8cf2606e2d
2 changed files with 82 additions and 3 deletions
|
|
@ -278,6 +278,23 @@ def _has_pre_call_deployment_hook(logging_obj: LiteLLMLoggingObj) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def _mask_presigned_request_headers(transformed_request: bytes | str | dict) -> bytes | str | dict:
|
||||
"""A pre-signed request carries its auth inside its own ``headers`` key, which
|
||||
logging treats as request body (only the top-level headers channel gets masked),
|
||||
so mask it here before the request is handed to ``pre_call``."""
|
||||
if not isinstance(transformed_request, dict):
|
||||
return transformed_request
|
||||
request_headers: Final = transformed_request.get("headers")
|
||||
if not isinstance(request_headers, dict):
|
||||
return transformed_request
|
||||
|
||||
from litellm.litellm_core_utils.litellm_logging import (
|
||||
_get_masked_values, # pyright: ignore[reportPrivateUsage] # the shared header-masking helper has no public name
|
||||
)
|
||||
|
||||
return {**transformed_request, "headers": _get_masked_values(request_headers)}
|
||||
|
||||
|
||||
def _aws_signing_overrides(optional_params: Mapping[str, Any], litellm_params: Mapping[str, Any]) -> Mapping[str, Any]:
|
||||
return MappingProxyType(
|
||||
{
|
||||
|
|
@ -3692,7 +3709,7 @@ class BaseLLMHTTPHandler:
|
|||
"complete_input_dict": (
|
||||
"<streaming media upload>"
|
||||
if isinstance(transformed_request, dict) and "streaming_media_upload" in transformed_request
|
||||
else transformed_request
|
||||
else _mask_presigned_request_headers(transformed_request)
|
||||
),
|
||||
"api_base": api_base,
|
||||
"headers": headers,
|
||||
|
|
@ -4115,7 +4132,7 @@ class BaseLLMHTTPHandler:
|
|||
input="",
|
||||
api_key="",
|
||||
additional_args={
|
||||
"complete_input_dict": transformed_request,
|
||||
"complete_input_dict": _mask_presigned_request_headers(transformed_request),
|
||||
"api_base": api_base,
|
||||
"headers": headers,
|
||||
},
|
||||
|
|
@ -4194,7 +4211,7 @@ class BaseLLMHTTPHandler:
|
|||
input="",
|
||||
api_key="",
|
||||
additional_args={
|
||||
"complete_input_dict": transformed_request,
|
||||
"complete_input_dict": _mask_presigned_request_headers(transformed_request),
|
||||
"api_base": api_base,
|
||||
"headers": headers,
|
||||
"batch_id": batch_id,
|
||||
|
|
|
|||
|
|
@ -2761,6 +2761,68 @@ def test_direct_vector_store_search_debug_log_omits_stored_credentials(caplog, i
|
|||
assert "sk-embedding-s3cret" not in logged
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_retrieve_batch_masks_presigned_auth_header_in_raw_request_log():
|
||||
"""Regression: a pre-signed retrieve-batch request (Mistral, Bedrock) embeds its auth
|
||||
header inside the transformed request, which pre_call logs verbatim as the raw request
|
||||
body, so the provider key landed unmasked in raw_request_typed_dict and every
|
||||
raw-request callback."""
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as LitellmLogging
|
||||
from litellm.llms.mistral.batches.transformation import MistralBatchesConfig
|
||||
|
||||
provider_key = "mistral-s3cret-provider-key-123456"
|
||||
job_payload = {
|
||||
"id": "batch-1",
|
||||
"input_files": ["file-1"],
|
||||
"endpoint": "/v1/ocr",
|
||||
"model": "mistral-ocr-latest",
|
||||
"status": "SUCCESS",
|
||||
"created_at": 1_757_400_000,
|
||||
}
|
||||
sent_requests = []
|
||||
|
||||
def _capture(request: httpx.Request) -> httpx.Response:
|
||||
sent_requests.append(request)
|
||||
return httpx.Response(200, json=job_payload)
|
||||
|
||||
client = AsyncHTTPHandler()
|
||||
client.client = httpx.AsyncClient(transport=httpx.MockTransport(_capture))
|
||||
|
||||
logging_obj = LitellmLogging(
|
||||
model="mistral/mistral-ocr-latest",
|
||||
messages=[],
|
||||
stream=False,
|
||||
call_type="batch_retrieve",
|
||||
start_time=time.time(),
|
||||
litellm_call_id="batch-retrieve-call-id",
|
||||
function_id="batch-retrieve-function-id",
|
||||
log_raw_request_response=True,
|
||||
)
|
||||
logging_obj.update_environment_variables(
|
||||
model="mistral/mistral-ocr-latest",
|
||||
optional_params={},
|
||||
litellm_params={"litellm_call_id": "batch-retrieve-call-id", "metadata": {}},
|
||||
)
|
||||
|
||||
result = await BaseLLMHTTPHandler().retrieve_batch(
|
||||
batch_id="batch-1",
|
||||
litellm_params={"api_key": provider_key},
|
||||
provider_config=MistralBatchesConfig(),
|
||||
headers={},
|
||||
api_base=None,
|
||||
api_key=provider_key,
|
||||
logging_obj=logging_obj,
|
||||
_is_async=True,
|
||||
client=client,
|
||||
model="mistral/mistral-ocr-latest",
|
||||
)
|
||||
|
||||
assert result.id == "batch-1"
|
||||
assert sent_requests[0].headers["Authorization"] == f"Bearer {provider_key}"
|
||||
raw_request_body = logging_obj.model_call_details["raw_request_typed_dict"]["raw_request_body"]
|
||||
assert provider_key not in json.dumps(raw_request_body)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_anthropic_messages_handler_carries_deployment_vertex_location_for_pricing(monkeypatch):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue