mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
fix(logging): reserve trace_id/session_id in JsonFormatter against message-content spoofing
JsonFormatter merges keys parsed from the message body before applying extra record attributes, and the extra-attributes loop skips a key that's already present. A caller-controlled log message that happens to parse as JSON/dict with a "trace_id"/"session_id" key (e.g. the proxy logging a raw request-header dict) could therefore make the JSON record carry the attacker-supplied value instead of the real correlation context set via CorrelationContextFilter. trace_id/session_id are now applied from the LogRecord's own attributes after message-content parsing, unconditionally overwriting anything the message body claimed for those two keys.
This commit is contained in:
parent
a7c8396a30
commit
7f390a57fc
2 changed files with 29 additions and 0 deletions
|
|
@ -231,6 +231,18 @@ class JsonFormatter(Formatter):
|
|||
if key not in _STANDARD_RECORD_ATTRS and key not in json_record:
|
||||
json_record[key] = value
|
||||
|
||||
# trace_id/session_id are reserved: CorrelationContextFilter is the only
|
||||
# legitimate source for these two fields. Without this, a message string
|
||||
# that happens to parse as JSON/dict (e.g. a proxy log line dumping raw
|
||||
# request headers) with a "trace_id"/"session_id" key would have already
|
||||
# claimed the key at the parsed-message step above, and the extra-attributes
|
||||
# loop's "key not in json_record" guard would then skip the real value -
|
||||
# letting a caller-supplied header spoof another request's correlation ids.
|
||||
for reserved_key in ("trace_id", "session_id"):
|
||||
value = getattr(record, reserved_key, None)
|
||||
if value:
|
||||
json_record[reserved_key] = value
|
||||
|
||||
# Set component/logger only if not already supplied via extra={...}
|
||||
if "component" not in json_record:
|
||||
json_record["component"] = record.name
|
||||
|
|
|
|||
|
|
@ -445,6 +445,23 @@ def test_session_id_injected_when_set(monkeypatch):
|
|||
session_id_var.set("")
|
||||
|
||||
|
||||
def test_trace_id_and_session_id_cannot_be_spoofed_by_message_content(monkeypatch):
|
||||
"""A log message that happens to parse as JSON/dict with "trace_id"/"session_id"
|
||||
keys (e.g. the proxy logging a raw request-header dict) must not override the
|
||||
real correlation ids set via set_trace_id()/set_session_id()."""
|
||||
monkeypatch.setattr(litellm, "request_correlation_in_logs", True)
|
||||
lg, cap = _make_capture_logger("test.spoof_attempt")
|
||||
set_trace_id("real-trace-id")
|
||||
set_session_id("real-session-id")
|
||||
try:
|
||||
lg.info('{"trace_id": "attacker-supplied-trace", "session_id": "attacker-supplied-session"}')
|
||||
assert cap.records[0]["trace_id"] == "real-trace-id"
|
||||
assert cap.records[0]["session_id"] == "real-session-id"
|
||||
finally:
|
||||
trace_id_var.set("")
|
||||
session_id_var.set("")
|
||||
|
||||
|
||||
def test_session_id_absent_when_not_set():
|
||||
"""session_id must NOT appear in JSON record when not set for this context."""
|
||||
lg, cap = _make_capture_logger("test.no_session")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue