Repository navigation
fix: redact provider credentials from logs and API responses - #279
guofeng201507 wants to merge 1 commit into
Conversation
Provider API keys could reach application logs — and in one path the API response — because the key travels in the request URL and `requests` embeds the URL inside its exception messages. - Google Gemini: send the key via the documented `x-goog-api-key` header instead of the `?key=` query parameter, so it never appears in a URL-derived string. - Add `app.utils.redaction.redact_secrets()` and route every exception-derived string through it before logging, re-raising, or returning it (scrubs query-parameter secrets and `Bearer` tokens while keeping host/path so failures stay debuggable). - Apply the same scrub to the settings connection test, where the exception text was both logged and returned to the client as `msg`. An invalid or expired key (HTTP 403) is the most common trigger, so the leak fires exactly when someone is troubleshooting. Backward compatibility: the Gemini change relies on the official `x-goog-api-key` header; a Gemini-compatible proxy that only accepts `?key=` would need updating. Testing: `pytest tests/test_llm_litellm_provider.py` -> 37 passed (3 new regression tests, 34 pre-existing unchanged).
Provider API keys could reach application logs — and in one path the API response — because the key travels in the request URL and `requests` embeds the URL inside its exception messages. The most common trigger is an ordinary failure (invalid key -> HTTP 403, or a network timeout), i.e. exactly when someone is troubleshooting. - Google Gemini: send the key via the documented `x-goog-api-key` header instead of the `?key=` query parameter. - Add `app.utils.redaction.redact_secrets()` and route every exception-derived string through it before logging, re-raising, or returning it (scrubs query-parameter secrets and `Bearer` tokens while keeping host/path so failures stay debuggable). - Apply the same scrub to the settings connection test, where the exception text was logged and returned to the client as `msg`. An upstream PR with the same change is open as OpenByteInc#279. Testing: pytest tests/test_llm_litellm_provider.py -> 37 passed (3 new regression tests, 34 pre-existing unchanged).
Send credentials only in headers (x-goog-api-key for Gemini) and drop the redact_secrets dependency, so the probe does not rely on PR OpenByteInc#279 and can be upstreamed as a standalone patch.
|
Applied this on current Two bare if not use_fallback or current_model == models_to_try[-1]:
raise # re-raises the original exception, key includedWith The minimal fix is to re-raise the same type with a scrubbed message, keeping Two smaller notes:
What I did with it: applied on |
What changed and why
Provider API keys could end up in application logs — and in one path in the API
response — because the key was carried in the request URL and
requestsembedsthe URL inside its exception messages. The most common trigger is an ordinary
failure: an invalid/expired key (HTTP 403) or a network timeout, i.e. exactly
when someone is troubleshooting.
backend_api_python/app/services/llm.py—LLMService._call_google_gemini()url = f"{base_url}/models/{model}:generateContent?key={api_key}"x-goog-api-keyrequest headerbackend_api_python/app/utils/redaction.py(new)redact_secrets()scrubskey=,api_key=,token=,secret=,password=andBearer <token>from a string, keeping the host/path sofailures stay debuggable.
backend_api_python/app/services/llm.pyredact_secrets()beforebeing logged, re-raised, or written into the returned
reportfield: the HTTPerror, request error, invalid-data, aggregate error, alternative-provider
fallback, and the
_llm_postconnection error paths.backend_api_python/app/routes/settings.py—test_connection()msg, so theFinnhub key (
...?symbol=AAPL&token=...) could be displayed in the UI. Bothplaces are now scrubbed.
How to test
Manual: configure the Google provider with a key, make the call fail (invalid
key → 403, unreachable
GOOGLE_BASE_URL, or blockgenerativelanguage.googleapis.com), trigger any AI feature, then check thebackend log. Before:
...:generateContent?key=AIza...; after:...?key=<redacted>.Automated:
Includes 3 new regression tests: the Gemini key is absent from the URL and
present in the header; the scrubber removes query-parameter secrets and bearer
tokens; harmless text passes through unchanged. Run on CPython 3.12 using the
project's own backend image. The 34 pre-existing tests are unchanged.
Backward compatibility
The Gemini change relies on the official
x-goog-api-keyheader, which theGemini REST API documents. A Gemini-compatible proxy that only accepts
?key=would need updating.
Notes
Filed as a normal fix rather than a private vulnerability report — happy to
re-route through the channel described in
SECURITY.mdif you would prefer that.