Skip to content

Commit 5447f02

Browse files
committed
Release v1.7.24
1 parent b90b225 commit 5447f02

11 files changed

Lines changed: 84 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ styled with Tailwind CSS by default. You can opt-out of Tailwind CSS with the `-
4646
flag (the Tailwind CSS classes are kept in the generated components as reference for
4747
future styling).
4848

49+
## 1.7.24 (2026-07-07)
50+
51+
### Security fixes
52+
- [CVE-2026-56811](https://github.com/phoenixframework/phoenix/security/advisories/GHSA-6983-jfq8-485w): Add a `max_channels_per_transport` option (defaulting to 100) to prevent a single client from spawning an unbounded number of channels (processes), eventually exhausting the server's memory or process limit.
53+
- [CVE-2026-56812](https://github.com/phoenixframework/phoenix/security/advisories/GHSA-63mc-hw7g-86rr): Prevent presence keys from colliding with `Object.prototype` properties members, crashing the JS Presence client
54+
- Enforce longpoll batch size introduced in 1.7.22. This is additional hardening against [CVE-2026-32689](https://github.com/phoenixframework/phoenix/security/advisories/GHSA-628h-q48j-jr6q). If your application sends events with a very high frequency and uses long polling, such that a single longpoll request would exceed 100 events, you should update to 1.7.23 first.
55+
4956
## 1.7.23 (2026-05-06)
5057

5158
### Bug fixes

installer/mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ end
66
defmodule Phx.New.MixProject do
77
use Mix.Project
88

9-
@version "1.7.23"
9+
@version "1.7.24"
1010
@scm_url "https://github.com/phoenixframework/phoenix"
1111

1212
# If the elixir requirement is updated, we need to update:

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ defmodule Phoenix.MixProject do
88
end
99
end
1010

11-
@version "1.7.23"
11+
@version "1.7.24"
1212
@scm_url "https://github.com/phoenixframework/phoenix"
1313

1414
# If the elixir requirement is updated, we need to make the installer

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "phoenix",
3-
"version": "1.7.23",
3+
"version": "1.7.24",
44
"description": "The official JavaScript client for the Phoenix web framework.",
55
"license": "MIT",
66
"module": "./priv/static/phoenix.mjs",

priv/static/phoenix.cjs.js

Lines changed: 22 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

priv/static/phoenix.cjs.js.map

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

priv/static/phoenix.js

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ var Phoenix = (() => {
761761
var Presence = class _Presence {
762762
constructor(channel, opts = {}) {
763763
let events = opts.events || { state: "presence_state", diff: "presence_diff" };
764-
this.state = {};
764+
this.state = /* @__PURE__ */ Object.create(null);
765765
this.pendingDiffs = [];
766766
this.channel = channel;
767767
this.joinRef = null;
@@ -818,9 +818,10 @@ var Phoenix = (() => {
818818
* @returns {Presence}
819819
*/
820820
static syncState(currentState, newState, onJoin, onLeave) {
821-
let state = this.clone(currentState);
822-
let joins = {};
823-
let leaves = {};
821+
let state = this.toNullProtoObj(this.clone(currentState));
822+
newState = this.toNullProtoObj(newState);
823+
let joins = /* @__PURE__ */ Object.create(null);
824+
let leaves = /* @__PURE__ */ Object.create(null);
824825
this.map(state, (key, presence) => {
825826
if (!newState[key]) {
826827
leaves[key] = presence;
@@ -857,6 +858,7 @@ var Phoenix = (() => {
857858
* @returns {Presence}
858859
*/
859860
static syncDiff(state, diff, onJoin, onLeave) {
861+
state = this.toNullProtoObj(state);
860862
let { joins, leaves } = this.clone(diff);
861863
if (!onJoin) {
862864
onJoin = function() {
@@ -914,6 +916,22 @@ var Phoenix = (() => {
914916
static map(obj, func) {
915917
return Object.getOwnPropertyNames(obj).map((key) => func(key, obj[key]));
916918
}
919+
// Presence keys are chosen on the server and may collide with
920+
// Object.prototype properties ("__proto__", "constructor", ...), so any
921+
// object indexed by presence key must not have a prototype chain
922+
//
923+
// TODO: replace the null-prototype objects with Maps in Phoenix 2.0
924+
// (breaking change for the lower-level static API)
925+
static toNullProtoObj(obj) {
926+
if (Object.getPrototypeOf(obj) === null) {
927+
return obj;
928+
}
929+
let cleaned = /* @__PURE__ */ Object.create(null);
930+
Object.getOwnPropertyNames(obj).forEach((key) => {
931+
cleaned[key] = obj[key];
932+
});
933+
return cleaned;
934+
}
917935
static clone(obj) {
918936
return JSON.parse(JSON.stringify(obj));
919937
}

priv/static/phoenix.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

priv/static/phoenix.mjs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ var LongPoll = class {
732732
var Presence = class _Presence {
733733
constructor(channel, opts = {}) {
734734
let events = opts.events || { state: "presence_state", diff: "presence_diff" };
735-
this.state = {};
735+
this.state = /* @__PURE__ */ Object.create(null);
736736
this.pendingDiffs = [];
737737
this.channel = channel;
738738
this.joinRef = null;
@@ -789,9 +789,10 @@ var Presence = class _Presence {
789789
* @returns {Presence}
790790
*/
791791
static syncState(currentState, newState, onJoin, onLeave) {
792-
let state = this.clone(currentState);
793-
let joins = {};
794-
let leaves = {};
792+
let state = this.toNullProtoObj(this.clone(currentState));
793+
newState = this.toNullProtoObj(newState);
794+
let joins = /* @__PURE__ */ Object.create(null);
795+
let leaves = /* @__PURE__ */ Object.create(null);
795796
this.map(state, (key, presence) => {
796797
if (!newState[key]) {
797798
leaves[key] = presence;
@@ -828,6 +829,7 @@ var Presence = class _Presence {
828829
* @returns {Presence}
829830
*/
830831
static syncDiff(state, diff, onJoin, onLeave) {
832+
state = this.toNullProtoObj(state);
831833
let { joins, leaves } = this.clone(diff);
832834
if (!onJoin) {
833835
onJoin = function() {
@@ -885,6 +887,22 @@ var Presence = class _Presence {
885887
static map(obj, func) {
886888
return Object.getOwnPropertyNames(obj).map((key) => func(key, obj[key]));
887889
}
890+
// Presence keys are chosen on the server and may collide with
891+
// Object.prototype properties ("__proto__", "constructor", ...), so any
892+
// object indexed by presence key must not have a prototype chain
893+
//
894+
// TODO: replace the null-prototype objects with Maps in Phoenix 2.0
895+
// (breaking change for the lower-level static API)
896+
static toNullProtoObj(obj) {
897+
if (Object.getPrototypeOf(obj) === null) {
898+
return obj;
899+
}
900+
let cleaned = /* @__PURE__ */ Object.create(null);
901+
Object.getOwnPropertyNames(obj).forEach((key) => {
902+
cleaned[key] = obj[key];
903+
});
904+
return cleaned;
905+
}
888906
static clone(obj) {
889907
return JSON.parse(JSON.stringify(obj));
890908
}

0 commit comments

Comments
 (0)