Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation requirements:

  • Client (setting MSC4489 contacts)
  • Client (finding contacts)
  • Server (serving contacts)
  • Server (forwarding contacts from federation to clients)

Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# MSC4490: Surfacing room contacts when a join or knock does not succeed

When a user's attempt to join or knock on a room is rejected — because they are
banned, because their server is denied, or for any other reason — they typically
see a generic message such as "You have been banned from this room" or "This
server is not permitted to join this room". They are given no path to contest
the decision, even when the room has explicitly published a contact for exactly
this purpose.

[MSC4489](https://github.com/matrix-org/matrix-spec-proposals/pull/4489)
introduces the `m.room.contact` state event, which lets a room advertise
responsible contacts. However, a user who is banned or whose server is blocked
generally cannot read room state, so they cannot read that event through normal
means. This proposal lets the resident server include the room's published
contact information in the rejection response, so the joining server can forward
it to the user's client for display.

## Proposal

This builds on [MSC4489](https://github.com/matrix-org/matrix-spec-proposals/pull/4489)
and changes only the *delivery* of already-published information on a failed
join or knock; it introduces no new place for a room to declare contacts and
defines no new error conditions.

### Federation response

When a resident server rejects a remote user's attempt to join or knock on a
room — at whichever point in the join or knock handshake the rejection is issued
(currently the `make_join`, `send_join`, `make_knock`, and `send_knock`
endpoints) — and that room has an `m.room.contact` state event with the empty
state key, the server MAY include the content of that event in the error
response under a dedicated `contact` key:

```json
{
"errcode": "M_FORBIDDEN",
"error": "You are banned from this room",
"contact": {
"contacts": [
{
"matrix_id": "@alice:example.org",
"email_address": "appeals@example.org",
"role": "m.role.admin"
}
],
"support_page": "https://example.org/room-support-page"
}
}
```

The `contact` value, when present, MUST be the verbatim `content` of the room's
`m.room.contact` state event. This proposal adds only an OPTIONAL field to
existing rejection responses: it does not change the `errcode` or HTTP status
code of any response and introduces no new error conditions or error codes. A
server MAY omit the field (for example if the event is absent, or by policy),
and consumers MUST treat its absence as "no contact information available"
rather than as an error.

Because this only adds an optional field to error responses that are already
produced by the join and knock endpoints, it introduces no new authentication,
rate-limiting, or guest-access requirements on either the Server-Server or
Client-Server side, and no new request path.

### Client-Server delivery

When a user's own server receives a federated join or knock rejection containing
`contact`, it SHOULD propagate that object to the client in the corresponding
Client-Server API error response (e.g. the response to
`POST /_matrix/client/v3/join/{roomIdOrAlias}`,
`/_matrix/client/v3/rooms/{roomId}/join`, or
`/_matrix/client/v3/knock/{roomIdOrAlias}`) under the same `contact` key.

For a purely local rejection (the user's own server is the resident server and
rejects the join or knock), the server MAY populate `contact` directly from
local room state using the same rules.

### Client behaviour

A client that receives a rejection containing `contact` SHOULD, instead of (or
in addition to) the generic rejection message, present the contact information
so the user can seek help or appeal — for example, "You have been banned from
this room. To appeal, contact @alice:example.org or visit \<support_page\>."
Clients MUST apply the same security treatment defined in
[MSC4489](https://github.com/matrix-org/matrix-spec-proposals/pull/4489): the
data is an unverified claim by the room, contacts MUST NOT be shown as verified
identities, and `support_page` MUST be handled with the client's normal
untrusted-URL safety measures.

## Potential issues

- **Information disclosure to rejected users.** This intentionally exposes the
contact event to a user who could not otherwise read room state. As the event
is explicitly intended to be reachable by people seeking to appeal — including
banned users — this is the desired behaviour. No other room state is exposed.
- **Server discretion causes inconsistency.** Because inclusion is OPTIONAL,
whether a rejected user sees contact info depends on the resident server's
implementation and policy. This is acceptable: the field is a best-effort
courtesy, and the fallback is exactly today's behaviour.

## Alternatives

- **Require clients to fetch the event via a peek/`/state` request after a
failed join.** Rejected users generally cannot peek or read `/state`, so this
does not work for the banned/blocked case that motivates the proposal.
- **A dedicated "appeal info" endpoint keyed by room ID.** This adds a new
endpoint (with its own auth, rate-limiting, abuse, and enumeration concerns)
to deliver data the rejection response can already carry inline. Inlining is
simpler and leaks no more than the rejection itself.
- **Folding this into [MSC4489](https://github.com/matrix-org/matrix-spec-proposals/pull/4489).**
Kept separate deliberately so that the state event (useful to existing members
and in-room bots on its own) is not blocked
on federation and client changes.

## Security considerations

In addition to the considerations inherited from
[MSC4489](https://github.com/matrix-org/matrix-spec-proposals/pull/4489)
(impersonation, email harvesting, `support_page` phishing, and the
no-authorisation-signal rule, all of which apply unchanged to the forwarded copy):

- **Disclosure scope.** The `contact` field MUST contain only the `content` of
the room's `m.room.contact` event; a resident server MUST NOT place any other
room state inside this field. The event's content is the only thing the room
has designated as publicly contactable. This says nothing about other fields
in the rejection response, which remain governed by their own definitions.
- **Spoofed rejection responses.** A malicious resident server could attach an
arbitrary `contact` object to a rejection, directing a rejected user's appeal
to an attacker-controlled address. This is bounded by the fact that the
resident server already controls the room's state and the rejection itself; a
user who does not trust the resident server has no stronger guarantee
elsewhere. Clients MUST therefore present the contact as room-supplied and
unverified.
- **Untrusted content handling.** The `contact` object is room-controlled
content delivered to a non-member, and a receiving server MUST treat it as
untrusted. It MUST apply the same size and validation limits it already
applies to the response in question, and MUST handle a malformed or oversized
object gracefully — dropping the field rather than failing the rejection it
accompanies. A resident server MAY decline to include the field for any
reason.

## Unstable prefix

Until this proposal is accepted, the response field uses
`net.codestorm.msc4490.contact` (rather than the bare `contact`), in both the
Server-Server and Client-Server rejection responses:

| Proposed (this MSC) | Unstable form |
|----------------------------|---------------------------------|
| `contact` (response field) | `net.codestorm.msc4490.contact` |

The embedded object reuses the event content defined by
[MSC4489](https://github.com/matrix-org/matrix-spec-proposals/pull/4489)
and follows that proposal's own unstable-prefix rules for its inner fields.

## Dependencies

This proposal depends on
[MSC4489](https://github.com/matrix-org/matrix-spec-proposals/pull/4489)
(`m.room.contact` state event), which MUST be accepted first.
Loading