litellm - ✅(Solved) Fix Gemini API key leaked in error traces via URL query parameter [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
BerriAI/litellm#24902Fetched 2026-04-08 02:23:53
View on GitHub
Comments
0
Participants
1
Timeline
5
Reactions
0
Participants
Timeline (top)
cross-referenced ×2referenced ×2labeled ×1

When using the gemini/ provider, LiteLLM passes the API key as a URL query parameter (?key=...). When API calls fail, the full URL — including the key — appears in exception traces.

This means any error logged, captured by an observability tool, or surfaced in a CI/CD run exposes the API key in plaintext.

Error Message

import litellm

Use an invalid model to trigger an error

try: litellm.completion( model="gemini/gemini-2.0-flash-lite", # deprecated model messages=[{"role": "user", "content": "hello"}], ) except Exception as e: print(str(e)) # Full URL with ?key=AIza... is visible

Root Cause

When using the gemini/ provider, LiteLLM passes the API key as a URL query parameter (?key=...). When API calls fail, the full URL — including the key — appears in exception traces.

This means any error logged, captured by an observability tool, or surfaced in a CI/CD run exposes the API key in plaintext.

Fix Action

Fixed

PR fix notes

PR #24943: fix(llm translation): redact Gemini API key from URL query params in error traces

Description (problem / solution / changelog)

Summary

Fixes #24902 — Gemini API keys leaked in error traces via URL query params.

Gemini authenticates via ?key=<api_key> in the request URL. When a call fails, httpx.Response.raise_for_status() includes the full URL in the error message, leaking the key in both exception messages and log output.

Changes:

  • New module litellm/litellm_core_utils/secret_redaction.py — extracts secret-redaction logic from litellm/_logging.py into a proper public utility (redact_string()), following the project convention of shared utilities living in litellm_core_utils/
  • Pattern added(?<=[?&])key=[^\s&'"]{8,} catches ?key=VALUE and &key=VALUE URL fragments in the existing SecretRedactionFilter, so all log output is covered
  • Exception messages — applies redact_string() to error_str in exception_mapping_utils.py so the key is also stripped from the mapped exception surfaced to API callers
  • Backward compat_redact_string = redact_string alias kept in _logging.py

Test plan

  • test_redact_url_query_param_key_question_mark?key=<secret> is redacted
  • test_redact_url_query_param_key_ampersand&key=<secret> redacted, other params preserved
  • test_redact_url_query_param_key_short_value_not_redacted — values < 8 chars not affected (avoids false positives on ?key=abc)
  • test_redact_string_applied_to_httpx_error_message — simulates the exact httpx raise_for_status() leak path
  • test_redact_url_query_param_key_in_logger_output — end-to-end through the logger
  • All 17 tests in test_secret_redaction.py pass
  • Black, Ruff, MyPy, circular-import, import-safety checks all clean on changed files

Changed files

  • litellm/_logging.py (modified, +5/-53)
  • litellm/litellm_core_utils/exception_mapping_utils.py (modified, +12/-3)
  • litellm/litellm_core_utils/secret_redaction.py (added, +66/-0)
  • tests/test_litellm/test_secret_redaction.py (modified, +102/-7)

PR #24958: Litellm oss staging 04 01 2026

Description (problem / solution / changelog)

Relevant issues

<!-- e.g. "Fixes #000" -->

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • 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
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.
  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Type

<!-- Select the type of Pull Request --> <!-- Keep only the necessary ones -->

🆕 New Feature 🐛 Bug Fix 🧹 Refactoring 📖 Documentation 🚄 Infrastructure ✅ Test

Changes

Changed files

  • docs/my-website/docs/providers/azure/azure_responses.md (modified, +2/-2)
  • docs/my-website/docs/proxy/guardrails/qohash_qostodian_nexus.md (added, +240/-0)
  • docs/my-website/docs/proxy/team_model_add.md (modified, +1/-1)
  • docs/my-website/docs/realtime.md (modified, +1/-1)
  • docs/my-website/sidebars.js (modified, +1/-0)
  • litellm/_logging.py (modified, +5/-53)
  • litellm/cost_calculator.py (modified, +3/-1)
  • litellm/litellm_core_utils/exception_mapping_utils.py (modified, +12/-3)
  • litellm/litellm_core_utils/secret_redaction.py (added, +66/-0)
  • litellm/llms/azure/azure.py (modified, +1/-0)
  • litellm/llms/azure/cost_calculation.py (modified, +3/-1)
  • litellm/llms/azure_ai/cost_calculator.py (modified, +2/-0)
  • litellm/llms/vertex_ai/gemini/transformation.py (modified, +15/-0)
  • litellm/proxy/guardrails/guardrail_hooks/qohash/__init__.py (added, +35/-0)
  • litellm/proxy/guardrails/guardrail_hooks/qohash/qohash.py (added, +74/-0)
  • litellm/proxy/utils.py (modified, +3/-0)
  • litellm/types/guardrails.py (modified, +5/-0)
  • litellm/types/proxy/guardrails/guardrail_hooks/qohash.py (added, +16/-0)
  • tests/litellm/llms/azure/__init__.py (added, +0/-0)
  • tests/litellm/llms/azure/test_azure_embedding.py (added, +94/-0)
  • tests/llm_translation/test_azure_openai.py (modified, +5/-5)
  • tests/test_litellm/llms/azure/test_azure_cost_calculation.py (added, +75/-0)
  • tests/test_litellm/llms/azure_ai/test_azure_ai_cost_calculator.py (modified, +48/-0)
  • tests/test_litellm/llms/vertex_ai/gemini/test_vertex_ai_gemini_transformation.py (modified, +47/-0)
  • tests/test_litellm/proxy/guardrails/test_qostodian_nexus_guardrail.py (added, +252/-0)
  • tests/test_litellm/proxy/hooks/test_parallel_request_limiter_v3.py (modified, +77/-0)
  • tests/test_litellm/test_secret_redaction.py (modified, +102/-7)
  • ui/litellm-dashboard/public/assets/logos/qohash.jpg (added, +0/-0)
  • ui/litellm-dashboard/src/components/guardrails/guardrail_info_helpers.tsx (modified, +2/-0)

Code Example

import litellm

# Use an invalid model to trigger an error
try:
    litellm.completion(
        model="gemini/gemini-2.0-flash-lite",  # deprecated model
        messages=[{"role": "user", "content": "hello"}],
    )
except Exception as e:
    print(str(e))  # Full URL with ?key=AIza... is visible

---

https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent?key=***REDACTED***

---

https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent?key=AIzaSy...full_key_here
RAW_BUFFERClick to expand / collapse

Description

When using the gemini/ provider, LiteLLM passes the API key as a URL query parameter (?key=...). When API calls fail, the full URL — including the key — appears in exception traces.

This means any error logged, captured by an observability tool, or surfaced in a CI/CD run exposes the API key in plaintext.

Reproduction

import litellm

# Use an invalid model to trigger an error
try:
    litellm.completion(
        model="gemini/gemini-2.0-flash-lite",  # deprecated model
        messages=[{"role": "user", "content": "hello"}],
    )
except Exception as e:
    print(str(e))  # Full URL with ?key=AIza... is visible

Expected behavior

The API key should be redacted from error messages and exception traces, e.g.:

https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent?key=***REDACTED***

Actual behavior

The full API key is visible in the error:

https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent?key=AIzaSy...full_key_here

Impact

  • Production logs contain plaintext API keys
  • CI/CD output (GitHub Actions, etc.) may expose keys
  • Error tracking tools (Sentry, Datadog, etc.) capture and store the key
  • Any automated retry/logging wrapper surfaces the key

Environment

  • LiteLLM version: latest (installed via pip)
  • Provider: gemini/ (Google AI Studio)
  • Python 3.12+

extent analysis

TL;DR

Modify the LiteLLM library to redact the API key from error messages and exception traces by not passing the key as a URL query parameter or by masking it in the error output.

Guidance

  • Investigate the LiteLLM library to determine if there's an existing configuration option or parameter to redact or mask the API key in error messages.
  • Consider modifying the library to pass the API key in the Authorization header instead of as a query parameter, which would prevent it from being logged.
  • If modifying the library is not feasible, create a custom error handling mechanism to intercept and redact the API key from exception traces before they are logged or surfaced.
  • Review the library's documentation and source code to understand how error messages are constructed and logged, to identify the best point to intervene and redact the API key.

Example

import litellm
import logging

# Custom error handler to redact API key
def redact_api_key(exception):
    # Assuming the API key is in the exception message
    api_key_pattern = "key=AIza"
    redacted_message = str(exception).replace(api_key_pattern, "key=***REDACTED***")
    logging.error(redacted_message)

try:
    litellm.completion(
        model="gemini/gemini-2.0-flash-lite",  
        messages=[{"role": "user", "content": "hello"}],
    )
except Exception as e:
    redact_api_key(e)

Notes

The provided solution assumes that the LiteLLM library does not currently provide a built-in mechanism for redacting the API key from error messages. If such a mechanism exists, it should be used instead of the custom solution proposed.

Recommendation

Apply a workaround by modifying the error handling to redact the API key, as a more permanent solution may require changes to the LiteLLM library itself, which could take time to implement and release.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

The API key should be redacted from error messages and exception traces, e.g.:

https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent?key=***REDACTED***

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING