2.6 KiB
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
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:
# 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:
# 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