mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
fix; small leaks possible and some greptile feedback
This commit is contained in:
parent
874ff028d2
commit
3a6632d533
2 changed files with 90 additions and 70 deletions
|
|
@ -5,10 +5,7 @@ import re
|
|||
import sys
|
||||
from datetime import datetime
|
||||
from logging import Formatter
|
||||
from collections.abc import Mapping
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
LogRecordArgs = Union[Tuple[Any, ...], Mapping[str, Any]]
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
|
||||
from litellm.litellm_core_utils.safe_json_loads import safe_json_loads
|
||||
|
|
@ -20,11 +17,7 @@ if set_verbose is True:
|
|||
"`litellm.set_verbose` is deprecated. Please set `os.environ['LITELLM_LOG'] = 'DEBUG'` for debug logs."
|
||||
)
|
||||
|
||||
_DISABLE_SECRET_REDACTION = os.getenv("LITELLM_DISABLE_REDACTION", "").lower() in (
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
)
|
||||
_ENABLE_SECRET_REDACTION = bool(os.getenv("LITELLM_REDACT_SECRETS", False))
|
||||
|
||||
_REDACTED = "REDACTED"
|
||||
|
||||
|
|
@ -72,30 +65,18 @@ class SecretRedactionFilter(logging.Filter):
|
|||
"""Scrubs known secret/credential patterns from log records."""
|
||||
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
if _DISABLE_SECRET_REDACTION:
|
||||
if not _ENABLE_SECRET_REDACTION:
|
||||
return True
|
||||
|
||||
if isinstance(record.msg, str):
|
||||
record.msg = _redact_string(record.msg)
|
||||
|
||||
if record.args:
|
||||
record.args = self._redact_args(record.args)
|
||||
try:
|
||||
record.msg = _redact_string(record.getMessage())
|
||||
record.args = None
|
||||
except Exception:
|
||||
if isinstance(record.msg, str):
|
||||
record.msg = _redact_string(record.msg)
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _redact_args(args: LogRecordArgs) -> LogRecordArgs:
|
||||
if isinstance(args, Mapping):
|
||||
return {
|
||||
k: _redact_string(v) if isinstance(v, str) else v
|
||||
for k, v in args.items()
|
||||
}
|
||||
if isinstance(args, tuple):
|
||||
return tuple(
|
||||
_redact_string(a) if isinstance(a, str) else a for a in args
|
||||
)
|
||||
return args
|
||||
|
||||
|
||||
_secret_filter = SecretRedactionFilter()
|
||||
|
||||
|
|
@ -401,7 +382,6 @@ def _turn_on_json():
|
|||
"""
|
||||
handler = logging.StreamHandler()
|
||||
handler.setFormatter(JsonFormatter())
|
||||
handler.addFilter(_secret_filter)
|
||||
_initialize_loggers_with_handler(handler)
|
||||
# Set up exception handlers
|
||||
_setup_json_exception_handlers(JsonFormatter())
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
from io import StringIO
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../../.."))
|
||||
|
||||
from litellm._logging import (
|
||||
SecretRedactionFilter,
|
||||
_redact_string,
|
||||
_secret_filter,
|
||||
verbose_logger,
|
||||
|
|
@ -16,32 +12,18 @@ from litellm._logging import (
|
|||
verbose_router_logger,
|
||||
)
|
||||
|
||||
|
||||
def test_redact_string_covers_all_secret_formats():
|
||||
secrets = {
|
||||
"AWS access key": "AKIAIOSFODNN7EXAMPLE",
|
||||
"AWS temp key": "ASIAISAMPLEKEYID1234",
|
||||
"AWS secret": "aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
||||
"AWS session token": "aws_session_token: FwoGZXIvYXdzEBYaDHqa0AP1RIF0re2EXAMPLETOKEN1234567890",
|
||||
"Bearer token": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.payload.sig",
|
||||
"OpenAI key": "sk-proj-abc123def456ghi789jklmnopqrst",
|
||||
"x-api-key header": "x-api-key: mysecretapikey123",
|
||||
"api-key header": "api-key: mysecretapikey123",
|
||||
"Azure key": "api_key=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
|
||||
"Google key": "AIzaSyA1234567890abcdefghijklmnopqrstuvwx",
|
||||
"Anthropic x-ak": "x-ak-abcdefghijklmnopqrstuvwxyz12345",
|
||||
"Dict repr with secrets": "Request Headers: {'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.payload.sig', 'x-api-key': 'sk-ant-api03-abcdefghijklmnopqrstuvwxyz'}",
|
||||
}
|
||||
for label, secret in secrets.items():
|
||||
result = _redact_string(f"msg: {secret}")
|
||||
assert secret not in result, f"{label} was not redacted"
|
||||
assert "REDACTED" in result, f"{label} missing REDACTED marker"
|
||||
|
||||
normal = "Loaded model gpt-4 with 3 replicas on us-east-1"
|
||||
assert _redact_string(normal) == normal
|
||||
SECRET = "sk-proj-abc123def456ghi789jklmnopqrst"
|
||||
|
||||
|
||||
def test_filter_redacts_secrets_in_logger_output():
|
||||
@pytest.fixture(autouse=True)
|
||||
def _enable_redaction():
|
||||
"""Enable secret redaction for all tests in this module."""
|
||||
with patch("litellm._logging._ENABLE_SECRET_REDACTION", True):
|
||||
yield
|
||||
|
||||
|
||||
def _capture_logger_output(fn):
|
||||
"""Run fn with all litellm loggers wired to a StringIO buffer, return output."""
|
||||
buf = StringIO()
|
||||
h = logging.StreamHandler(buf)
|
||||
h.addFilter(_secret_filter)
|
||||
|
|
@ -51,22 +33,80 @@ def test_filter_redacts_secrets_in_logger_output():
|
|||
lg.handlers.clear()
|
||||
lg.addHandler(h)
|
||||
lg.setLevel(logging.DEBUG)
|
||||
|
||||
try:
|
||||
verbose_logger.debug(f"Credentials: AKIAIOSFODNN7EXAMPLE")
|
||||
verbose_proxy_logger.debug(f"Headers: Bearer eyJhbGciOiJSUzI1NiJ9.payload.sig")
|
||||
verbose_router_logger.debug(f"Key: sk-proj-abc123def456ghi789jklmnopqrst")
|
||||
verbose_logger.debug("Normal message with no secrets")
|
||||
|
||||
output = buf.getvalue()
|
||||
assert "AKIAIOSFODNN7EXAMPLE" not in output
|
||||
assert "eyJhbGciOiJSUzI1NiJ9" not in output
|
||||
assert "sk-proj-abc123def456ghi789jklmnopqrst" not in output
|
||||
assert "REDACTED" in output
|
||||
assert "Normal message with no secrets" in output
|
||||
fn()
|
||||
return buf.getvalue()
|
||||
finally:
|
||||
for lg, handlers, level in saved:
|
||||
lg.handlers.clear()
|
||||
for old_h in handlers:
|
||||
lg.addHandler(old_h)
|
||||
lg.setLevel(level)
|
||||
|
||||
|
||||
def test_redact_string_catches_secret_patterns():
|
||||
"""Core regex patterns redact known secret formats."""
|
||||
cases = [
|
||||
"Bearer eyJhbGciOiJSUzI1NiJ9.payload.sig",
|
||||
"api_key=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
|
||||
"password=supersecretpassword123",
|
||||
"postgresql://admin:s3cretpass@db.example.com:5432/mydb",
|
||||
SECRET,
|
||||
]
|
||||
for secret in cases:
|
||||
result = _redact_string("msg: " + secret)
|
||||
assert secret not in result, f"{secret!r} was not redacted"
|
||||
assert "REDACTED" in result
|
||||
|
||||
normal = "Loaded model gpt-4 with 3 replicas on us-east-1"
|
||||
assert _redact_string(normal) == normal
|
||||
|
||||
|
||||
def test_filter_redacts_secrets_in_logger_output():
|
||||
def log_messages():
|
||||
verbose_logger.debug("Key: " + SECRET)
|
||||
verbose_logger.debug("Normal message with no secrets")
|
||||
|
||||
output = _capture_logger_output(log_messages)
|
||||
assert SECRET not in output
|
||||
assert "REDACTED" in output
|
||||
assert "Normal message with no secrets" in output
|
||||
|
||||
|
||||
def test_filter_redacts_percent_style_args():
|
||||
"""Secrets passed as %-style args should be redacted."""
|
||||
|
||||
def log_messages():
|
||||
verbose_logger.debug("key=%s region=%s", SECRET, "us-east-1")
|
||||
|
||||
output = _capture_logger_output(log_messages)
|
||||
assert SECRET not in output
|
||||
assert "us-east-1" in output
|
||||
|
||||
|
||||
def test_filter_redacts_non_string_args():
|
||||
"""Secrets inside dicts/lists passed as %-style args should be redacted."""
|
||||
|
||||
def log_messages():
|
||||
verbose_logger.debug("Config: %s", {"nested": {"key": SECRET}})
|
||||
verbose_logger.debug("Keys: %s", [SECRET])
|
||||
|
||||
output = _capture_logger_output(log_messages)
|
||||
assert SECRET not in output
|
||||
assert "REDACTED" in output
|
||||
|
||||
|
||||
def test_disable_redaction_passes_secrets_through(_enable_redaction):
|
||||
"""When _ENABLE_SECRET_REDACTION is False, secrets pass through."""
|
||||
with patch("litellm._logging._ENABLE_SECRET_REDACTION", False):
|
||||
record = logging.LogRecord(
|
||||
name="test",
|
||||
level=logging.DEBUG,
|
||||
pathname="",
|
||||
lineno=0,
|
||||
msg="key=" + SECRET,
|
||||
args=(),
|
||||
exc_info=None,
|
||||
)
|
||||
_secret_filter.filter(record)
|
||||
assert "sk-proj-" in record.msg
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue