From Wave 5 security audit, findings F-O-05 and F-O-09. Severity: medium.
F-O-05 — Pydantic ValidationError dumped via `exc_info=True` includes user trade data
`kalshi/ws/dispatch.py:88-95` does:
```python
logger.warning("Failed to parse %s message", msg_type, exc_info=True)
```
Pydantic v2's `ValidationError` repr includes the input dict that failed validation. For private channels — `fill`, `user_order`, `market_positions` — that dict contains user-identifiable trade data: order IDs, sizes in USD, side, ticker, fill prices, the user's `client_order_id`. Operators running at `WARNING` log level (default) or shipping warnings into a SIEM/Splunk/Datadog write financial PII to log infrastructure that's typically lower-trust than the trading environment.
Fix: Drop `exc_info=True` from the parse-failure log, or replace with a sanitized message like `logger.warning("Failed to parse %s message (validation error)", msg_type)`. Gate the full traceback behind an opt-in `KALSHI_DEBUG_WS=1` env flag.
Also: `dispatch.py:73` does `logger.warning("Received non-JSON frame: %s", raw[:100])` — log only the length and first/last 16 bytes, not 100 chars of arbitrary payload.
F-O-09 — `KalshiError` interpolates httpx exception strings (full URLs)
`kalshi/_base_client.py:136, 144, 249, 257` and `ws/connection.py:117-120`:
```python
KalshiError(f"Request timed out: {e}")
KalshiError(f"HTTP error: {e}")
KalshiConnectionError(f"WebSocket connection failed: {e}")
```
`str(e)` on an httpx exception typically contains the full URL including query string. For private endpoints (`/portfolio/positions?ticker=...`) the query isn't sensitive on Kalshi's surface — but anyone constructing a URL with a token-like value in a query param leaks it into uncaught-exception sinks (Sentry, stderr).
Fix: Strip the URL from `e` before interpolation, or use a fixed message string and rely on `cause` (`raise ... from e`) for the detail. Most error trackers serialize `cause` anyway.
From Wave 5 security audit, findings F-O-05 and F-O-09. Severity: medium.
F-O-05 — Pydantic ValidationError dumped via `exc_info=True` includes user trade data
`kalshi/ws/dispatch.py:88-95` does:
```python
logger.warning("Failed to parse %s message", msg_type, exc_info=True)
```
Pydantic v2's `ValidationError` repr includes the input dict that failed validation. For private channels — `fill`, `user_order`, `market_positions` — that dict contains user-identifiable trade data: order IDs, sizes in USD, side, ticker, fill prices, the user's `client_order_id`. Operators running at `WARNING` log level (default) or shipping warnings into a SIEM/Splunk/Datadog write financial PII to log infrastructure that's typically lower-trust than the trading environment.
Fix: Drop `exc_info=True` from the parse-failure log, or replace with a sanitized message like `logger.warning("Failed to parse %s message (validation error)", msg_type)`. Gate the full traceback behind an opt-in `KALSHI_DEBUG_WS=1` env flag.
Also: `dispatch.py:73` does `logger.warning("Received non-JSON frame: %s", raw[:100])` — log only the length and first/last 16 bytes, not 100 chars of arbitrary payload.
F-O-09 — `KalshiError` interpolates httpx exception strings (full URLs)
`kalshi/_base_client.py:136, 144, 249, 257` and `ws/connection.py:117-120`:
```python
KalshiError(f"Request timed out: {e}")
KalshiError(f"HTTP error: {e}")
KalshiConnectionError(f"WebSocket connection failed: {e}")
```
`str(e)` on an httpx exception typically contains the full URL including query string. For private endpoints (`/portfolio/positions?ticker=...`) the query isn't sensitive on Kalshi's surface — but anyone constructing a URL with a token-like value in a query param leaks it into uncaught-exception sinks (Sentry, stderr).
Fix: Strip the URL from `e` before interpolation, or use a fixed message string and rely on `cause` (`raise ... from e`) for the detail. Most error trackers serialize `cause` anyway.