perps: config/init hardening — host-allowlist split, ws_base_url + password kwargs, config-vs-shorthand guard (#406, #412, #410)#416
Conversation
…ssword kwargs, config-vs-shorthand guard Closes #412, closes #406, closes #410. Pre-release polish (3.2.0 unreleased). #412 — PerpsConfig validated both base_url and ws_base_url against the UNION of REST+WS hosts, so a swapped host (REST URL on a WS host, or vice versa) was accepted; and ws_base_url had no path check. Now base_url validates against `_PERPS_REST_HOSTS` and ws_base_url against `_PERPS_WS_HOSTS`, and ws_base_url must carry the `/trade-api/ws/v2/margin` path (mirrors the REST path check). Removed the now-unused `_PERPS_KNOWN_HOSTS` union. #406 — (a) passing `config=` together with `demo=True`/`base_url`/`ws_base_url` silently ignored the shorthand (a real-money footgun when demo=True is dropped); now raises. (b) added a `ws_base_url` kwarg to both clients + `from_env` (`KALSHI_PERPS_WS_BASE_URL`) so a REST override no longer leaves the WS feed on prod — the split is caught by PerpsConfig's split-env guard if mismatched. `from_env` only injects env endpoint overrides when no explicit `config=` is passed, so the new guard isn't tripped by env defaults. #410 — exposed a `password=` kwarg on both clients + `from_env` for encrypted private keys, threaded to `KalshiAuth.from_key_path`/`from_pem` and `try_perps_auth_from_env` (which already accepted it). `from_env` reads `KALSHI_PERPS_PRIVATE_KEY_PASSPHRASE`. Tests: host-swap + WS-path rejection (#412); config+shorthand guards + ws_base_url threading (#406); encrypted-PEM password threading, sync + async (#410). mypy strict + ruff + 1131 perps/contract tests green.
Code Review — perps config/init hardening (#406, #412, #410)OverviewThree focused security/correctness hardenings for the perps client:
The motivation is solid, the code is clean, and the sync/async parity is maintained. A few things worth addressing before merge: Issues1. Missing
|
Cluster B of the post-review follow-ups — three config/client-construction hardenings. Pre-release polish (3.2.0 unreleased), so the new
ws_base_url/passwordkwargs are additive with no breaking change.#412 — host-allowlist split + WS path check (security)
PerpsConfigvalidated bothbase_urlandws_base_urlagainst the union_PERPS_KNOWN_HOSTS, so a REST URL on a WS host (or vice versa) was accepted; andws_base_urlhad no path check.base_urlnow validates against_PERPS_REST_HOSTS,ws_base_urlagainst_PERPS_WS_HOSTS.ws_base_urlpath check requiring/trade-api/ws/v2/margin(mirrors the REST/trade-api/v2check)._PERPS_KNOWN_HOSTSunion.#406 — client init footguns
PerpsClient(config=…, demo=True)silently connected to whateverconfigspecified, ignoringdemo— a real-money footgun. Now combiningconfig=withdemo/base_url/ws_base_urlraises.ws_base_urlkwarg to both clients +from_env(KALSHI_PERPS_WS_BASE_URL), so a REST override no longer silently leaves the WS feed on prod (a mismatch is caught byPerpsConfig's split-env guard).from_envonly injects env endpoint overrides when no explicitconfig=is passed, so the new guard isn't tripped by env defaults.#410 — encrypted-key passphrase
Exposed a
password=kwarg on both clients +from_envfor encrypted private keys, threaded toKalshiAuth.from_key_path/from_pemandtry_perps_auth_from_env(the underlying signer already accepted it).from_envreadsKALSHI_PERPS_PRIVATE_KEY_PASSPHRASE.Verification
mypy --strict+ruffclean; 1131 perps + contract-drift tests pass.ws_base_urlthreading (perps: client init footguns — config= silently ignores demo/base_url, and non-demo base_url override leaves WS on prod #406); encrypted-PEM password threading sync and async (perps: exposepassword=for encrypted private keys in client kwargs + from_env (shared with core client) #410, non-vacuous — fails without the fix since the encrypted key can't load).🤖 Generated with Claude Code