Community
Mercure 1.0.2 is a security release. After initial reports from Pascal Borreli (@pborreli), Tilia.coop did a full audit of the hub, which found ten vulnerabilities, four of them rated high. Thanks to both! On a multi-hub Caddy configuration, one hub could receive another's private updates. A subscriber could crash the hub or make it retain gigabytes of memory, and a Go library embedding the hub with an empty key accepted every token. The release also fixes the Bolt transport: it lost history after a restart and its size directive didn't trim anything. Every hub operator should upgrade.
1.0.1 was tagged but never released: its build failed on 32-bit platforms.
🔒 Security
- Hubs in one Caddy process shared a transport and received each other's private updates when their
transportblocks were identical. Pooled transports are now scoped by hub name, and a configuration may declare at most one unnamed hub. GHSA-qqgv-h269-4x6g - An oversized URI template crashed the hub (compatibility mode only). The hub now refuses a URI template too complex to compile with
400 Bad Requestat subscription time. GHSA-cq4v-p87p-prjq - Subscribers could make the hub retain gigabytes of compiled topic matchers. The matcher cache is now weighed by the memory each compiled matcher retains. GHSA-88rf-rrhm-w65h
- A hub embedded as a Go library accepted an empty JWT key and verified every token. Empty keys, and PEM keys paired with an HMAC algorithm, are now refused. GHSA-65rh-h4mp-598g
- A PEM public key after an OpenSSL preamble was used as an HMAC secret, so anyone holding the public key could forge tokens. PEM detection now finds the block anywhere in the key. GHSA-pwm3-wvjf-p5rj
- The
nullorigin and broad publish origin wildcards bypassed origin allowlists. CORS credentials are disabled for thenullorigin, host-spanning patterns and public suffix wildcards, and a publish origin wildcard spanning registrable domains is refused. GHSA-x28p-f53f-94x7 - Unauthenticated requests could spend seconds of hub CPU each. Access tokens are now size-bounded before parsing. GHSA-5q4r-3c47-cx5r
- A publisher could forge subscription events by publishing a path-relative topic. Reserved topics are now resolved against the same base subscribers are matched against. GHSA-68q4-w8qp-hqfx
- A request without a
Hostdisabled the access token audience check. Such requests are refused, and so are tokens when the hub can't derive an audience. GHSA-f6v7-xrh7-9f9f - The subscription API listed every subscription when no subscriber verifier was configured. It is now disabled in that case. GHSA-92v5-xq4p-j4cx
- Hardening:
- Published topics and event IDs are length-bounded, and so is the live queue of a subscriber that isn't ready yet. by @dunglas in #1396, #1407
- Subscription API responses carry
Cache-Control: private, so shared caches don't store them. by @dunglas in #1396 - The legacy
mercureclaim is ignored outside compatibility mode. In compatibility mode, the audience is checked on tokens without legacy claims. by @dunglas in #1396, #1403 - Access token claims are decoded with
encoding/json/v2, like the authorization details already were. - Playground echo endpoints only serve JSON, JSON-LD or plain text. by @dunglas in #1410
- The subscriber filter cache is keyed by a digest of the topics. by @dunglas in #1408
⚠️ Upgrade Notes
The protocol doesn't change, but these configurations are now refused at startup:
- more than one unnamed hub in a Caddy configuration;
- two differently named hubs pointing at the same Bolt file;
- a
publish_originswildcard that can match more than one registrable domain.
The subscription API is disabled when no subscriber verifier is configured, except in compatibility mode.
✨ New Features
- Warnings about weak keys. The hub logs a warning when it starts with the published development keys outside the playground, or with an HMAC key shorter than the hash output. by @dunglas in #1404
- Helm chart: new
resourceIdentifierandpublicUrlsvalues pin the hub identity instead of deriving the token audience from the client'sHost. by @dunglas in #1406
🐛 Bug Fixes
- Bolt: history is replayed after a restart. by @dunglas in #1400
- Bolt:
Last-Event-IDnewer than the 10,000th oldest event was unreachable. The search now starts from the newest event. by @dunglas in #1394 - Bolt: the
sizedirective had no effect in the Caddy module, and each cleanup pass removed only half of the expired entries. by @dunglas in #1401 - Bolt: a reused
Last-Event-IDresolves to its earliest occurrence. The replay of the earliest history is bounded, and events without an ID stay out of replay responses. by @dunglas in #1397 - The dispatch timeout is enforced when no write timeout is set. by @dunglas in #1402
- A subscriber whose registration fails is removed. by @dunglas in #1407
- Helm chart: generated JWT keys are kept across upgrades, and metrics are served from a dedicated listener instead of the admin API. by @dunglas in #1406
- Debugger: its dependencies are vendored with npm, so it no longer loads scripts or fonts from third-party CDNs. by @dunglas in #1405
📖 Documentation
- Laravel Broadcasting guide. by @Lea-Bar in #1390
- Clearer guides, and 0.x references dropped now that 1.0 is out. by @dunglas in #1395, #1399
Enterprise
Mercure Cloud and Mercure Enterprise pick up the same fixes. The on-prem hub ships as image v1.0.2, and Cloud tenants are being rolled onto it.
These fixes ship under the Enterprise SLA, with prioritized patches and direct support. Contact contact@mercure.rocks for the managed Cloud offering, on-premise licenses, custom development, consulting, and training.
💖 New Contributors
Full Changelog: v1.0.0...v1.0.2