Skip to content

OrderbookManager mutates Pydantic models in place; consumers see leaked mutations #85

Description

@TexasCoding

From Wave 5 WebSocket audit, finding F-P-10. Severity: medium.

Code

`kalshi/ws/orderbook.py:72-94`:
```python
levels = book.yes if side == "yes" else book.no
...
levels.pop(existing_idx)
levels[existing_idx] = OrderbookLevel(...)
levels.append(...)
levels.sort(key=lambda lv: lv.price)
```

`book.yes` and `book.no` are mutable lists on the `Orderbook` Pydantic model. `OrderbookManager.apply_delta` mutates them directly.

Impact

`_OrderbookIterator.anext` returns `book = self._mgr.get(...)` — the same shared instance every iteration. Subsequent deltas mutate the previously-yielded book. Consumers that snapshot an `Orderbook` by reference (e.g. "state at delta N") silently see future mutations leak in. `Orderbook` is not frozen so the mutation is allowed.

Fix options

  • (a) Document explicitly that the `Orderbook` yielded by `orderbook()` is a live view, not a snapshot, and consumers must `deepcopy` if they want to keep history.
  • (b) Construct a fresh `Orderbook(ticker=..., yes=list(new_yes), no=list(new_no))` per yield. Slight allocation cost, but matches the natural expectation.

(b) is the safer default; (a) is acceptable with prominent doc warnings + maybe an opt-out flag for performance.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingwsWebSocket-related

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions