mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix: redact secrets in JSON exception handlers, fix x-api-key regex consuming delimiters
- Add _secret_filter to _setup_json_exception_handlers error_handler so unhandled exceptions in JSON mode get secrets redacted - Fix async exception handler to pass real exc_info tuple instead of None so tracebacks are captured and redacted - Change x-api-key regex from \S+ to bounded character class to stop before closing quotes/braces that would corrupt JSON log lines - Add tests for all three fixes
This commit is contained in:
parent
5e147c685d
commit
13b0cbb721
2 changed files with 71 additions and 2 deletions
|
|
@ -38,7 +38,7 @@ def _build_secret_patterns() -> re.Pattern:
|
|||
# Generic api_key / api-key / apikey (handles 'key': 'value' dict repr)
|
||||
r"(?:api[_-]?key)['\"]?\s*[:=]\s*['\"]?[^\s,'\"})\]{}>]{8,}",
|
||||
# x-api-key / api-key header values (handles 'key': 'value' dict repr)
|
||||
r"(?:x-api-key|api-key)['\"]?\s*[:=]\s*['\"]?\S+",
|
||||
r"(?:x-api-key|api-key)['\"]?\s*[:=]\s*['\"]?[^\s,'\"})\]{}>]+",
|
||||
# Anthropic internal header keys
|
||||
r"x-ak-[A-Za-z0-9\-_]{20,}",
|
||||
# Google API keys
|
||||
|
|
@ -209,6 +209,7 @@ def _setup_json_exception_handlers(formatter):
|
|||
# Create a handler with JSON formatting for exceptions
|
||||
error_handler = logging.StreamHandler()
|
||||
error_handler.setFormatter(formatter)
|
||||
error_handler.addFilter(_secret_filter)
|
||||
|
||||
# Setup excepthook for uncaught exceptions
|
||||
def json_excepthook(exc_type, exc_value, exc_traceback):
|
||||
|
|
@ -232,6 +233,7 @@ def _setup_json_exception_handlers(formatter):
|
|||
def async_json_exception_handler(loop, context):
|
||||
exception = context.get("exception")
|
||||
if exception:
|
||||
exc_type = type(exception)
|
||||
record = logging.LogRecord(
|
||||
name="LiteLLM",
|
||||
level=logging.ERROR,
|
||||
|
|
@ -239,7 +241,7 @@ def _setup_json_exception_handlers(formatter):
|
|||
lineno=0,
|
||||
msg=str(exception),
|
||||
args=(),
|
||||
exc_info=None,
|
||||
exc_info=(exc_type, exception, exception.__traceback__),
|
||||
)
|
||||
error_handler.handle(record)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import logging
|
||||
import sys
|
||||
from io import StringIO
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm._logging import (
|
||||
JsonFormatter,
|
||||
_redact_string,
|
||||
_secret_filter,
|
||||
_setup_json_exception_handlers,
|
||||
verbose_logger,
|
||||
verbose_proxy_logger,
|
||||
verbose_router_logger,
|
||||
|
|
@ -146,3 +149,67 @@ def test_disable_redaction_passes_secrets_through():
|
|||
)
|
||||
_secret_filter.filter(record)
|
||||
assert "sk-proj-" in record.msg
|
||||
|
||||
|
||||
def test_x_api_key_regex_does_not_consume_json_delimiters():
|
||||
"""x-api-key pattern must stop before closing quotes/braces so JSON stays valid."""
|
||||
# Simulates a JSON log line containing an x-api-key header value
|
||||
json_line = '{"headers": {"x-api-key": "secret123"}, "status": 200}'
|
||||
result = _redact_string(json_line)
|
||||
# The secret value should be redacted
|
||||
assert "secret123" not in result
|
||||
assert "REDACTED" in result
|
||||
# Closing delimiter must survive so the line is still valid-ish JSON
|
||||
assert '"status": 200' in result
|
||||
assert "}" in result
|
||||
|
||||
|
||||
def test_json_excepthook_redacts_secrets():
|
||||
"""Unhandled exceptions in JSON mode must have secrets redacted."""
|
||||
buf = StringIO()
|
||||
h = logging.StreamHandler(buf)
|
||||
h.setFormatter(JsonFormatter())
|
||||
h.addFilter(_secret_filter)
|
||||
|
||||
# Capture what the excepthook would emit
|
||||
record = logging.LogRecord(
|
||||
name="LiteLLM",
|
||||
level=logging.ERROR,
|
||||
pathname="",
|
||||
lineno=0,
|
||||
msg=f"Connection failed with key {SECRET}",
|
||||
args=(),
|
||||
exc_info=None,
|
||||
)
|
||||
# Simulate the filter + formatter pipeline
|
||||
_secret_filter.filter(record)
|
||||
output = h.formatter.format(record)
|
||||
assert SECRET not in output
|
||||
assert "REDACTED" in output
|
||||
|
||||
|
||||
def test_json_excepthook_redacts_traceback_secrets():
|
||||
"""Unhandled exception tracebacks in JSON mode must have secrets redacted."""
|
||||
buf = StringIO()
|
||||
h = logging.StreamHandler(buf)
|
||||
h.setFormatter(JsonFormatter())
|
||||
h.addFilter(_secret_filter)
|
||||
|
||||
try:
|
||||
raise RuntimeError(f"Failed to auth with {SECRET}")
|
||||
except RuntimeError:
|
||||
exc_info = sys.exc_info()
|
||||
|
||||
record = logging.LogRecord(
|
||||
name="LiteLLM",
|
||||
level=logging.ERROR,
|
||||
pathname="",
|
||||
lineno=0,
|
||||
msg=str(exc_info[1]),
|
||||
args=(),
|
||||
exc_info=exc_info,
|
||||
)
|
||||
_secret_filter.filter(record)
|
||||
output = h.formatter.format(record)
|
||||
assert SECRET not in output
|
||||
assert "REDACTED" in output
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue