fix(cartesia): redact API keys from websocket handshake errors - #6740
Conversation
aiohttp WSServerHandshakeError embeds X-API-Key in RequestInfo.repr, which leaked via ConnectionPool.prewarm's unretrieved task logs. Wrap handshake failures without chaining the aiohttp cause, and swallow prewarm errors safely. Fixes livekit#6739
| # Swallow the error so asyncio does not log an unretrieved task | ||
| # exception. Use %s (str) rather than %r: aiohttp ClientResponseError | ||
| # embeds request headers (including API keys) in its repr. | ||
| logger.warning("failed to prewarm connection pool: %s", e) |
There was a problem hiding this comment.
this could leak url credentials as well such as ?api_key=... or &jwt_token=...
we can do something like this:
logger.warning(
"failed to prewarm connection pool",
extra={"exception_type": type(e).__name__},
)There was a problem hiding this comment.
Good catch — thanks. I updated the prewarm failure log to only record the exception type via extra, so URL credentials like ?api_key= / &jwt_token= no longer show up in the warning message.
…warm logs Log only exception types on prewarm failure, and break __cause__ chains on generic Cartesia connect errors so auth headers and URL credentials cannot surface in logs.
| leaky = ConnectionError( | ||
| f"wss://api.cartesia.ai/tts/websocket?api_key={SECRET_API_KEY}" | ||
| ) |
There was a problem hiding this comment.
🟡 New test code is not formatted, so the automated formatting check fails
Two newly added multi-line call expressions are written split across lines even though they fit within the 100-character limit (ConnectionError(...) at tests/test_plugin_cartesia_tts.py:62-64), so the repository's mandatory formatting check reports a diff and rejects the change.
Impact: The project's required format check fails on these files, blocking the automated quality gate.
Why ruff format collapses these calls
CONTRIBUTING.md requires running ruff format before committing and AGENTS.md requires make check (which includes format-check). Both new call sites have no magic trailing comma and fit on one line once joined:
tests/test_plugin_cartesia_tts.py:62-64joins to 92 characters.tests/test_connection_pool.py:132-134joins to 98 characters.
ruff format therefore reformats both onto a single line, so make format-check fails.
| leaky = ConnectionError( | |
| f"wss://api.cartesia.ai/tts/websocket?api_key={SECRET_API_KEY}" | |
| ) | |
| leaky = ConnectionError(f"wss://api.cartesia.ai/tts/websocket?api_key={SECRET_API_KEY}") |
Was this helpful? React with 👍 or 👎 to provide feedback.
Co-authored-by: Cursor <cursoragent@cursor.com>
…it#6740) Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
APIStatusError/APIConnectionErrorwithout chaining the aiohttp cause, soX-API-KeyinRequestInfocannot appear in exception/Taskrepr logs.ConnectionPool.prewarm()swallow connect failures and log with%s(str) instead of leaving an unretrieved task exception (the path that published Cartesia API keys).Test plan
pytest tests/test_connection_pool.py tests/test_plugin_cartesia_tts.py -v --unit --plugin cartesiaX-API-Key/ raw API key materialFixes #6739