From ee2eb3b615d22825a644df18cf1ffa0ad7e7956d Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 16 May 2026 19:11:10 +0100 Subject: [PATCH 1/9] Initial draft --- proposals/xxxx-proxied-alias-resolution.md | 136 +++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 proposals/xxxx-proxied-alias-resolution.md diff --git a/proposals/xxxx-proxied-alias-resolution.md b/proposals/xxxx-proxied-alias-resolution.md new file mode 100644 index 00000000000..738290aca95 --- /dev/null +++ b/proposals/xxxx-proxied-alias-resolution.md @@ -0,0 +1,136 @@ +# MSCXXXX: Proxied room alias resolution + +Room aliases provide a way to assign memorable addresses to otherwise random room identifiers. For +example, `#matrix:matrix.org`. Typically, users will provide aliases to their clients, which the +client (and/or server) then needs to resolve to a room ID, depending on the action to take. In order +to do this, clients will ask the server to resolve the alias for them, which returns the associated +room ID, and a list of servers who may be in the room. + +However, alias resolution over federation depends on querying only the authoritative owner over the +alias (the server whose name is in the alias itself). This presents a number of problems that can +often result in an alias being unresolvable, and users have to go hunting for the room ID on their +own. + +This proposal will detail a modification to the room alias resolution endpoints that allows them to +be *proxied*, enabling higher availability of room aliases, without having to resort to less ideal +means. + +## Proposal + +Note: *authoritative* and *origin* server are used interchangeably in this proposal to refer to the +server which an alias belongs to, i.e. the server name in the alias. + +The approach taken in this proposal is inspired by the existing signing key discovery system: +. +As such, it is assumed that servers implementing this proposal either have a concept of trusted +servers, or at least key notary servers. These servers already have an implicitly high amount of +trust. + +When this proposal is implemented, [`GET /_matrix/federation/v1/query/directory`][fed-1] should be +queried as normal. If the server being queried (receiving server) is the server which owns the +room alias, it SHOULD [sign the response](https://spec.matrix.org/v1.18/appendices/#signing-json). +If the server performing the query (sending server) caches room alias resolutions, it SHOULD store +this signature alongside the response. The sending server MUST verify that the returned signature +(if any) is valid and in-date at the time of the request, discarding the response if not. + +If the sending server is unable to reach the room alias server, it MAY attempt to contact other +trusted servers, such as configured key perspectives/notaries. + +Currently, if a homeserver receives a request for an alias which does not belong to it, the request +is refused, returning an error code. If this proposal is implemented, the behaviour should instead +be changed to follow the following steps: + +1. If the alias belongs to the receiving server, return as normal (see above). +2. If the alias belongs to a different server, attempt to query the alias' origin directly. + 1. If the alias origin responds, the response is checked for a signature. + 2. If the response has a valid, in-date signature, the origin's response is returned directly. + 3. If the response does not have a signature, or the signature is invalid, discard the response + and continue. +3. If the receiving server has a cached entry for the queried alias, AND the signing key used to + sign the cached response is still in-date at the time of the incoming request, the server MAY + return the cached response. +4. Otherwise, resolution is impossible, and `404 M_NOT_FOUND` is returned. + +Additionally, servers which cache room alias resolution responses SHOULD respect the +`Cache-Control` header in the response, if it is present. Servers MUST limit the maximum lifetime +of a cache entry to that of the signing key which signed the response, even if the `cache-control` +header indicates it should be stored for longer. For example, if the key expires in 12 hours, +but the room alias query response indicated it should be stored for 12 hours, the cache should only +live for 12 hours. If the response indicated it should be stored for 5 minutes, it should be stored +for 5 minutes, not 12 hours. + +Step 3 specifically qualifies that servers *MAY* return the cached response, because implementations +may prefer to check that the alias is a canonical alias of a room it is a resident of. Servers may +choose not to return aliases which they cannot find in canonical alias events, +however this is up to the receiving server's discretion. + +Servers MUST NOT cache responses from servers other than the room alias' origin. This prevents an +effect where a stale response may be incorrectly "freshened" by a second-degree server, and then +passed on to a third-degree server, so on. Or visually, in a chain of +`ORIGIN <- A <- B <- C`, `A` can cache the response from `ORIGIN`, and `A` can return that cached +response to `B` (see step 3), but `B` *MUST NOT* cache that response. If `C` attempts to query `B`, +`B` will perform the same steps `A` did, meaning it will always return a fresh response, or a +not-found error. + +Furthermore, servers MUST NOT serve cached responses that lack a signature from the origin. Such +responses cannot be verified as authentic by any server other than the one that made the query to +the origin. This also means that servers who do not wish to allow their aliases to be proxied may +simply avoid signing the response in the first place, even if they support this proposal. + +Servers MUST NOT allow clients to define vias. Allowing clients to specify which non-authoritative +servers are queried for a room alias may allow an attacker to inject a malicious response into the +sending server's cache, which has greater implications for multi-user deployments. + +## Potential issues + +See [§ Security Considerations](#security-considerations). + +This proposal is designed in a manner that is intentionally 100% backwards compatible with existing +implementations, so no potential issues (other than security considerations) are known at this time. + +## Alternatives + +None. + +## Security considerations + +**Room aliases are mutable**. By not exclusively querying the authoritative server, this proposal +does open up the possibility for stale responses to be considered valid, which carries several +concerns, including: + +1. The room alias may have since been changed to point at a different room. +2. The server names reported to be in the room may no longer be in the room. +3. The alias may be temporarily pointed at a malicious room. +4. The receiving server might lie. +5. This effectively introduces a recursive lookup functionality. + +The author believes appropriate mitigations for new security concerns introduced by this proposal +have been implemented. By retaining the behaviour that the authoritative server should always be +contacted first, the likelihood of a poisoned response is lowered. + +If the authoritative server can't be reached, the sending server then queries third parties it +already implicitly trusts, and at no point is such control handed over to a user +(who may be malicious). + +Then, each third party must contact the authoritative server before checking its cache. This +resolves a problem where Alice may not be able to contact Charlie, but can contact Bob, and Bob can +contact Charlie. Since, in this case, the response would be fresh from the authoritative server, +assuming it has a (valid) signature, the receiving server can return the response it received +directly. This ensures that the sending server receives a fresh response, however, it does NOT cache +it, as it may not be aware of an appropriate TTL. Since servers cannot cache non-authoritative +responses, they cannot return them to other servers that may ask either. + +If the authoritative server cannot be reached, non-stale cache entries may be returned. This allows +the alias to continue to function for a limited amount of time without the origin's involvement, but +ultimately ensures it does not live so long that the origin effectively loses control over the +alias. + +## Unstable prefix + +As this change is transparent, no unstable prefix is required. Servers which do not understand this +proposal will not include `signatures`, which disables proxying, and will return an error response +if queried for an alias which does not belong to them. + +## Dependencies + +None From d2973faf15519d98f72af7458fe13e7e85f37cce Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 16 May 2026 19:12:47 +0100 Subject: [PATCH 2/9] Fix missing link --- proposals/xxxx-proxied-alias-resolution.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proposals/xxxx-proxied-alias-resolution.md b/proposals/xxxx-proxied-alias-resolution.md index 738290aca95..5549a3bb4e0 100644 --- a/proposals/xxxx-proxied-alias-resolution.md +++ b/proposals/xxxx-proxied-alias-resolution.md @@ -33,6 +33,8 @@ If the server performing the query (sending server) caches room alias resolution this signature alongside the response. The sending server MUST verify that the returned signature (if any) is valid and in-date at the time of the request, discarding the response if not. +[fed-1]: https://spec.matrix.org/v1.18/server-server-api/#get_matrixfederationv1querydirectory + If the sending server is unable to reach the room alias server, it MAY attempt to contact other trusted servers, such as configured key perspectives/notaries. From 3ed3b9ba1e69d3eadb91749f985b510f5a2ea278 Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 16 May 2026 19:16:55 +0100 Subject: [PATCH 3/9] Clarifications --- proposals/xxxx-proxied-alias-resolution.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/xxxx-proxied-alias-resolution.md b/proposals/xxxx-proxied-alias-resolution.md index 5549a3bb4e0..b2d6af36b7b 100644 --- a/proposals/xxxx-proxied-alias-resolution.md +++ b/proposals/xxxx-proxied-alias-resolution.md @@ -30,8 +30,8 @@ When this proposal is implemented, [`GET /_matrix/federation/v1/query/directory` queried as normal. If the server being queried (receiving server) is the server which owns the room alias, it SHOULD [sign the response](https://spec.matrix.org/v1.18/appendices/#signing-json). If the server performing the query (sending server) caches room alias resolutions, it SHOULD store -this signature alongside the response. The sending server MUST verify that the returned signature -(if any) is valid and in-date at the time of the request, discarding the response if not. +this signature alongside the response. If a signature is returned, the sending server MUST verify +it - if it fails, the response is discarded and the server is treated as unavailable. [fed-1]: https://spec.matrix.org/v1.18/server-server-api/#get_matrixfederationv1querydirectory From 943790f4e18b960ae6d858a2b16948109f1b3f42 Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 16 May 2026 19:24:17 +0100 Subject: [PATCH 4/9] Clarify the authoritative signature must be verified --- proposals/xxxx-proxied-alias-resolution.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proposals/xxxx-proxied-alias-resolution.md b/proposals/xxxx-proxied-alias-resolution.md index b2d6af36b7b..0ae0ade3e94 100644 --- a/proposals/xxxx-proxied-alias-resolution.md +++ b/proposals/xxxx-proxied-alias-resolution.md @@ -51,6 +51,8 @@ be changed to follow the following steps: 3. If the receiving server has a cached entry for the queried alias, AND the signing key used to sign the cached response is still in-date at the time of the incoming request, the server MAY return the cached response. + 1. The sending server MUST verify that the authoritative server signed the response it received, + discarding it if the signature is missing or corrupt. 4. Otherwise, resolution is impossible, and `404 M_NOT_FOUND` is returned. Additionally, servers which cache room alias resolution responses SHOULD respect the From 541715dbdb1d731996dd246980552a2921affb9f Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 16 May 2026 19:26:24 +0100 Subject: [PATCH 5/9] Assume MSC ID 4473 --- ...ied-alias-resolution.md => 4473-proxied-alias-resolution.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename proposals/{xxxx-proxied-alias-resolution.md => 4473-proxied-alias-resolution.md} (99%) diff --git a/proposals/xxxx-proxied-alias-resolution.md b/proposals/4473-proxied-alias-resolution.md similarity index 99% rename from proposals/xxxx-proxied-alias-resolution.md rename to proposals/4473-proxied-alias-resolution.md index 0ae0ade3e94..458c1ed9d79 100644 --- a/proposals/xxxx-proxied-alias-resolution.md +++ b/proposals/4473-proxied-alias-resolution.md @@ -1,4 +1,4 @@ -# MSCXXXX: Proxied room alias resolution +# MSC4473: Proxied room alias resolution Room aliases provide a way to assign memorable addresses to otherwise random room identifiers. For example, `#matrix:matrix.org`. Typically, users will provide aliases to their clients, which the From 9d8b75b4c4696d8a46439f7a19678784601efdf7 Mon Sep 17 00:00:00 2001 From: timedout Date: Sun, 17 May 2026 00:03:14 +0100 Subject: [PATCH 6/9] Add `expiry_ts`, clarify some things, improve errors, some TODOs --- proposals/4473-proxied-alias-resolution.md | 141 +++++++++++++++------ 1 file changed, 100 insertions(+), 41 deletions(-) diff --git a/proposals/4473-proxied-alias-resolution.md b/proposals/4473-proxied-alias-resolution.md index 458c1ed9d79..690c2ffc950 100644 --- a/proposals/4473-proxied-alias-resolution.md +++ b/proposals/4473-proxied-alias-resolution.md @@ -17,6 +17,13 @@ means. ## Proposal +Quick glossary: + +- *Authoritative* or *origin*: used to refer to the server which owns the room alias (the server + whose name appears in the alias itself) +- *Receiving server*: the server which is receiving a query request. May or may not be the *origin*. +- *Sending server*: the server which is sending a query request. + Note: *authoritative* and *origin* server are used interchangeably in this proposal to refer to the server which an alias belongs to, i.e. the server name in the alias. @@ -27,63 +34,115 @@ servers, or at least key notary servers. These servers already have an implicitl trust. When this proposal is implemented, [`GET /_matrix/federation/v1/query/directory`][fed-1] should be -queried as normal. If the server being queried (receiving server) is the server which owns the -room alias, it SHOULD [sign the response](https://spec.matrix.org/v1.18/appendices/#signing-json). -If the server performing the query (sending server) caches room alias resolutions, it SHOULD store -this signature alongside the response. If a signature is returned, the sending server MUST verify -it - if it fails, the response is discarded and the server is treated as unavailable. +queried as normal - there are no new query parameters. [fed-1]: https://spec.matrix.org/v1.18/server-server-api/#get_matrixfederationv1querydirectory +### Signing & Expiry + +If the server being queried (receiving server) is the server which owns the +room alias, it SHOULD [sign the response](https://spec.matrix.org/v1.18/appendices/#signing-json). + +When a homeserver signs the query response, it MUST additionally include an `expires_ts` field with +a unix timestamp (milliseconds) for when servers should consider the response stale, and evict it +from their cache. So, the updated response body would look like: + +```json5 +{ + "room_id": "!c10y-t1HZB9jgYr9mmaKtMDsS19HXbWRFc6d0bWGVYU", + "servers": [ + "nexy7574.co.uk", + // ... + ], + "expires_ts": 1778969913414, + "signatures": { + // ... + } +} +``` + +If the sending server receives a signed response, it MUST [verify the signature][sig-verify] of the +origin server (signatures from other servers are ignored). If the signature is forged/corrupt, or +the key used to sign the response expired before the request was made, the response is discarded, +and the server should be treated as unreachable. + +[sig-verify]: https://spec.matrix.org/v1.18/appendices/#checking-for-a-signature + +Furthermore, if the `expires_ts` is in the past, the response is again discarded. The server MAY +choose to abort further resolution attempts on account of the origin itself being reachable, but +returning bogus data (the chances of a third-party acquiring a fresh response are low). + +Servers MUST NOT ever ret the cached entry past `expires_ts`. Likewise, servers MUST NOT re-use +a cached response if the key used to sign it itself expires before `expires_ts`, and it cannot +be refreshed. Servers MAY instead choose the lower of the two timestamps when deciding the cache +entry's TTL, rather than attempting to re-validate the signature. + +If a sending server receives a signed response, it SHOULD store the signature and expiry timestamp, +as this will allow it to act as a proxy (much like how third party servers can be used to fetch +signing keys). If the signature and expiry timestamp are not stored, the server can only proxy by +forwarding the request, as it will be unable to serve from its cache. + +### New room alias resolution steps + +The sending server MUST first attempt to contact the origin server directly. If the origin server +responds, and the response passes any applicable checks in [§ Signing & Expiry](#signing--expiry), +the sending server MUST use that response and cease any further resolution. This additionally means +sending servers who receive an error response, such as `404 M_NOT_FOUND`, should use that response, +NOT attempt to query other servers. + If the sending server is unable to reach the room alias server, it MAY attempt to contact other -trusted servers, such as configured key perspectives/notaries. +servers it trusts (for example, configured notary servers). The sending server MUST NOT allow its +clients to define which servers are queried, unlike with room joins & summaries. Currently, if a homeserver receives a request for an alias which does not belong to it, the request -is refused, returning an error code. If this proposal is implemented, the behaviour should instead -be changed to follow the following steps: - -1. If the alias belongs to the receiving server, return as normal (see above). -2. If the alias belongs to a different server, attempt to query the alias' origin directly. - 1. If the alias origin responds, the response is checked for a signature. - 2. If the response has a valid, in-date signature, the origin's response is returned directly. - 3. If the response does not have a signature, or the signature is invalid, discard the response - and continue. -3. If the receiving server has a cached entry for the queried alias, AND the signing key used to - sign the cached response is still in-date at the time of the incoming request, the server MAY - return the cached response. +is refused, returning an error code. If this proposal is implemented, the behaviour of queries is +instead changed to the following: + +1. If the queried room alias is owned by receiving server, return as normal (see above). +2. If the queried room alias belongs to a different server, + the receiving server should attempt to query the room alias origin directly. + 1. If the room alias origin responds with `404 M_NOT_FOUND`, this is returned to the sending + server. + 2. If the room alias origin responds with a successful response, the response is + [checked](#signing--expiry). + 3. If the response is valid, it is directly returned to the sending server, and the process ends + here. + 4. If the response is invalid, or some other error is returned, discard the response and continue. +3. If the receiving server has a cached entry for the queried room alias, AND it passes + [the same checks](#signing--expiry) at the time of the request, the server MAY + return the cached response (see next paragraph). 1. The sending server MUST verify that the authoritative server signed the response it received, discarding it if the signature is missing or corrupt. -4. Otherwise, resolution is impossible, and `404 M_NOT_FOUND` is returned. - -Additionally, servers which cache room alias resolution responses SHOULD respect the -`Cache-Control` header in the response, if it is present. Servers MUST limit the maximum lifetime -of a cache entry to that of the signing key which signed the response, even if the `cache-control` -header indicates it should be stored for longer. For example, if the key expires in 12 hours, -but the room alias query response indicated it should be stored for 12 hours, the cache should only -live for 12 hours. If the response indicated it should be stored for 5 minutes, it should be stored -for 5 minutes, not 12 hours. +4. Otherwise, resolution is impossible, and `502 M_NOT_FOUND` is returned (see below). Step 3 specifically qualifies that servers *MAY* return the cached response, because implementations -may prefer to check that the alias is a canonical alias of a room it is a resident of. Servers may -choose not to return aliases which they cannot find in canonical alias events, -however this is up to the receiving server's discretion. +may prefer to check that the alias is a canonical alias of a room it is a resident of. Servers MAY +choose not to return aliases which they cannot find in canonical alias events, however this is up to +the receiving server's discretion. -Servers MUST NOT cache responses from servers other than the room alias' origin. This prevents an +Servers MUST NOT cache responses from servers other than the authoritative server. This prevents an effect where a stale response may be incorrectly "freshened" by a second-degree server, and then passed on to a third-degree server, so on. Or visually, in a chain of `ORIGIN <- A <- B <- C`, `A` can cache the response from `ORIGIN`, and `A` can return that cached response to `B` (see step 3), but `B` *MUST NOT* cache that response. If `C` attempts to query `B`, `B` will perform the same steps `A` did, meaning it will always return a fresh response, or a not-found error. - -Furthermore, servers MUST NOT serve cached responses that lack a signature from the origin. Such -responses cannot be verified as authentic by any server other than the one that made the query to -the origin. This also means that servers who do not wish to allow their aliases to be proxied may -simply avoid signing the response in the first place, even if they support this proposal. - -Servers MUST NOT allow clients to define vias. Allowing clients to specify which non-authoritative -servers are queried for a room alias may allow an attacker to inject a malicious response into the -sending server's cache, which has greater implications for multi-user deployments. + + +Since origin servers MAY NOT return signatures, unsigned responses MUST NOT be considered for +step 3. Both sides of the connection being required to validate the checks at the time of the +request should prevent this anyway, but this clause is defined specifically to allow servers to +continue the current behaviour of using these responses with a local cache to reduce the number of +lookups performed by local users. + +Servers who receive a `502 M_NOT_FOUND` response MAY continue discovery with another trusted server. +However, servers who receive a `404 M_NOT_FOUND` MUST terminate discovery, as this means the origin +server has reported the alias no longer exists. `502` is used to distinguish the *proxy* server not +being capable of resolving the alias from `404`, meaning the origin reports that the alias no longer +exists. + ## Potential issues @@ -114,7 +173,7 @@ contacted first, the likelihood of a poisoned response is lowered. If the authoritative server can't be reached, the sending server then queries third parties it already implicitly trusts, and at no point is such control handed over to a user -(who may be malicious). +(who may be malicious, see below). Then, each third party must contact the authoritative server before checking its cache. This resolves a problem where Alice may not be able to contact Charlie, but can contact Bob, and Bob can From 0990a2d28e77841a150701de981a80b3bff40c11 Mon Sep 17 00:00:00 2001 From: timedout Date: Sun, 17 May 2026 03:24:36 +0100 Subject: [PATCH 7/9] Introduce unstable prefix --- proposals/4473-proxied-alias-resolution.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/proposals/4473-proxied-alias-resolution.md b/proposals/4473-proxied-alias-resolution.md index 690c2ffc950..c03a1e128cf 100644 --- a/proposals/4473-proxied-alias-resolution.md +++ b/proposals/4473-proxied-alias-resolution.md @@ -190,9 +190,14 @@ alias. ## Unstable prefix -As this change is transparent, no unstable prefix is required. Servers which do not understand this -proposal will not include `signatures`, which disables proxying, and will return an error response -if queried for an alias which does not belong to them. +While this proposal is unstable, `uk.timedout.msc4473` should be used as an unstable prefix for the +directory route: + +| Stable | Unstable | +| ------ | -------- | +| `GET /_matrix/federation/v1/query/directory` | `GET /_matrix/federation/unstable/uk.timedout.msc4473/query/directory` | + +Once stabilised, the endpoint can be switched back to the already existing endpoint transparently. ## Dependencies From 39ef3623ec0710173eaecbfd44584d81d6c251e7 Mon Sep 17 00:00:00 2001 From: timedout Date: Sun, 17 May 2026 05:30:34 +0100 Subject: [PATCH 8/9] Flesh out summary, allow Nth-degree lookups, clarifications, define v2 endpoint and unstable prefix --- proposals/4473-proxied-alias-resolution.md | 221 ++++++++++++++------- 1 file changed, 154 insertions(+), 67 deletions(-) diff --git a/proposals/4473-proxied-alias-resolution.md b/proposals/4473-proxied-alias-resolution.md index c03a1e128cf..5948302181b 100644 --- a/proposals/4473-proxied-alias-resolution.md +++ b/proposals/4473-proxied-alias-resolution.md @@ -1,19 +1,56 @@ # MSC4473: Proxied room alias resolution Room aliases provide a way to assign memorable addresses to otherwise random room identifiers. For -example, `#matrix:matrix.org`. Typically, users will provide aliases to their clients, which the -client (and/or server) then needs to resolve to a room ID, depending on the action to take. In order -to do this, clients will ask the server to resolve the alias for them, which returns the associated -room ID, and a list of servers who may be in the room. +example, `#matrix:matrix.org` is a user-friendly pointer to +`!L58ME6ufiP49v97UIOBIpvWKEgj4912JmECPuDzlvCI` (at the time of writing). +Typically, users will provide aliases to their clients, which the client then needs to resolve to a +room ID for further use. In order to do this, clients will ask the server to resolve the alias for +them via the [`GET /_matrix/client/v3/directory/room/{roomAlias}][c2s-resolve]. +The user's homeserver will then check its local cache and/or database, but if it cannot find an appropriate result for the +alias, the server will then contact +[`GET matrix-federation://{authority}/_matrix/federation/v1/query/directory`][s2s-resolve] to fetch +the details over federation. + +[c2s-resolve]: https://spec.matrix.org/v1.18/client-server-api/#get_matrixclientv3directoryroomroomalias +[s2s-resolve]: https://spec.matrix.org/v1.18/server-server-api/#get_matrixfederationv1querydirectory However, alias resolution over federation depends on querying only the authoritative owner over the alias (the server whose name is in the alias itself). This presents a number of problems that can -often result in an alias being unresolvable, and users have to go hunting for the room ID on their -own. - -This proposal will detail a modification to the room alias resolution endpoints that allows them to -be *proxied*, enabling higher availability of room aliases, without having to resort to less ideal -means. +often result in an alias being unresolvable, which tends to lead to confused users having to ask +around for links, or even worse, having to try to find the underlying room ID on their own, which +may allow a malicious actor to lie to them in order to get them to join a room of their choosing. + +These problems are not uncommon or unexpected either. Servers can temporarily go down for a few +hours every now and again for updates or unexpected outages, and sometimes unexpected outages may +be prolonged to a number of days. A great example of this was when +[matrix.org was offline for 24 hours](https://matrix.org/blog/2025/10/post-mortem/). For those +24 hours, there were constant complaints from users on other servers who were now unable to join +rooms that at the time hosted their canonical alias(es) on `matrix.org`, which was now unreachable. +This also meant that `#foundation-office:matrix.org`, the Matrix.org Foundation Office, could not +be joined without knowing someone who was already in the room that could drop you a join link with +alternative vias for routing. + +And that's just unexpected outages. There is a growing trend in online censorship globally that +threatens to disrupt day-to-day connectivity of swathes of the Matrix network. + +This highlights a critical weak-point in the decentralised network: while rooms themselves are +network-partition resistant, and you can even join rooms through other servers if the desired one is +unreachable, *you have to already know the room ID* and *at least one other server in the room*. +Most people aren't going to remember that they can join +`!L58ME6ufiP49v97UIOBIpvWKEgj4912JmECPuDzlvCI` via `t2l.io`, or that +`!8cR4g-i9ucof69E4JHNg9LbPVkGprHb3SzcrGBDDJgk` can be joined through `continuwuity.rocks`. And even +if they *did* know this, most client UIs right now don't provide an easy way for users to specify +vias with room IDs, unless they manually type out a join link and click it (or sometimes run +`/join`). +This means that effectively, if the room alias can't be resolved, you can't join a room. +You might not even be able to figure out if you're even joined to a room an alias points to if your +server doesn't cache the last known response long enough, since your client can't check if you're +joined to `#matrix:matrix.org` without resolving the alias in the first place! + +This proposal will outline a new way to resolve room aliases that makes them almost as +partition-resistant as the rooms they point towards, allowing aliases to survive their origins +going down or otherwise becoming unreachable, while still ensuring the origin maintains as much +control over the alias as possible. ## Proposal @@ -21,6 +58,8 @@ Quick glossary: - *Authoritative* or *origin*: used to refer to the server which owns the room alias (the server whose name appears in the alias itself) +- *Alias*: The actual room alias itself (shortened to alias for brevity). +- *Proxy server(s)*: Servers which are capable of forwarding the lookup request. - *Receiving server*: the server which is receiving a query request. May or may not be the *origin*. - *Sending server*: the server which is sending a query request. @@ -33,38 +72,80 @@ As such, it is assumed that servers implementing this proposal either have a con servers, or at least key notary servers. These servers already have an implicitly high amount of trust. -When this proposal is implemented, [`GET /_matrix/federation/v1/query/directory`][fed-1] should be -queried as normal - there are no new query parameters. +### New server-server endpoint -[fed-1]: https://spec.matrix.org/v1.18/server-server-api/#get_matrixfederationv1querydirectory +In order to facilitate a reasonably different response body to the existing, the version of the +endpoint is bumped, although its shape remains familiar: -### Signing & Expiry +`GET /_matrix/federation/v2/query/directory` + +Rate-limited: no + +Authenticated: yes + +Query params: + +- `room_alias` (string, required): The full room alias to resolve (`#example:matrix.example`) -If the server being queried (receiving server) is the server which owns the -room alias, it SHOULD [sign the response](https://spec.matrix.org/v1.18/appendices/#signing-json). +Responses: -When a homeserver signs the query response, it MUST additionally include an `expires_ts` field with -a unix timestamp (milliseconds) for when servers should consider the response stale, and evict it -from their cache. So, the updated response body would look like: +`200 OK`: The resolved room ID, a list of servers in the room, when this response should be +considered stale, and signatures to attest the authenticity: -```json5 +| key | type | description | +| ------------ | --------------------- | ------------------------------------------------------------------------------- | +| `room_id` | string | The resolved room ID | +| `servers` | \[string\] | A list of servers in the room | +| `expires_ts` | integer | A timestamp in unix milliseconds when this response should be considered stale. | +| `signatures` | [Signatures][signing] | Any signatures required to attest to the authenticity of the response. | + +[signing]: https://spec.matrix.org/v1.18/appendices/#signing-details + +```json { - "room_id": "!c10y-t1HZB9jgYr9mmaKtMDsS19HXbWRFc6d0bWGVYU", - "servers": [ - "nexy7574.co.uk", - // ... - ], - "expires_ts": 1778969913414, + "room_id": "!example", + "servers": ["server1.example", "server2.example"], + "expires_ts": 1778991778468, "signatures": { - // ... + "origin.example": { + "ed25519:foobar": "OmJsb2JjYXQgbW9ub2NsZTogOmNvb2tpZTogOmZpc2g6Cg==" + } } } ``` +`404 M_NOT_FOUND`: A **signed** standard error response: + +| key | type | description | +| ------------ | --------------------- | ---------------------------------------------------------------------- | +| `errcode` | string | An error code. | +| `err` | string (optional) | A human-readable error message. | +| `signatures` | [Signatures][signing] | Any signatures required to attest to the authenticity of the response. | + +`502 M_NOT_FOUND`: A standard error response: + +| key | type | description | +| ------------ | --------------------- | ------------------------------- | +| `errcode` | string | An error code. | +| `err` | string (optional) | A human-readable error message. | + +### Signing & Expiry + +If the receiving server owns the alias being queried, and the alias is known, an appropriate +response is generated, ready for signing. The server MUST attach an expiry to the response - +the recommended (and maximum) time period is **7 days**, but it MUST be no less than 1 minute. +The server MUST then [sign the response][signing], before returning it. + +If the receiving server owns the alias being queried, but the alias is *not* known, a `HTTP 404` +response is generated, with `errcode` set to `M_NOT_FOUND`. The server MUST sign the error response +before returning it. The signature is required so that the sending server can pass the response +along if it happens to be acting as a proxy - this allows the error response to be authoritative, +thus signalling to downstreams that resolution should halt. + If the sending server receives a signed response, it MUST [verify the signature][sig-verify] of the -origin server (signatures from other servers are ignored). If the signature is forged/corrupt, or -the key used to sign the response expired before the request was made, the response is discarded, -and the server should be treated as unreachable. +authoritative server (signatures from other servers are ignored). +If the signature cannot be verified, or the key used to sign the response expired before the request +was made, the response is discarded, and the server should be treated as if it were unreachable. [sig-verify]: https://spec.matrix.org/v1.18/appendices/#checking-for-a-signature @@ -85,14 +166,15 @@ forwarding the request, as it will be unable to serve from its cache. ### New room alias resolution steps The sending server MUST first attempt to contact the origin server directly. If the origin server -responds, and the response passes any applicable checks in [§ Signing & Expiry](#signing--expiry), -the sending server MUST use that response and cease any further resolution. This additionally means -sending servers who receive an error response, such as `404 M_NOT_FOUND`, should use that response, -NOT attempt to query other servers. +responds (or returns a signed `404 M_NOT_FOUND` error), and the response passes any applicable +checks in [§ Signing & Expiry](#signing--expiry), the sending server MUST use that response and +cease any further resolution. If the sending server is unable to reach the room alias server, it MAY attempt to contact other servers it trusts (for example, configured notary servers). The sending server MUST NOT allow its -clients to define which servers are queried, unlike with room joins & summaries. +clients to define which servers are queried, unlike with room joins & summaries. If the server does +not trust any other servers, it should return `M_NOT_FOUND` to the requesting client. Proxy servers +MUST NOT attempt to query other proxy servers over federation, in order to prevent recursion loops. Currently, if a homeserver receives a request for an alias which does not belong to it, the request is refused, returning an error code. If this proposal is implemented, the behaviour of queries is @@ -100,56 +182,59 @@ instead changed to the following: 1. If the queried room alias is owned by receiving server, return as normal (see above). 2. If the queried room alias belongs to a different server, - the receiving server should attempt to query the room alias origin directly. - 1. If the room alias origin responds with `404 M_NOT_FOUND`, this is returned to the sending - server. + the receiving server should attempt to query the room alias origin directly. The receiving server + is now a *proxy server*. + 1. If the room alias origin responds with a [valid signed](#signing--expiry) `404 M_NOT_FOUND`, + this is returned to the sending server. Resolution is halted here, the alias does not exist. 2. If the room alias origin responds with a successful response, the response is [checked](#signing--expiry). 3. If the response is valid, it is directly returned to the sending server, and the process ends here. 4. If the response is invalid, or some other error is returned, discard the response and continue. -3. If the receiving server has a cached entry for the queried room alias, AND it passes - [the same checks](#signing--expiry) at the time of the request, the server MAY +3. If the proxy server has a cached entry for the queried room alias, AND it passes + [the same checks](#signing--expiry) at the time of the request, the proxy server MAY return the cached response (see next paragraph). - 1. The sending server MUST verify that the authoritative server signed the response it received, - discarding it if the signature is missing or corrupt. -4. Otherwise, resolution is impossible, and `502 M_NOT_FOUND` is returned (see below). + 1. The sending server MUST verify that the authoritative server signed the response it received + from the proxy server, discarding it if the signature is missing or corrupt. +4. Otherwise, resolution is impossible, and unsigned `502 M_NOT_FOUND` is returned (see below). Step 3 specifically qualifies that servers *MAY* return the cached response, because implementations may prefer to check that the alias is a canonical alias of a room it is a resident of. Servers MAY choose not to return aliases which they cannot find in canonical alias events, however this is up to the receiving server's discretion. -Servers MUST NOT cache responses from servers other than the authoritative server. This prevents an -effect where a stale response may be incorrectly "freshened" by a second-degree server, and then -passed on to a third-degree server, so on. Or visually, in a chain of -`ORIGIN <- A <- B <- C`, `A` can cache the response from `ORIGIN`, and `A` can return that cached -response to `B` (see step 3), but `B` *MUST NOT* cache that response. If `C` attempts to query `B`, -`B` will perform the same steps `A` did, meaning it will always return a fresh response, or a -not-found error. - - -Since origin servers MAY NOT return signatures, unsigned responses MUST NOT be considered for -step 3. Both sides of the connection being required to validate the checks at the time of the -request should prevent this anyway, but this clause is defined specifically to allow servers to -continue the current behaviour of using these responses with a local cache to reduce the number of -lookups performed by local users. +If a sending server is itself later requested to act as a proxy server, it MAY use a cached response +it received from another proxy server, provided it still passes [the checks](#signing--expiry). Or, +in other words, this means a second degree server is allowed to serve the response a first-degree +proxy server gave it, provided it is still fresh. + +It is important to remember that any server can be a proxy server and as such MUST run the full flow +defined above even if they already know they have a valid cached entry - the authoritative origin +MUST always be contacted before serving from a cache, in order to ensure the origin retains complete +control over the validity of the alias. Servers who receive a `502 M_NOT_FOUND` response MAY continue discovery with another trusted server. -However, servers who receive a `404 M_NOT_FOUND` MUST terminate discovery, as this means the origin -server has reported the alias no longer exists. `502` is used to distinguish the *proxy* server not -being capable of resolving the alias from `404`, meaning the origin reports that the alias no longer -exists. - +However, servers who receive a signed `404 M_NOT_FOUND` MUST terminate discovery, as this means the +origin server has reported the alias no longer exists. +`502` is used to distinguish the *proxy* server not being capable of resolving the alias from `404`, +meaning the origin reports that the alias no longer exists, further re-enforced with a signature. + +### Servers that don't support v2 + +If the server being queried is the authoritative server, and it returns `404 M_UNRECOGNIZED`, the +sending server SHOULD re-try the request with the [v1 endpoint][s2s-resolve]. As the v1 endpoint is +not signed, the response from the v1 endpoint MAY be cached for **local use only**, and MUST NOT be +returned as a proxy response under any circumstances. + +If the server being queried is NOT the authoritative server, and it returns `404 M_UNRECOGNIZED`, +the sending server SHOULD re-try the request with a different proxy server, and assume that the one +that returned the error does not support being a proxy server. ## Potential issues See [§ Security Considerations](#security-considerations). -This proposal is designed in a manner that is intentionally 100% backwards compatible with existing -implementations, so no potential issues (other than security considerations) are known at this time. +No other unsolved issues are considered at this time. ## Alternatives @@ -157,6 +242,8 @@ None. ## Security considerations + + **Room aliases are mutable**. By not exclusively querying the authoritative server, this proposal does open up the possibility for stale responses to be considered valid, which carries several concerns, including: @@ -195,7 +282,7 @@ directory route: | Stable | Unstable | | ------ | -------- | -| `GET /_matrix/federation/v1/query/directory` | `GET /_matrix/federation/unstable/uk.timedout.msc4473/query/directory` | +| `GET /_matrix/federation/v2/query/directory` | `GET /_matrix/federation/unstable/uk.timedout.msc4473/query/directory` | Once stabilised, the endpoint can be switched back to the already existing endpoint transparently. From c6602f488ee92b927f24e1f9558b8606213ec722 Mon Sep 17 00:00:00 2001 From: timedout Date: Sun, 17 May 2026 05:50:57 +0100 Subject: [PATCH 9/9] typo fixes --- proposals/4473-proxied-alias-resolution.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/proposals/4473-proxied-alias-resolution.md b/proposals/4473-proxied-alias-resolution.md index 5948302181b..0ec90d0b4db 100644 --- a/proposals/4473-proxied-alias-resolution.md +++ b/proposals/4473-proxied-alias-resolution.md @@ -5,7 +5,7 @@ example, `#matrix:matrix.org` is a user-friendly pointer to `!L58ME6ufiP49v97UIOBIpvWKEgj4912JmECPuDzlvCI` (at the time of writing). Typically, users will provide aliases to their clients, which the client then needs to resolve to a room ID for further use. In order to do this, clients will ask the server to resolve the alias for -them via the [`GET /_matrix/client/v3/directory/room/{roomAlias}][c2s-resolve]. +them via the [`GET /_matrix/client/v3/directory/room/{roomAlias}`][c2s-resolve]. The user's homeserver will then check its local cache and/or database, but if it cannot find an appropriate result for the alias, the server will then contact [`GET matrix-federation://{authority}/_matrix/federation/v1/query/directory`][s2s-resolve] to fetch @@ -153,7 +153,7 @@ Furthermore, if the `expires_ts` is in the past, the response is again discarded choose to abort further resolution attempts on account of the origin itself being reachable, but returning bogus data (the chances of a third-party acquiring a fresh response are low). -Servers MUST NOT ever ret the cached entry past `expires_ts`. Likewise, servers MUST NOT re-use +Servers MUST NOT retain the cached entry past `expires_ts`. Likewise, servers MUST NOT re-use a cached response if the key used to sign it itself expires before `expires_ts`, and it cannot be refreshed. Servers MAY instead choose the lower of the two timestamps when deciding the cache entry's TTL, rather than attempting to re-validate the signature. @@ -284,7 +284,8 @@ directory route: | ------ | -------- | | `GET /_matrix/federation/v2/query/directory` | `GET /_matrix/federation/unstable/uk.timedout.msc4473/query/directory` | -Once stabilised, the endpoint can be switched back to the already existing endpoint transparently. +After the proposal is stabilised, servers SHOULD continue to accept requests to the unstable +endpoint for a period of time, which itself is at the discretion of the implementation. ## Dependencies