Problem
The 16 KB MAX_ERROR_BODY_BYTES guard added in #203 only fires inside _map_error. Every 2xx response is parsed via response.json() (or a caller-supplied rest_json_loads(response.content)) with no upstream size check. A misconfigured reverse proxy, a hijacked DNS entry pointing at an attacker-controlled host, or a Kalshi backend regression that streams an oversize list-orders payload will eagerly materialize the entire body into memory — potentially OOM'ing a serverless function or stalling trading.
The asymmetry is notable: the project considered 16 KB worth bounding on the rare error path, but the success path — far more frequently reachable for an attacker who can swap a 200 body via a compromised proxy — is unbounded.
Evidence
kalshi/_base_client.py:45,57-64 — error-only size guard:
MAX_ERROR_BODY_BYTES = 16 * 1024
...
content_length = response.headers.get("content-length")
suppressed: bool = False
if content_length:
try:
if int(content_length) > MAX_ERROR_BODY_BYTES:
suppressed = True
except ValueError:
pass
kalshi/resources/_base.py:142-151 — success path materializes the whole body unconditionally:
def _load_json(self, response: httpx.Response) -> Any:
loader = self._transport._config.rest_json_loads
if loader is None:
return response.json()
return loader(response.content)
Suggested fix
Mirror the _map_error Content-Length guard on the success path. Add a KalshiConfig.max_response_bytes (e.g. 64 MiB default) and raise KalshiError("response exceeds max_response_bytes") in _load_json when the advertised Content-Length exceeds the cap. For chunked transfer (no Content-Length), use response.iter_bytes() and short-circuit when the running total trips the cap. Apply symmetrically to both SyncResource._load_json and AsyncResource._load_json.
Source
Round-3 independent audit (reviewer: http_transport).
Problem
The 16 KB
MAX_ERROR_BODY_BYTESguard added in #203 only fires inside_map_error. Every 2xx response is parsed viaresponse.json()(or a caller-suppliedrest_json_loads(response.content)) with no upstream size check. A misconfigured reverse proxy, a hijacked DNS entry pointing at an attacker-controlled host, or a Kalshi backend regression that streams an oversize list-orders payload will eagerly materialize the entire body into memory — potentially OOM'ing a serverless function or stalling trading.The asymmetry is notable: the project considered 16 KB worth bounding on the rare error path, but the success path — far more frequently reachable for an attacker who can swap a 200 body via a compromised proxy — is unbounded.
Evidence
kalshi/_base_client.py:45,57-64— error-only size guard:kalshi/resources/_base.py:142-151— success path materializes the whole body unconditionally:Suggested fix
Mirror the
_map_errorContent-Length guard on the success path. Add aKalshiConfig.max_response_bytes(e.g. 64 MiB default) and raiseKalshiError("response exceeds max_response_bytes")in_load_jsonwhen the advertisedContent-Lengthexceeds the cap. For chunked transfer (noContent-Length), useresponse.iter_bytes()and short-circuit when the running total trips the cap. Apply symmetrically to bothSyncResource._load_jsonandAsyncResource._load_json.Source
Round-3 independent audit (reviewer:
http_transport).