Skip to content

Success-path response body has no size cap (16 KB cap is error-only) #323

Description

@TexasCoding

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingperformancePerformance / hot-path concern

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions