Version 2.0.1.9 Released
Date: 7th August 2026
Sandbox and Reliability Release: brings GTT orders to analyzer mode with full live parity; migrates Nubra to OMS V3 and adds IndMoney TOTP login with stop orders; closes a forced-logout CSRF vector and a logout that left users stranded mid-login; and fixes three unbounded-growth defects that degraded a long-running worker over hours
This release spans 72 commits since 2.0.1.8. The headline work is GTT orders in analyzer mode (issue #1740), taken from first implementation through a hardening pass that made every transition atomic, correctly funded and recoverable — the sandbox now mirrors live GTT behaviour rather than approximating it. On the broker side, Nubra was migrated to OMS V3 with phone-OTP login, IndMoney gained TOTP login and stop orders, 5paisa completed a live-verified audit across orders, market data, master contract and order updates, and Shoonya had daily historical data restored via EODChartData. A single class of bug — market data keyed on token alone, which collides across exchanges — was fixed across six Noren-family brokers in one sweep.
Three security items landed: a forced-logout CSRF vector on /auth/logout, a logout that silently did nothing when broker OAuth was never completed, and h2 upgraded past a request-smuggling advisory. Separately, three unbounded-growth defects were fixed — the kind that only surface on a single Gunicorn worker that never restarts, where nothing reclaims what leaks.
Highlights
- Security: forced-logout CSRF on
/auth/logout(4fd69d718) — the endpoint was CSRF-exempt under the comment "safe - only destroys session", which was not true of it: logout revokes the broker token, publishesCACHE_INVALIDATE_ALLto tear down the shared WebSocket feed, clears every device's session and flushes the symbol cache. Flask-WTF never validates GET, andSESSION_COOKIE_SAMESITE="Lax"still attaches the session cookie to top-level cross-site navigations, so a link on any page the user visited could revoke their broker session mid-trading-day. The exemption is gone, and GET is now covered by a fetch-metadata check that honours onlysame-originandnone. The endpoint had been POST-only after43e8a1b5band regressed in74c25ef26; the regression test that should have caught it was passing vacuously against a server that was not running, and has been rewritten offline. - Security: logout left users stranded in a half-logged-in session (
63da7e8d5, #1762, #1756) —session.clear()sat inside anif session.get("logged_in")guard, so a session with the password step done but broker OAuth not completed survived logout entirely and/auth/loginbounced straight back to/broker, with no in-app escape. That state is also the normal path every morning after the ~03:00 IST broker token expiry, not just an abandoned login. The session is now cleared unconditionally and before teardown, so a failure in revocation cannot strand the user; the heavy side effects stay gated on a fully active session, since running them for a half-logged-in one would kill the live feed on the user's other devices (#1591). It also closed an incomplete-logout weakness: the residualsession["user"]was enough to complete broker OAuth via the CSRF-exemptbrlogin.broker_callback. Reported and fixed by Sebastian Legarraga. - Security:
h24.4.1 (9c664d8f6,087a20e4c, GHSA-6hr6-w5qg-qmwg) — a duplicateHostheader could facilitate request smuggling.h2is on the hot path rather than incidental:utils/httpx_clientenables HTTP/2 for the shared client, so every broker API call negotiates through it. 4.4.1 requireshpack>=4.2, so that pin moved too, and the advisory was filed against four manifests —pyproject.toml,uv.lock,requirements.txtandrequirements-nginx.txt— all of which now agree. - Sandbox: GTT orders in analyzer mode (
3c001183a,5df370d7c, #1740) — GTT support in the sandbox with websocket monitoring, catch-up processing, expiry and reconciliation, reaching parity with the live path. - Sandbox: GTT correctness hardening (
551805904,13f896da7,67e2b521a,8c4ec5abe,54baa011c,bfc43cb5a,f001d7cc8) — every GTT transition now moves funds and state in a single commit, claims conditionally, and is recoverable; no state change is published unless the funds actually moved; order correlation is durable with no double-block and an honest cancel; the cancel/fire race is closed and immutability honoured. A release-blocker pass corrected direction, OCO, cancel, margin, ticks and payload shape. - Sandbox: fund-accounting integrity (
f91567238,38ab7b62c,bbff5fbc7) — staged margin is computed in SQL rather than Python, closing a lost-update window; margin release can no longer exceed what was reserved; and every committing fund path is guarded, with three tests that were passing without asserting anything made real. - Sandbox: execution cycles no longer scale with resting orders (
b6cf20595, #1768) —check_and_execute_pending_ordersslept 1s per 10 pending orders, so a cycle costceil(N/10)-1seconds on queue depth alone — 14s behind 150 resting orders — and paid it even when nothing was fillable. The sleep protected nothing: quotes for the whole cycle are fetched up front in one batch, leaving only dict lookups and local writes with no broker call to throttle. It compounded, since a slower cycle stranded more orders. Measured, one order's time-to-fill went from 4.6s with no backlog to 34.8s behind 150; now flat. - EventBus and Flow lock registry no longer grow without bound (
42776df82, #1739) —EventBusdispatched through aThreadPoolExecutorwhose work queue is an unboundedSimpleQueue, so a publisher outrunning its subscribers grew it until the process was OOM-killed. Publishing now reserves one of 1000 in-flight slots and sheds load when saturated, which is the right trade for best-effort side effects and avoids pushing the stall back into the order path. The Flow workflow-lock registry moved to aWeakValueDictionaryso entries track workflows executing now rather than every workflow ever run — deliberately not a size-capped dict, since evicting a lock a thread is holding would let the same workflow execute twice and place every order in it twice. - Nubra: OMS V3 migration (
97a196a0c,ad144314a,67980aef0,79644572d,e512abe08,94bdc3e54,8cac1c4b0) — migrated to OMS V3 with phone-OTP login, corrected position fields, F&O exchange mapping and history window; session expiry now propagates through depth, multiquotes and cancel-all instead of surfacing as a generic failure; index history returns candles again (tick_volumeis invalid for INDEX); and exchange mapping moved intotransform_dataper the standard broker layout. - IndMoney: TOTP login and stop orders (
7834da0d9,1be1d9238,2f9f3b32e,2a991a8d9,43e3ad2a6) — TOTP login, stop orders and live-verified feed fixes, with REST calls paced against one shared per-category clock, the underlying root stored inSymToken.name, the order-id cache scoped per user, and token rejection hardened. - 5paisa: live-verified audit (
2d230d1d4, #1772) — orders, market data, master contract and order updates verified against a live session. - Shoonya: daily historical data restored (
b5cdf635e,376f3c313,3ac3ced43) — daily candles now come fromEODChartData, the date-only fallback is pinned to 00:00 UTC, and an incomplete master-contract row is skipped rather than aborting the whole exchange. - Six brokers: market data keyed on exchange and token (
e6b7e0028,e5f11b690,b1eac93a3,baa9cbb53,7349d24ae,c1476766e) — Shoonya, Flattrade, Zebu, Tradesmart, Definedge and Firstock all keyed their market data cache or routing on token alone, which collides across exchanges. Fixed uniformly. - Tools: MCX and NCO option chains (
dc233afa9,fe3a01826,21bd191a0,ae6d85756, #1747, #1748) — MCX option chains are priced against the near-month future rather than a spot that does not exist; NCO and BCD chains and expiries are supported; MCX appears in the options tools with NCO accepted at the API boundary; and expiry lookups no longer return a mix of options and futures. - Flow: explainability (
1139da6c4,59617068e,efb1f82c4) — a diagnostic that explains why a workflow placed no orders, a persisted node trace so a run that did nothing can still be examined, and the market-hours window taken from the calendar with a per-flow override. - Market calendar: NFO/BFO close moved to 15:40 (
e0f7536a6) — for SEBI's Closing Auction Session. - Startup and scheduling (
f708aaa1f,4e6d370fb,eb54e9d1c,413534d8c) — the SQLite startup lock is fixed by serializing init and cleaning sessions, and the APScheduler job store tables are created both at db init and in the migrations, so existing installations get them too. - Reliability — Historify releases its scoped sessions when a scheduled download finishes (
730cd7dc7); the order-update adapter no longer connects or retries against a dead broker token (7b6db0bc7); GTT latency is retained rather than purged after a week (7433e5f7c); Tradesmart quotes are paced at the new 100/sec cap separately from history (f5705c4f0); and the chart terminal no longer offers CNC on MCX, NCO and CDS, where only NRML applies (694795e01). - Testing and CI — the CI-safe backend suite grew from 5 tests to 35. Two files in the allowlist had been passing without asserting anything, because they drive a live server that CI does not run; both are rewritten offline. New regression coverage guards the logout paths, the sandbox execution backlog, and the EventBus and lock-registry bounds.
Dependencies
h2: upgraded to 4.4.1 (GHSA-6hr6-w5qg-qmwg)hpack: upgraded to 4.2.0 (required byh24.4.1)
Upgrading
git pull on main is the canonical path — frontend/dist is tracked there and rebuilt by CI, so no Node.js is needed on the server. Run cd upgrade && uv run migrate_all.py to pick up the APScheduler job store tables on an existing installation.