Public mode

No access to your tenant or devices. Every item links to its source. What this means

Some sources, documents or advisories need attention: 275/275 feeds/APIs · 469/469 docs · 7/9 advisories · checked 00:03 UTC Customize Public modeDiscuss ChangeIntel on Discord

Phoenix: Presence keys colliding with `Object.prototype` members break existence checks

Exact source revision recorded 24 Oct 2026 13:17:17 UTC. It does not establish the source's current position. Latest evidence.

osv · GHSA-63mc-hw7g-86rr

Published

### Summary The Phoenix JavaScript presence client (`assets/js/phoenix/presence.js`) tests whether a presence already exists using a bare truthiness check (`state[key]`) rather than an own-property check. Because applications commonly track presences under a client-supplied username or id, the presence key can be attacker-controlled. A user who joins a channel and picks a key that names an `Object.prototype` member (`__proto__`, `constructor`, `toString`, `hasOwnProperty`, and similar) makes the lookup return the inherited `Object.prototype` object instead of `undefined`, which is truthy. The code then reads `.metas.map(...)` off it and throws an uncaught `TypeError`, breaking presence sync for every viewer of that channel topic. Any authenticated channel participant can trigger it. ### Details The victim is any browser subscribed to a presence channel. When it receives the server's `presence_state` message, it invokes `Presence.syncState`, which iterates the incoming presences and checks whether each one already exists locally via `let currentPresence = state[key]`. `state` is a plain object inheriting from `Object.prototype`. For an ordinary key like `alice`, `state["alice"]` is `undefined` (falsy) and the safe path runs. For the key `__proto__` (or `constructor`, `toString`, etc.), `state["__proto__"]` does not resolve to a tracked presence but to JavaScript's built-in `Object.prototype`, which is truthy. The `if(currentPresence)` guard passes, and the code evaluates `currentPresence.metas.map(m => m.phx_ref)`. Since `Object.prototype.metas` is `undefined`, calling `.map` on it throws a `TypeError`. Phoenix wraps no try/catch around channel binding callbacks, so the `TypeError` propagates out of the message handler: `this.state` is never updated and `onSync()` never fires. The malicious key is tracked server-side, so it is re-pushed on every presence update and keeps re-throwing, leaving presence permanently broken until the attacker leaves. `Presence.syncDiff` uses the same unsafe `state[key]` existence-check pattern, so presence diffs fail identically. Two scoping points matter. The impact is per channel topic, not global: presence state is per-topic on the server and per-`Presence`-instance in the browser, so only viewers of the topic carrying the malicious key are affected. The bug is a read-time confusion of the prototype object, not prototype pollution: the crash occurs on the `state["__proto__"]` read in `syncState`, before any `state[key] = ...` write is reached, so `Object.prototype` is never mutated and nothing leaks across channels. The fix builds the state and accumulator objects with `Object.create(null)` (or a `Map`) and gates existence checks with `Object.prototype.hasOwnProperty.call(obj, key)`. If an application does not pass a client-controlled key to `Presence.track`, it is **not** affected. ### PoC 1. Connect to an application that uses `Phoenix.Presence` and tracks presences under a client-chosen key (e.g. a username). 2. Join a presence channel choosing the key `__proto__` (or `constructor`, `toString`, `hasOwnProperty`). 3. The server tracks the presence and pushes `presence_state` / `presence_diff` to every subscriber of that topic. 4. Each viewer's `Presence.syncState` (or `syncDiff`) reads `state["__proto__"]`, gets the truthy `Object.prototype`, and throws an uncaught `TypeError`. 5. Presence sync stays broken for all viewers of the topic until the attacker leaves the channel. ### Impact An attacker with ordinary channel access can cause a persistent, stored client-side denial of service against every browser viewing a presence channel topic, freezing presence updates for all of them until the attacker disconnects. Any application driving the Phoenix JavaScript presence client with user-influenced presence keys is affected.

Source published
3 Sep 2026 20:30:33 UTC
Source modified
3 Sep 2026 20:45:04 UTC
Observed here
24 Oct 2026 13:17:17 UTC
Source terms
GitHub Advisory Database · CC-BY-4.0

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS_V4 · CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N

Publisher rating · MODERATE

ChangeIntel

An IT change radar: releases, security, known issues, retirements, documentation changes, and service status from public sources. Every item links to supporting evidence; dates and statuses can change after they are read.

Sources read 8 Nov 00:03 UTC · 275 of 275 readable · 273 fully read · documentation 469/469 current