Skip to content

Polish bundle: HTTP transport DX (http2 extra, per-request headers, Retry-After date form, trailing-slash signing, RecordingTransport headers, extra_headers docs) #220

Closed

Description

@TexasCoding

Summary

Bundle of small HTTP transport / auth DX items. Each is low individual severity but collectively meaningful for "professional-grade" feel.

1. http2=True opaque ImportError; declare h2 extra

KalshiConfig(http2=True) lazily imports h2 only when the first request fires, surfacing ImportError("Using http2=True requires the package h2 ..."). pyproject.toml exposes only pandas, polars, all — no http2 extra.

Fix: add http2 = ["h2>=4,<5"] to [project.optional-dependencies] (include in all). In KalshiConfig.__post_init__, if self.http2 is True, check importlib.util.find_spec('h2') and raise ValueError("http2=True requires the 'h2' package — install with pip install kalshi-sdk[http2]").

2. No per-request header override

SyncTransport.request / AsyncTransport.request only accept params and json. Resource methods cannot attach X-Request-ID, W3C traceparent, tenant headers, etc.

Fix: add extra_headers: dict[str, str] | None = None to both transports' request() signature. Merge as {**config.extra_headers, **(extra_headers or {}), **auth_headers} so signature headers always win.

3. Retry-After HTTP-date form ignored

RFC 7231 §7.1.3 permits Retry-After as delta-seconds OR an HTTP-date. The SDK only parses delta-seconds and silently falls back to computed backoff on ValueError. Safe but masks the signal.

Fix: on ValueError, attempt email.utils.parsedate_to_datetime(retry_after) and convert to delta-seconds via (dt - datetime.now(tz=timezone.utc)).total_seconds(). Cap at retry_max_delay. Debug-log when falling back.

4. extra_headers docs claim wrong

docs/configuration.md:83-85 says extra_headers can override anything the SDK sets. In reality, per-request KALSHI-ACCESS-* headers always win (correct security behavior; wrong doc claim).

Fix: Rewrite the docs paragraph: extra_headers is set at httpx.Client level and is OVERRIDDEN by the SDK's per-request auth headers; it can only override client-level defaults like User-Agent. Optionally add an assertion at transport init that rejects user-supplied KALSHI-ACCESS-* keys in extra_headers.

5. RecordingTransport persists response headers verbatim

The module-level warning calls out that recorded fixtures contain the full response body but not that ALL response headers are also written. Custom X-Kalshi-* headers, rate-limit headers, request IDs, server timestamps get persisted with no scrubbing.

Fix: Update the docstring warning to mention response headers. Add an optional response_header_filter: Callable[[str, str], bool] | Iterable[str] | None parameter to RecordingTransport/AsyncRecordingTransport that defaults to dropping a small allowlist-violating set (Set-Cookie, Authorization, anything matching (?i)^x-kalshi-.*-(id|key|account|user).*$).

6. Trailing-slash signing mismatch foot-gun

sign_request strips a trailing slash from the signing payload, but the path passed to httpx is unchanged. Today no resource path uses a trailing slash, so the two payloads match. A future self._get("/markets/") would produce a request whose wire path is /trade-api/v2/markets/ while the signed message is …/markets → opaque 401.

Fix: canonicalize the trailing slash at the caller (transport request()) so wire URL and signed payload share canonicalization. OR assert in sign_request that the caller's path does not end with / (except literal root) and raise a clear ValueError.

Severity & category

Bundle of low items, mostly dx + security + infra.

Activity

  1. added
    infraInfrastructure/tooling
    polishCode-quality and DX improvements; non-functional
    securitySecurity-related concern
    on May 21, 2026
  2. added a commit that references this issue on May 21, 2026
    7785e10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    infraInfrastructure/toolingpolishCode-quality and DX improvements; non-functionalsecuritySecurity-related concern

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions