Skip to content

fix: redact provider credentials from logs and API responses - #279

Open
guofeng201507 wants to merge 1 commit into
OpenByteInc:mainfrom
guofeng201507:fix/redact-provider-credentials
Open

guofeng201507 wants to merge 1 commit into
OpenByteInc:mainfrom
guofeng201507:fix/redact-provider-credentials

Conversation

@guofeng201507

Copy link
Copy Markdown

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 requests embeds
the 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()

  • before: url = f"{base_url}/models/{model}:generateContent?key={api_key}"
  • after: the key is sent in the documented x-goog-api-key request header

backend_api_python/app/utils/redaction.py (new)

  • redact_secrets() scrubs key=, api_key=, token=, secret=,
    password= and Bearer <token> from a string, keeping the host/path so
    failures stay debuggable.

backend_api_python/app/services/llm.py

  • every exception-derived string now passes through redact_secrets() before
    being logged, re-raised, or written into the returned report field: the HTTP
    error, request error, invalid-data, aggregate error, alternative-provider
    fallback, and the _llm_post connection error paths.

backend_api_python/app/routes/settings.py — test_connection()

  • the exception text was both logged and returned to the client as msg, so the
    Finnhub key (...?symbol=AAPL&token=...) could be displayed in the UI. Both
    places 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 block
generativelanguage.googleapis.com), trigger any AI feature, then check the
backend log. Before: ...:generateContent?key=AIza...; after:
...?key=<redacted>.

Automated:

pytest tests/test_llm_litellm_provider.py
# 37 passed

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-key header, which the
Gemini 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.md if you would prefer that.

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).
guofeng201507 added a commit to guofeng201507/SharkQuantDinger that referenced this pull request Oct 9, 2026
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).
guofeng201507 added a commit to guofeng201507/SharkQuantDinger that referenced this pull request Oct 9, 2026
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.
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.

1 participant