fix(passthrough): mask a private key block that straddles the redaction window

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng 2026-09-26 10:39:22 +00:00
parent b04c9830b9
commit e4fb4171df
2 changed files with 98 additions and 1 deletions

View file

@ -3,6 +3,7 @@ import asyncio
import copy
import json
import posixpath
import re
import traceback
from base64 import b64encode
from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable, Iterable, Mapping, Sequence
@ -63,6 +64,7 @@ from litellm.litellm_core_utils.litellm_logging import _get_masked_values
from litellm.litellm_core_utils.logging_worker import GLOBAL_LOGGING_WORKER
from litellm.litellm_core_utils.redact_messages import should_redact_message_logging
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
from litellm.litellm_core_utils.secret_redaction import REDACTED
from litellm.llms.base_llm.managed_resources.utils import (
resolve_passthrough_managed_id_provider,
)
@ -947,6 +949,14 @@ def _headers_without_body_framing(headers: httpx.Headers) -> httpx.Headers:
)
_DANGLING_PRIVATE_KEY: Final = re.compile(r"-----BEGIN[A-Z \-]*PRIVATE KEY-----")
def _mask_dangling_private_key(text: str) -> str:
match: Final = _DANGLING_PRIVATE_KEY.search(text)
return text if match is None else text[: match.start()] + REDACTED
def _passthrough_upstream_failure_reporter(
response: httpx.Response,
user_api_key_dict: UserAPIKeyAuth,
@ -965,7 +975,9 @@ def _passthrough_upstream_failure_reporter(
upstream_error_body: Final = (
REDACTED_BY_LITELLM
if should_redact_message_logging(logging_obj.model_call_details)
else _truncate_upstream_error_body(_sanitize_upstream_error_body(redact(preview_text)))
else _truncate_upstream_error_body(
_sanitize_upstream_error_body(_mask_dangling_private_key(redact(preview_text)))
)
)
log_warning(
"pass_through_endpoint: upstream %s %s returned %s: %s",

View file

@ -8331,3 +8331,88 @@ async def test_passthrough_upstream_failure_reporter_redacts_only_the_bounded_pr
assert len(logged_details) == 1, logged_details
assert "REDACTED-KEY" in logged_details[0], logged_details[0]
assert marker_key not in logged_details[0], logged_details[0]
@pytest.mark.asyncio
async def test_passthrough_upstream_failure_reporter_masks_a_pem_block_straddling_the_redact_window():
"""A PEM block that starts inside the logged prefix but ends past the redact
window must not leak its head into warnings or the failure hook."""
json_prefix: Final = '{"error":{"message":"downstream provider failed: '
key_body: Final = "Ab3dEf7gHi9jKl0mN" * 1250
preview: Final = (
json_prefix + '", "detail": "' + "-----BEGIN PRIVATE KEY-----\n" + key_body + "\n-----END PRIVATE KEY-----"
).encode()
logged_details: list[str] = []
def recording_warning(fmt, *args, **kwargs):
if str(fmt).startswith("pass_through_endpoint: upstream"):
logged_details.append(str(fmt % args))
upstream_response: Final = httpx.Response(
status_code=500,
headers={"content-type": "application/json"},
request=httpx.Request("POST", "http://target-api.com/v1/chat/completions"),
content=b"{}",
)
logging_obj: Final = MagicMock()
logging_obj.model_call_details = {}
proxy_logging: Final = MagicMock()
proxy_logging.post_call_failure_hook = AsyncMock()
report: Final = _passthrough_upstream_failure_reporter(
response=upstream_response,
user_api_key_dict=MagicMock(),
request_payload={},
logging_obj=logging_obj,
proxy_logging=proxy_logging,
log_warning=recording_warning,
)
await report(preview)
assert len(logged_details) == 1, logged_details
hook_exception: Final = proxy_logging.post_call_failure_hook.call_args.kwargs["original_exception"]
rendered: Final = logged_details[0] + str(hook_exception)
key_head_slice: Final = key_body[200:240]
assert key_head_slice not in rendered, rendered
assert "-----BEGIN" not in rendered, rendered
assert "downstream provider failed" in rendered, rendered
@pytest.mark.asyncio
async def test_passthrough_upstream_failure_reporter_keeps_text_after_a_complete_pem_block():
"""A complete PEM inside the window is redacted whole, and content after its
END marker still reaches the log: the dangling mask must not over-cut."""
pem: Final = (
"-----BEGIN PRIVATE KEY-----\n"
+ "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQshortkey==\n"
+ "-----END PRIVATE KEY-----"
)
preview: Final = ('{"error":{"detail":"' + pem + '","status":"INTERNAL","code":500}}').encode()
logged_details: list[str] = []
def recording_warning(fmt, *args, **kwargs):
if str(fmt).startswith("pass_through_endpoint: upstream"):
logged_details.append(str(fmt % args))
upstream_response: Final = httpx.Response(
status_code=500,
headers={"content-type": "application/json"},
request=httpx.Request("POST", "http://target-api.com/v1/chat/completions"),
content=b"{}",
)
logging_obj: Final = MagicMock()
logging_obj.model_call_details = {}
proxy_logging: Final = MagicMock()
proxy_logging.post_call_failure_hook = AsyncMock()
report: Final = _passthrough_upstream_failure_reporter(
response=upstream_response,
user_api_key_dict=MagicMock(),
request_payload={},
logging_obj=logging_obj,
proxy_logging=proxy_logging,
log_warning=recording_warning,
)
await report(preview)
assert len(logged_details) == 1, logged_details
assert "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQshortkey==" not in logged_details[0], logged_details[0]
assert "INTERNAL" in logged_details[0], logged_details[0]