java-topology/docs/tickets/phoenix-0001-channel-dispatch-event-intercepts-list-scan.md

77 lines
2.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# phoenix-0001: CWE-407 — channel dispatch `event in event_intercepts` O(n) list scan per subscriber
| Field | Value |
|-------------|-------|
| ID | phoenix-0001 |
| Project | phoenixframework/phoenix |
| Severity | HIGH |
| Status | OPEN |
| CWE | CWE-407 (Algorithmic Complexity — Inefficient Algorithmic Complexity) |
| Found | 2026-03-27 |
## Location
`lib/phoenix/channel/server.ex:100`
```elixir
def dispatch(subscribers, from, %Broadcast{event: event} = msg) do
Enum.reduce(subscribers, %{}, fn
{pid, _}, cache when pid == from ->
cache
{pid, {:fastlane, fastlane_pid, serializer, event_intercepts}}, cache ->
if event in event_intercepts do # <-- CWE-407: O(n) list scan
```
## Root Cause
`event_intercepts` is populated at channel join time from `channel.__intercepts__()`, which
returns `@phoenix_intercepts` — a plain Elixir list accumulated at compile time:
```elixir
# lib/phoenix/channel.ex:456,488,522-525
@phoenix_intercepts []
def __intercepts__, do: @phoenix_intercepts
defmacro intercept(events) do
quote do: @phoenix_intercepts unquote(events)
end
```
The `dispatch/3` function is called once per broadcast event and iterates **every subscriber**
via `Enum.reduce/3`. For each fastlane subscriber it evaluates `event in event_intercepts`,
which is `List.member?/2` — O(k) where k is the number of intercepted events.
Total complexity per broadcast: **O(subscribers × intercepts)**.
## Impact
In a production Phoenix Channels deployment with N subscribers and K intercepted events, every
`broadcast/3` call costs O(N×K) membership tests. For a chat room with 10,000 subscribers and
5 intercepted events, this is 50,000 linear scans per broadcast message.
Channels are the highest-throughput path in Phoenix. Real-time applications (LiveView presence,
multiplayer games, chat) broadcast frequently. This is a genuine hot-path defect.
## Fix
Store `event_intercepts` as a `MapSet` at subscribe time:
```elixir
# lib/phoenix/channel/server.ex:443 — change to MapSet
fastlane = {:fastlane, transport_pid, serializer, MapSet.new(channel.__intercepts__())}
# lib/phoenix/channel/server.ex:100 — already uses `in`, MapSet.member? is called automatically
if event in event_intercepts do # MapSet.member? is O(1) hash lookup
```
The `in` operator in Elixir dispatches to `Enumerable.member?/2`, which for `MapSet` is O(1).
No change to line 100 is needed — only the construction at line 443.
## Patch
`defects/phoenix/patch/phoenix-0001-channel-dispatch-event-intercepts-mapset.patch`
## Benchmark
`defects/phoenix/unit/PhoenixTest.java`