Summary
_map_error calls response.json() (which materializes response.content — entire body into memory) and falls back to response.text or f"HTTP {status}". For a normal Kalshi error this is a few hundred bytes, but a misbehaving upstream (intermediate proxy returning a 502 HTML page, a Cloudflare error page in the multi-MB range, or a deliberately oversized response) is buffered in full and then formatted into an exception message.
There is no Content-Length cap, no streaming read with a size limit, and no truncation when constructing the message. The exception then propagates into user logs verbatim — both a memory and a log-volume risk.
Location
kalshi/_base_client.py:38-44
Evidence
# kalshi/_base_client.py:38-44
try:
body = response.json() # buffers full body
except Exception:
body = {}
message = body.get("message") or body.get("error") or response.text or f"HTTP {status}"
# message may contain MBs of HTML; flows into KalshiError.args
Recommended fix
Cap reads on the error path:
MAX_ERROR_BODY_BYTES = 16 * 1024 # 16KB
MAX_ERROR_MESSAGE_CHARS = 1024
def _map_error(response: httpx.Response) -> KalshiError:
status = response.status_code
# Guard against hostile / huge error payloads
content_length = response.headers.get("content-length")
if content_length and int(content_length) > MAX_ERROR_BODY_BYTES:
return KalshiError(
message=f"HTTP {status} (body {content_length} bytes, suppressed)",
status_code=status,
)
try:
body = response.json()
except Exception:
body = {}
raw = body.get("message") or body.get("error") or response.text or f"HTTP {status}"
message = str(raw)[:MAX_ERROR_MESSAGE_CHARS]
...
Add tests:
test_oversized_error_body_truncated_by_content_length_header
test_message_truncated_to_1024_chars
test_normal_error_body_unchanged
Severity & category
medium / security, correctness
Summary
_map_errorcallsresponse.json()(which materializesresponse.content— entire body into memory) and falls back toresponse.text or f"HTTP {status}". For a normal Kalshi error this is a few hundred bytes, but a misbehaving upstream (intermediate proxy returning a 502 HTML page, a Cloudflare error page in the multi-MB range, or a deliberately oversized response) is buffered in full and then formatted into an exception message.There is no
Content-Lengthcap, no streaming read with a size limit, and no truncation when constructing the message. The exception then propagates into user logs verbatim — both a memory and a log-volume risk.Location
kalshi/_base_client.py:38-44Evidence
Recommended fix
Cap reads on the error path:
Add tests:
test_oversized_error_body_truncated_by_content_length_headertest_message_truncated_to_1024_charstest_normal_error_body_unchangedSeverity & category
medium / security, correctness