misc/release/notes.md
Centrifugo is an open-source scalable real-time messaging server. It instantly delivers messages to application online users connected over supported transports (WebSocket, HTTP-streaming, Server-Sent Events (SSE), GRPC, WebTransport). Centrifugo is built around channel subscriptions – so it's a user-facing PUB/SUB server.
Centrifugo is language-agnostic and can be used to build chat apps, live comments, multiplayer games, real-time data visualizations, collaborative tools, AI streaming responses, etc. in combination with any backend. It is well suited for modern architectures and allows decoupling the business logic from the real-time transport layer.
Official client SDKs are available for JavaScript (browser, Node.js, React Native), Dart/Flutter, Swift, Java, Python, Go, and .NET. In addition, Centrifugo supports a unidirectional approach for simple use cases with no SDK dependency.
For details, go to the Centrifugo documentation site. For runnable demos see centrifugal/examples.
TLDR: Proxy http_headers is now transport-only; Need to configure explicit emulated_headers list for emulated headers in Centrifugo server configuration.
Before v6.9.0, the proxy http_headers option forwarded headers from two different sources:
In v6.9.0 these two sources are split into separate options:
| Option | Source | Trust |
|---|---|---|
http_headers | the real connection request only | controllable by your edge (a proxy/gateway can set/strip it) |
emulated_headers | the client's headers emulation map only | always client-controlled — always an untrusted input |
http_headers no longer forwards emulated headers. To forward a header supplied via headers emulation, you must now list it in the new emulated_headers option near http_headers.
Why we had to make this a breaking change. Before v6.9.0 a name listed in http_headers could be filled from either the real connection request or the client's emulation map. That made a trusted header like x-user-id forgeable in a way that is not the operator's mistake:
x-user-id to http_headers for a legitimate reason — their reverse proxy sets it, and they want that identity forwarded to the backend. They have no reason to expect this also lets a client supply x-user-id via emulation, especially if they have never heard of the headers emulation feature.x-user-id only for authenticated requests and leave it fully unset otherwise.x-user-id in its connect frame via emulation. For any request where the proxy did not set the real header, Centrifugo forwarded the client's forged value to the backend, which trusted it as an authenticated identity.A setup where the proxy always sets x-user-id (for example to an empty string even for anonymous requests) is not vulnerable. But Centrifugo cannot inspect your proxy to know whether you did that, and cannot tell a genuine x-user-id from a client-emulated one at request time — so there is no way for Centrifugo to make this safe automatically or to warn only the affected setups.
The only robust fix is structural to make operators aware of the feature: split the sources so http_headers is incapable of carrying a client-emulated value. A name in http_headers is now sourced only from the real HTTP connection; anything a client sends via emulation must be explicitly opted into emulated_headers (and is clearly documented as untrusted input). This removes the possibility of the flaw entirely, rather than relying on every operator to configure their proxy defensively against a feature they may not even know exists.
Are you affected? Only if you rely on headers emulation:
centrifuge-jscentrifuge-dart (web platform only — on native dart:io these are sent as real WebSocket upgrade headers and are not affected)centrifuge-csharpheaders as part of ConnectRequestYou are not affected if you are not using headers at all or only forward real transport headers (e.g. Cookie, Authorization, X-Real-Ip set by your reverse proxy or by native clients) — those keep working under http_headers with no change.
How to migrate: Before upgrading to v6.9.0, add the header names you allow to be sent over headers emulation to emulated_headers proxy configuration object option near http_headers. Apply this change to every proxy that used emulated headers (connect, refresh, channel proxies, RPC proxies, and any named proxies in the top-level proxies list).
{
"client": {
"proxy": {
"connect": {
"enabled": true,
"endpoint": "https://your_backend/centrifugo/connect",
"http_headers": ["X-User-Id", "Authorization"],
"emulated_headers": ["Authorization"]
}
}
}
}
If you can't decide which headers to move right away, but need to update to v6.9.0 – then as a workaround, copy the whole http_headers list into emulated_headers nearby to preserve the pre-v6.9.0 behavior, then trim emulated_headers down to only the names your backend treats as untrusted client input.
Long-term, we will also rename headers to emulated_headers in SDKs that provide this feature.
Please reach out in the community rooms if the help with the migration is needed.
The security issue was reported in GHSA-9468-v6mj-fppw.
ErrConsumerDeleted, ErrConsumerNotFound and ErrStreamNotFound instead of getting permanently stuck (#1166, commit 920d0c23). By @thuy-le-kafi.fa984f0f).Nak, avoiding hot redelivery loops on a poison message (#1175, commit ddda397e).5f6b3871).c591b954).A lot of issues were discovered and then fixed by LLM:
e30aca89).hashtext(channel) integer overflow that could panic shard routing when the hash landed exactly on INT_MIN (#1170, commit b52a3cb4).cd85c450).timestamptz text parsing (#1174, commit 3e163854).2a58bf7f).aee4cb6c).05fb837c).0b59fc3a).4cce70ff).519c27a4).message_size_limit of 0 to the default, preventing unbounded per-message memory use (OOM) (#1179, commit 45751b29).2fffa613).2169cdcc).a043b736).e5140842).59f0ed16).b64data/b64info in the subscribe method (they were ignored) (#1177, commit e1b3ecdd).3278277f).8eb4fe9c).connection_rate_limit even when connection_limit is not set (#1174, commit a914f5cf).658bc52e).broadcast_publish instead of reusing the publish label (#1172, commit 0188e07f).475a8669).h2c_external check (#1178, commit 6d057175).84c32661).d29c3893).