spec!: OAuth 2.0 authorization, two matcher types, IETF-style pass#1262
Open
dunglas wants to merge 53 commits into
Open
spec!: OAuth 2.0 authorization, two matcher types, IETF-style pass#1262dunglas wants to merge 53 commits into
dunglas wants to merge 53 commits into
Conversation
Co-authored-by: Antoine Bluchet <soyuka@users.noreply.github.com>
Co-authored-by: Antoine Bluchet <soyuka@users.noreply.github.com>
Co-authored-by: Antoine Bluchet <soyuka@users.noreply.github.com>
Co-authored-by: Antoine Bluchet <soyuka@users.noreply.github.com>
Co-authored-by: Antoine Bluchet <soyuka@users.noreply.github.com>
Co-authored-by: Antoine Bluchet <soyuka@users.noreply.github.com>
…dling Three clarifications surfaced while implementing this spec revision: - Payload assignment: define what it means for a JWT claim matcher to "match" a subscription matcher. A claim matches when it is the wildcard `*`, when its (type, pattern) pair equals the subscription's, or when its matcher accepts the subscription's `match` string as a topic. Payload fallback to top-level `mercure.payload` is stated explicitly. - URL Pattern base URL: recommend absolute patterns and describe the hub's freedom in resolving relative ones. Subscribers and publishers should not rely on the base URL being anything specific because resolution is implementation-defined. - CEL evaluation cost limit: hubs SHOULD enforce an implementation- defined cost limit to mitigate DoS from pathological expressions in hostile JWTs; exceeding the limit yields `false`. Also notes that hubs MAY reject oversized patterns with a 400 response.
Silently treating v8 bare-string claims as `Exact` is an interoperability trap: the v8 rule was "exact OR URI Template", so a token signed for a v8 hub would match fewer topics on a v9 hub without any diagnostic. Bare-string claims are now only accepted when the hub is explicitly running in protocol-compatibility mode; v9 hubs reject them with 401. This forces clients to migrate to the unambiguous object form.
Topics are absolute IRIs by design, and the protocol has no notion of a base URL against which a subscriber could expect a relative pattern to resolve. Allowing relative patterns forced hubs to invent an implicit base (typically the hub's own location), which made subscriptions non-portable across hubs and the resolution implementation-defined. Hubs MUST now reject relative URL patterns. The Go implementation in PR #1207 already does this.
The hub publishes subscription events on relative topic IDs (e.g., /.well-known/mercure/subscriptions/...). Requiring patterns to be absolute prevents subscribers from matching those topics with matchURLPattern.
…egistry
- Bump draft to -08
- Drop redundant "exactly one of" wording in Subscription
- Add (#discovery) cross-reference from Subscription
- Switch topic matcher query parameter names to case-sensitive,
registered in a new IANA "Mercure Topic Matcher Query Parameter Names"
registry
- URL Pattern: MUST NOT enable ignoreCase; host case-insensitivity
retained per URL canonicalization
- Exact and Regexp: MUST NOT resolve relative values against a base URL
- Topic Matcher List: reject non-object entries with 400 (was 401 bare-string),
matchType value is case-sensitive and matches the canonical name from
the matcher-type table
- Payloads: define top-level mercure.payload, drop the redundant "*"
wildcard rule (catchall expressible via regex .*)
- Subscription Events: {matchType} URL component uses canonical case;
JSON-LD matchType field case-sensitive
- Fix dart_mercure underscore typo
Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
Address security issues identified in a section-by-section audit. Adds normative requirements to close authentication, authorization, denial- of-service, injection, and information-disclosure gaps that were previously implicit or unspecified. Terminology and Subscription - Constrain topic strings to UTF-8 and forbid C0 controls and DEL - Bump per-request matcher count and pattern length limits from MAY to SHOULD, with 400 on overflow - Require UTF-8 and control-char rejection for matcher query values - Reword the subscription creation rule to match the registered query parameter set; allow hubs to deduplicate identical matchers Matcher Types - Exact: forbid relative resolution and implicit normalization; add guidance on NFC and IDNA canonicalization - URL Pattern: require an RE2-class engine or evaluation cost/time limit; warn about scheme-wildcard hazards (data:, javascript:, etc.) - Regular Expression: require an RE2-class engine or evaluation cost/time limit; I-Regexp is a dialect, not an engine - CEL: bar custom functions from network, filesystem, process, or other side-effectful access; bump evaluation cost limit from SHOULD to MUST; cap topics array size - Other Matcher Types: register names in the new IANA "Mercure Matcher Types" registry; vendor-prefix implementation-specific names Publication - Reserve /.well-known/mercure/ for hub-generated topics; reject publish requests violating the rule with 403 - Make the private field's truthiness rule explicit (presence wins, value ignored), preventing accidental opt-out interpretations - Forbid C0 controls in id, LF/CR in type, and require ASCII digits for retry, with 400 on violation - Require UTF-8 for field names, values, and data - Cap request body and field length; reject overflow with 413 Authorization - Add a JWS Validation subsection mandating rejection of alg: none, alg/key-type binding, signature verification, exp/nbf enforcement, and optional aud validation; require minimum alg support of EdDSA, ES256, and RS256 - Distinguish 401 (JWS missing/invalid) from 403 (insufficient scope); forbid disclosing which one failed - Promote different-key-per-role guidance from MAY to SHOULD - Promote cookie Secure and HttpOnly from SHOULD to MUST; default SameSite=Strict, Lax permitted only for cross-site discovery flows - Topic Matcher List: any per-entry parse failure rejects the whole request with 400 (no partial acceptance, which could alter scope); cap matcher count and pattern length, with 400 on overflow - Payloads: warn that first-match-wins ordering can mask specific entries; warn that payloads broadcast to other authorized subscribers via subscription events and must not carry per- subscriber identifiers Reconnection - Require authorization re-check on replayed events against the current JWS scope (not the scope at publication time) - Forbid the Last-Event-ID response header from disclosing the identifier of an event the subscriber is not authorized to receive Active Subscriptions - Subscriber identifier is hub-assigned; clients MUST NOT supply, suggest, or override it; hub MUST guarantee uniqueness and MAY derive the value from the JWS sub claim - Recommend correlating subscriptions of the same client across requests (e.g., by sub claim) JSON-LD Context - Subscribers SHOULD NOT auto-fetch the @context URL; embed or cache it locally to prevent MITM-induced semantic drift Discovery - Warn that a compromised publisher can redirect subscribers to a malicious hub; recommend restricting hub origins - Tighten key-set hosting language; require publisher-side access control on the JWK Set endpoint; warn that misconfiguration defeats encryption Encryption - Restrict JWE algorithms to currently strong choices; forbid algorithms with known compromises (e.g., RSA1_5) - Note that JWE has no replay protection; recommend in-payload freshness indicators - Note that long-lived JWE keys lack forward secrecy; recommend rotation and ephemeral key agreement IANA - Add the "Mercure Matcher Types" registry (Specification Required) with initial entries Exact, URLPattern, Regexp, CEL, URITemplate - Update query-parameter registry to reference the matcher-types registry Security Considerations - Add subsections summarizing the new threats: JWS validation, SSE field injection, reserved hub namespace, replay authorization, subscriber identifier assignment, regex/URL Pattern DoS, CEL sandbox, payload privacy, topic normalization, resource limits, hub trust, JWE algorithms and replay References - Add UNICODE and re2 bibliography entries Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
…ontroller - Replace ambiguous "No topic value MAY start with the prefix /.well-known/mercure/" with "Topic values MUST NOT start with the prefix". MAY is a permission keyword; combining it with "No" mixed RFC 2119 semantics with English negation. - Add a normative body rule for per-JWS concurrent subscription limits (MAY + 429), aligning the body with the Resource Limits entry in Security Considerations. - State "The change controller for all initial entries is the IETF" in both new IANA registries (Topic Matcher Query Parameter Names and Matcher Types), matching the convention used by the other IANA registrations in this document. Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
Collapse the two newly added IANA registries into one. Query parameter names are mechanically derived from matcher type names (match + TypeName, plus the historical match alias for Exact), so maintaining two separate registries adds editorial weight without adding information. The merged registry binds the type name and its query parameter at the moment of registration, making the relationship explicit and reducing the chance of drift. Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
Custom matcher types extend the protocol surface and the attack surface. Without explicit constraints, hubs implementing them risk authorization bypass through type confusion, denial of service via unguarded pattern evaluation, and SSRF or sensitive-material disclosure via expression-based matchers. - Require non-registered names to use reverse-DNS dotted notation (com.example.Foo) or an absolute HTTPS URI rooted in a domain controlled by the implementer. The PascalCase production is reserved for registered names; collisions are syntactically prevented. - Require parity with built-in DoS and sandbox controls: linear-time matching engine or evaluation cost limit, no network/filesystem/ process/clock/random access from expressions. - Add an authorization invariant: a custom matcher MUST NOT produce an authorization decision unreachable by some registered matcher type for the same match value and topic. - State explicitly that JWSs using non-registered names are not portable, and that incompatible hubs reject with 501. - Add a Security Considerations subsection summarizing the rationale and requiring hub operators to review each custom matcher before enabling it. Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
Refinements following further review.
- Custom matchers
- Drop the URI-form name option (matchhttps%3A%2F%2F... was
parser-hostile after query-key percent-encoding).
- Relax to "vendor-controlled prefix separated by .": DNS-reverse
domain (com.example.Foo) or trademark (ACME.Foo). The intent is
explicitly the inverse of RFC 6648's deprecated X- convention —
require globally-unique prefixes rather than ambiguous markers.
- Update the Subscription query-parameter rule so hub-supported
non-registered names are accepted, not only IANA-registered ones.
- Authorization
- Note that hubs MAY be deployed without JWS-based authorization
when the network is fully trusted and the hub is unreachable from
untrusted clients. Avoids relaxing alg=none, which would be
unsafe even on intranets.
- Compress JWS Validation by delegating to RFC 8725 (JWT BCP).
Keep the high-impact items (alg=none, alg/key binding, exp/nbf,
aud, recommended alg list) inline; defer the rest.
- Reconnection
- Replace the predecessor-event lookup with a two-state Last-Event-
ID response: same identifier if the requested event existed and
was visible to the subscriber, earliest otherwise. Avoids both
the backward-scan DoS vector and the leak of unauthorized event
identifiers.
- Subscription Events
- Subscriber identifier: drop the strict "hub-generated" rule.
Reality: the JWS is minted by the application server, not the
hub. Reframe as a forgery-prevention rule: clients MUST NOT
forge, supply, or override the identifier; the hub MUST derive
it from cryptographically-validated information (typically the
sub claim). Updated Security Considerations subsection
correspondingly.
Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
Switch Last-Event-ID response semantics from the binary two-state form back to a precise predecessor identifier, with two guards: - the predecessor MUST be visible to the requesting subscriber (private events the subscriber cannot read are never disclosed via this header); - the lookup is bounded by an implementation-defined backward-scan limit, preventing it from becoming a DoS vector through ancient Last-Event-ID values. When the cap is hit or no visible predecessor is found, the hub returns "earliest". This restores the event-store use case (precise recovery anchor) while keeping the security and DoS protections of the previous version. Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
The previous wording required hubs to enforce a backward-scan limit unconditionally. The real cost of the lookup is per-event authorization filtering, not the predecessor seek itself, which is O(log N) on backends such as Redis Streams (XREVRANGE) and constant on b-tree indexes. - Demote the scan-limit rule from MUST to SHOULD, and explicitly permit hubs with cheap predecessor seek and cheap per-event authorization filtering to omit it entirely. - Keep the authorization-filter constraint on the response header unchanged (MUST NOT disclose unauthorized event identifiers). - Add an operator note explaining that the privacy impact of predecessor disclosure depends on the hub's identifier format: opaque random IDs (UUIDv4) leak only existence, while time- ordered IDs (UUIDv7, Redis Stream IDs, snowflake) additionally leak timing and ordering of events the subscriber cannot read. Operators handling highly sensitive private updates SHOULD prefer opaque random identifiers. Signed-off-by: Kévin Dunglas <kevin@dunglas.fr>
Architectural/technical: - Reserved-namespace publish check now tests the resolved path component, host-agnostic, closing the absolute-topic subscription-event forgery gap. - 401 only when no credential is presented; 403 for every presented-credential failure, so the status code no longer discloses validation vs scope. - Matcher query-param names: 400 if malformed, 501 if well-formed but unimplemented, matching the claim-list handling. - Define URITemplate matching (reverse-expansion) and its DoS limit. - Specify full-string anchoring for Regexp matching. - Subscriptions are created for registered and hub-specific matchers alike. - State that payload claim-matching governs selection only, never authorization. References: - Add RFC 8174 (BCP 14 boilerplate). - RFC 5988 -> 8288, RFC 4122 -> 9562, RFC 6982 -> 7942. - Demote EventSource interface and XHR to informative. - Convert inline DID links to a proper reference. Editorial: - Add Introduction; define canonical/alternate topic in Terminology. - Clarify the JWS payload is a JWT. - Registrable domain (eTLD+1) instead of "second-level domain". - Rewrite Security Considerations as analysis plus pointers, no duplicated normative keywords; move the custom-matcher operator-review requirement into the body. - Recommend 200 on publish, 201 NOT RECOMMENDED. Metadata: - Add ipr=trust200902 and abbrev; area -> Web and Internet Transport.
The pluggable matcher system (Regexp, CEL, URITemplate, the IANA matcher-type registry, and reverse-DNS custom types) was speculative and unshipped. It expanded the interoperability surface without a driving use case: a conforming hub could share almost nothing with another beyond Exact, and the advanced types had no second implementation. Keep two mandatory types, Exact and URLPattern. URLPattern supersedes URI Template, which lacked defined matching semantics (RFC 6570 specifies expansion, not matching). Restore a reserved `*` wildcard for match-all. - Subscription: only match/matchExact/matchURLPattern; unknown names -> 400. - Topic matcher list: matchType MUST be Exact or URLPattern; drop the 501 unsupported-type paths (both types are now mandatory). - Remove the CEL sandbox and custom-matcher security subsections; fold the DoS note into URL-Pattern only. - Remove the "Mercure Matcher Types" IANA registry and the cel/RFC 9485 references. RFC 6570 is retained for subscription-event URI Template expansion.
- JWS validation: require an explicit algorithm allowlist (never inferred from the token); add key-selection guidance (kid / endpoint role, never token- steered); strengthen aud from conditional to SHOULD-require-when-configured, bounding a token to its intended hub. - Subscribers: hubs SHOULD impose a maximum connection lifetime independent of exp, so a no-exp token cannot hold an unrevocable connection open. - Document the private-update audience leak: delivery is gated per update, not per topic, so a broad alternate topic discloses sensitive content to its audience. Add a publisher MUST NOT and a Security Considerations subsection, and name the publisher-delegated-authorization model as a known limitation. - Add Security Considerations for bearer-token theft (optional DPoP/mTLS sender constraint, RFC 9449) and publish-request replay.
dunglas
added a commit
that referenced
this pull request
Jun 9, 2026
Replace the bespoke mercure JWT claim with RFC 9396 authorization_details as the modern authorization model. Access tokens are now validated as RFC 9068 JWT access tokens: the typ header must be at+jwt and the aud claim must contain the hub's resource identifier (NewHub requires WithResourceIdentifier or WithPublicURL when JWT auth is enabled in modern mode). The publish/subscribe grant and per-subscription payload resolution run against the validated authorization details. The legacy mercure claim (string and object forms), the namespaced fallback, mercure.payload and the "authorization" query parameter move behind the new deprecated_claim build tag and are honored only in compatibility mode (WithProtocolVersionCompatibility). The modern query parameter is access_token. BREAKING CHANGE: tokens carrying the mercure claim are rejected unless the hub is built with the deprecated_claim tag and runs in compatibility mode. The Go API loses canReceive/canDispatch (replaced by the internal authorization-detail grant logic). Refs #1262
dunglas
added a commit
that referenced
this pull request
Jun 9, 2026
Map authorization failures to RFC 6750: a 401 with a bare `WWW-Authenticate: Bearer` challenge (carrying the RFC 9728 resource_metadata parameter) when no token is presented, 401 `invalid_token` when a presented token fails validation, 403 `insufficient_scope` when a valid token lacks the required publish/subscribe grant, and 400 `invalid_request` for malformed authorization requests or invalid authorization details. Previously every authorization failure returned a bare 401. Refs #1262
dunglas
added a commit
that referenced
this pull request
Jun 9, 2026
…identifier Update the Caddy test fixtures, the demo UI and the conformance suite to RFC 9068 access tokens (typ at+jwt, audience set) carrying an authorization_details claim, and configure resource_identifier in the test, dev and production Caddyfiles so the hub validates the audience. The deprecated Caddy tests now require both the deprecated_topic and deprecated_claim build tags. The dev and production Caddyfiles redact the access_token query parameter from logs alongside the legacy authorization parameter. BREAKING CHANGE: the official Caddyfile now sets resource_identifier (default https://localhost/.well-known/mercure); set MERCURE_RESOURCE_IDENTIFIER to the audience your access tokens carry, or enable protocol_version_compatibility 8 to keep accepting v8 mercure-claim tokens. Refs #1262
dunglas
added a commit
that referenced
this pull request
Jun 9, 2026
Add deprecated_claim to the CI GOFLAGS, the goreleaser builds and the project build-tag list, and document the OAuth 2.0 authorization model (access tokens, authorization_details, RFC 6750 errors, resource_identifier, the protected resource metadata endpoint) in the upgrade guide and the hub configuration reference. Refs #1262
Replace the stale `topic` query parameter in the access_token example with the encoded `match` form, quote ETag values per RFC 9110, cite RFC 8615 (which obsoletes RFC 5785), drop the pre-OAuth authentication sentence from the subscription section, mark `topic` as the required publish field instead of listing it among optional tuples, make bearer_methods_supported reflect what the hub actually accepts, and spell out the reserved `match` prefix rationale and the payload matcher-against-matcher semantics.
Requiring an access token from every subscriber made the most common deployment (public feeds and dashboards) impossible without minting tokens for anonymous visitors. Split the rule: publication always requires a token, while hubs may accept unauthenticated subscribers that only receive non-private updates. Adjust the 401 challenge, the exp-based connection close, and the subscription event subscriber identifier accordingly.
"The request target resolved against the hub's URL" yields an absolute URL, while subscription event topics are relative paths. Exact matching is byte-for-byte with no resolution, so no Exact matcher could cover both a subscription event and the API URL describing it. Define the canonical topic for these endpoints as the absolute path, shared with subscription event topics.
Authorization details live inside the token, so defects there are token-validation failures: per RFC 6750 they map to 401 invalid_token, not 400 invalid_request. This also avoids disclosing that the signature verified but the claims were malformed, consistent with the single invalid_token rationale of the error responses section.
RFC 9396 establishes no registry of authorization details type identifiers (it leaves type registration out of scope), so drop the registration into a nonexistent registry and state that the mercure type is defined by this document. Register the mercure_cookie and new mercure_version members in the OAuth Protected Resource Metadata registry established by RFC 9728. mercure_version gives clients a way to detect hubs speaking incompatible earlier revisions, which changed the subscribe query parameters and the token format.
The kid header is attacker-controlled, contradicting the requirement to select keys independently of attacker input: restate it as a hint choosing among pre-trusted keys, never introducing new key material (jwk/jku/x5u). Define which RFC 9068 claims hubs actually require so strict validators do not reject minimal self-issued tokens, recommend the hub URL as the default resource identifier, and add a minimal self-issued token example. Replace the untestable "compromised algorithm" MUST NOTs with SHOULD NOT, keeping a testable MUST NOT for RSA1_5.
The cookie mechanism explicitly targets cross-origin EventSource connections with credentials, but the spec said nothing about CORS. Require hubs serving cross-origin browsers to send CORS headers, and forbid wildcard or reflected Access-Control-Allow-Origin on credentialed connections: reflection would let any website read updates with the visitor's cookie.
Normalize percent-encoding (unreserved characters) before the reserved-namespace path test, so /.well-known/%6Dercure/... cannot bypass it. Forbid publisher-supplied update IDs equal to the reserved value earliest, which would otherwise make Last-Event-ID handling ambiguous despite the no-collision claim in the reconciliation section.
The W3C Server-Sent Events Recommendation and HTML 5.2 are superseded; SSE is maintained in the WHATWG HTML Living Standard and application/x-www-form-urlencoded in the URL Living Standard, both already cited elsewhere in this draft (URL Pattern, XHR).
A version number in resource metadata has no negotiation semantics and contradicts IETF practice: the published specification is the version, and an incompatible revision would be a new spec defining its own metadata members or well-known location. The stated purpose (detecting pre-standardization hubs) is already served by the absence of protected resource metadata, which those hubs never publish; keep that as an explicit hint instead. The draft-revision number 8 was also meaningless as a protocol version.
Requiring the identifier to be derived exclusively from validated token claims contradicted the reference implementation, which assigns a random UUID per connection, and overlooked a privacy cost: an identifier derived from sub discloses that claim to every subscriber authorized for subscription events. Permit hub-generated identifiers explicitly; the security property that matters is that clients cannot choose or influence the value.
HTTP authentication scheme names are case-insensitive per RFC 9110 §11.1; spell it out so implementations do not byte-compare the prefix.
RFC 9728 defines this optional member; advertising ["mercure"] lets discovery-driven clients learn that the hub understands the mercure authorization detail type.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This supersedes #959. It carries the entire spec rework: an IETF-style editorial
and security pass, a reduction of the matcher surface, an authorization-model
hardening pass, and a move to a standards-based OAuth 2.0 authorization model.
The through-line is the same: when Mercure was first drafted in 2018, the web
standards that now cover its needs did not exist or were immature, so the
protocol grew its own machinery. Those standards exist now, so this removes the
bespoke parts and reuses them — leaving a smaller spec a generic OAuth
resource-server library can enforce.
Relevance
SSE has become the de-facto transport for streaming output from AI systems: the
OpenAI and Anthropic APIs and the Model Context Protocol all stream tokens over
SSE. Mercure is SSE-native and adds what those ad-hoc uses lack — authorization,
multiplexing, discovery, and reconnection with state reconciliation — over plain
HTTP. RFC 9728, which this work relies on, was itself driven to publication by
AI-agent demand. A standardized, authenticated way to stream incremental results
is more useful now than when this work started.
Authorization: OAuth 2.0 instead of a bespoke claim
The old
mercureJWT claim conflated routing ("which update is this") withaccess control ("who may read it"): per-resource access was encoded in hand-built
"capability" topics, and a private update could leak its content to a broader
audience than intended.
JWT access token ([RFC 9068],
typ: at+jwt,aud= hub resource identifier).authorization_details,[RFC 9396]) via a new
mercuretype carryingactions(publish/subscribe) andtopics. The bespoke claim is gone; flat scopes are not used (topics are theonly resource type).
Authorization: Bearer, anaccess_tokenquery parameter, cookie as a documented extension;invalid_token(401) /insufficient_scope(403) /invalid_request(400).([RFC 9728], 2025). Verification keys are delegated to authorization server
metadata ([RFC 8414]) or that resource metadata; no Mercure-specific JWKS.
(never inferred from the token),
kid/role key selection,exp/nbf, requiredaud, checkediss,typconfusion rejected.Matchers: two types, standards-based
([RFC 6570] defines expansion, not matching, and did not fit). URL Pattern did
not exist in 2018.
exploratory Regexp / URI Template / CEL types and the matcher-type registry are
dropped as speculative surface without a driving use case.
authorization becomes a single hub-enforced check against the token, removing the
content-disclosure footgun of the old "any topic matches any matcher" rule.
match(Exact, the default) andmatch<MatcherType>(e.g.matchURLPattern), mirroring the optionalmatchTypemember of authorization details; unknown names under the reservedmatchprefix are rejected so typos fail loudly.Editorial and security pass
Linking, RFC 9562 for UUID, RFC 7942 for the implementation-status template).
injection, the reserved hub namespace, event-replay authorization, DoS limits,
topic normalization/homographs, bearer-token theft (optional DPoP/mTLS, RFC
9449), publish-request replay, and protected-resource-metadata / authorization-
server-selection risks (SSRF, AitM).
key-setLink attribute is dropped (keys shared out of band).A post-review hardening pass on the same branch: unauthenticated subscribers are
allowed again for non-private updates (publication always requires a token), the
subscription API authorizes against the same relative URL form as subscription
event topics, malformed authorization details map to 401
invalid_tokenperRFC 6750, CORS rules forbid wildcard/reflected origins on credentialed
connections, the reserved-namespace check normalizes percent-encoding, the
reserved
earliestvalue cannot be supplied as an update ID, the IANA sectionregisters the
mercure_cookieRFC 9728 metadata member (and no longer targets aregistry RFC 9396 never created), and superseded W3C references point to the
WHATWG living standards. Follow-ups: the Bearer scheme name is matched
case-insensitively per RFC 9110, and
{subscriber}identifiers may behub-generated (e.g., random UUIDs) — what matters is that clients cannot choose
them, and hub-generated values avoid disclosing
subto other subscribers.Notes
before merge.
and feat!: OAuth 2.0 authorization (RFC 9068/9396/6750/9728) #1273 (OAuth 2.0 authorization)
track this spec. Docs (docs: rewrite for the 1.0 protocol release #1238) still
need rework.