Skip to content

fix(security): prevent secret_fields from leaking into spend logs - #27143

Merged
yuneng-berri merged 1 commit into
litellm_internal_stagingfrom
cursor/fix-secret-fields-in-spend-logs-a532
May 5, 2026
Merged

fix(security): prevent secret_fields from leaking into spend logs#27143
yuneng-berri merged 1 commit into
litellm_internal_stagingfrom
cursor/fix-secret-fields-in-spend-logs-a532

Conversation

@krrish-berri-2

Copy link
Copy Markdown
Contributor

Relevant issues

Reported in Slack: secret_fields (containing raw HTTP headers including Authorization Bearer tokens) were being logged in LiteLLM spend logs, exposing user credentials.

Pre-Submission checklist

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem

Type

🐛 Bug Fix (Security)

Changes

Root Cause

data["secret_fields"] contains raw HTTP headers (including Authorization: Bearer ... tokens) via a SecretFields(raw_headers=...) dict. When the body snapshot is created at:

data["proxy_server_request"]["body"] = copy.copy(data)

...the entire data dict — including secret_fields — is copied into the body snapshot. This body is later serialized to JSON and persisted in the LiteLLM_SpendLogs.proxy_server_request database column via _get_proxy_server_request_for_spend_logs_payload, which calls _sanitize_request_body_for_spend_logs_payload but that sanitizer only truncated long strings — it did not strip sensitive keys.

Fix (defense in depth)

  1. Primary fix (litellm/proxy/litellm_pre_call_utils.py): Exclude secret_fields when creating the body snapshot. The dict comprehension {k: v for k, v in data.items() if k != "secret_fields"} prevents secret_fields from entering the snapshot at all. secret_fields remains available on the live data dict for legitimate downstream consumers (MCP, Responses API).

  2. Secondary safeguard (litellm/proxy/spend_tracking/spend_tracking_utils.py): Added _SENSITIVE_REQUEST_BODY_KEYS frozenset to _sanitize_request_body_for_spend_logs_payload which strips secret_fields from any request body before it's serialized to JSON for spend log storage. This catches any other code paths that might include secret_fields in data that flows to spend logs.

Tests added

  • test_add_litellm_data_to_request_body_snapshot_excludes_secret_fields — verifies the body snapshot in proxy_server_request does not contain secret_fields while the live data dict still has it
  • test_sanitize_request_body_strips_secret_fields — verifies the sanitizer strips secret_fields from request bodies
  • test_proxy_server_request_payload_excludes_secret_fields — end-to-end test that the spend log serialization pipeline strips secret_fields

Screenshots / Proof of Fix

All 118 pre_call_utils tests pass and all 46 spend_tracking_utils tests pass (including 3 new tests).

Slack Thread

Open in Web Open in Cursor 

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@codecov

codecov Bot commented May 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@krrish-berri-2
krrish-berri-2 marked this pull request as ready for review May 5, 2026 02:00
secret_fields (containing raw HTTP headers including Authorization
Bearer tokens) was being included in proxy_server_request['body']
because the body snapshot was a copy.copy(data) of the full request
dict. This body gets serialized and persisted in the LiteLLM_SpendLogs
table, exposing user credentials in the database.

Root cause: data['secret_fields'] was set before the body snapshot at
data['proxy_server_request']['body'] = copy.copy(data), so the full
raw headers (including auth tokens) ended up in the snapshot.

Fix (defense in depth):
1. Exclude 'secret_fields' when creating the body snapshot in
   litellm_pre_call_utils.py (primary fix)
2. Strip 'secret_fields' in _sanitize_request_body_for_spend_logs_payload
   as a secondary safeguard

secret_fields remains available on the live data dict for legitimate
downstream consumers (MCP, Responses API).

Co-authored-by: Krrish Dholakia <krrish-berri-2@users.noreply.github.com>
@cursor
cursor Bot force-pushed the cursor/fix-secret-fields-in-spend-logs-a532 branch from b773a17 to 5923c32 Compare May 5, 2026 02:02
@greptile-apps

greptile-apps Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a security bug where secret_fields (containing raw HTTP headers including Authorization: Bearer tokens) was being copied into the proxy_server_request.body snapshot and subsequently persisted in LiteLLM_SpendLogs. The fix applies a two-layer defense: exclude secret_fields at snapshot creation time in add_litellm_data_to_request, and add it to the _SENSITIVE_REQUEST_BODY_KEYS filter in the spend-log sanitizer as a fallback for any other code paths.

Confidence Score: 5/5

Safe to merge — targeted, correct fix with solid defense-in-depth and full test coverage.

No P0 or P1 issues found. Both fix layers (snapshot exclusion and sanitizer filter) are correctly implemented. The sanitizer applies the key filter recursively, so nested occurrences are also caught. Tests cover the sanitizer directly, the serialization pipeline end-to-end, and the add_litellm_data_to_request body snapshot. All tests use mocks only, consistent with the repo's CI requirements.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/litellm_pre_call_utils.py Primary fix: body snapshot now uses a dict comprehension that excludes secret_fields, preventing Authorization tokens from entering the audit/spend-log path.
litellm/proxy/spend_tracking/spend_tracking_utils.py Secondary safeguard: adds _SENSITIVE_REQUEST_BODY_KEYS frozenset and filters it out in _sanitize_request_body_for_spend_logs_payload; the filter is applied recursively for nested dicts.
tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py Two new unit tests: one verifying the sanitizer strips secret_fields, and one end-to-end test verifying the full serialization pipeline excludes it; both use mocks only (no network calls).
tests/test_litellm/proxy/test_litellm_pre_call_utils.py New async test verifies add_litellm_data_to_request excludes secret_fields from the body snapshot while keeping it on the live data dict for downstream consumers.

Reviews (1): Last reviewed commit: "fix(security): prevent secret_fields fro..." | Re-trigger Greptile

@yuneng-berri
yuneng-berri enabled auto-merge May 5, 2026 02:04
@yuneng-berri
yuneng-berri merged commit 9ea824d into litellm_internal_staging May 5, 2026
42 checks passed
@yuneng-berri
yuneng-berri deleted the cursor/fix-secret-fields-in-spend-logs-a532 branch May 5, 2026 02:07
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…s-in-spend-logs-a532

fix(security): prevent secret_fields from leaking into spend logs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants