You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
KalshiConfig.extra_headers is mutable post-construction, bypassing the #298 auth-header guard #313
#298 closed the auth-header forge surface at two boundaries: per-request extra_headers= (rejected in SyncTransport.request / AsyncTransport.request) and config construction (KalshiConfig.__post_init__). But @dataclass(frozen=True) only forbids attribute reassignment — the dict stored in extra_headers is still mutable. A caller that mutates the dict after construction (config.extra_headers["kalshi-access-key"] = "forged") re-introduces exactly the forge surface #298 set out to close, because neither the per-request guard nor __post_init__ re-runs on the mutated dict.
The forged lowercase kalshi-access-key survives _ci_merge (no case-twin in base_headers), and the final merged_headers = {**base_headers, **auth_headers} is case-sensitive — so kalshi-access-key (forged) and KALSHI-ACCESS-KEY (SDK-signed) ship as two distinct raw header lines on httpx 0.28.
Evidence
kalshi/config.py:71 — extra_headers stored as a plain dict
kalshi/_base_client.py:255-291 — request path reads self._config.extra_headers straight into _ci_merge, and only the per-call extra_headers kwarg is validated
Freeze the dict at construction so external mutation is ineffective. Mirror the _closed/idempotent pattern: in __post_init__, after validation, replace self.extra_headers with types.MappingProxyType(dict(self.extra_headers)) (or a frozendict shim if one is preferred). Belt-and-suspenders: in SyncTransport.request / AsyncTransport.request, also call _assert_no_auth_headers(self._config.extra_headers) once at the top — cheap, and keeps the contract explicit.
Add a regression test that mutates config.extra_headers after construction and asserts the next request() either raises or refuses to ship the forged header.
Activity
fix(security): KalshiConfig.extra_headers immutable post-construction…
release: 2.7.0 — post-v2.6 independent multi-LLM reviewer SDK audit c…