Skip to content
Open
Changes from 4 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
114 changes: 114 additions & 0 deletions proposals/4319-room-member-events-in-stripped-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# MSC4139: Room member events in stripped state
Copy link
Member

Choose a reason for hiding this comment

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

It strikes me that the current proposal is not really "Room member events in stripped state"; rather, it is "add new fields [which are independent of stripped state] to the /sync response".


In the Client-Server API, the response of the [`GET /sync`](https://spec.matrix.org/v1.15/client-server-api/#get_matrixclientv3sync)
endpoint, the `events` array in the `invite_state` for rooms under `invite` and in the `knock_state`
for rooms under `knock` are defined as containing the stripped state of the room.

This stripped state comes over federation, via the
[`PUT /invite`](https://spec.matrix.org/v1.15/server-server-api/#put_matrixfederationv2inviteroomideventid)
and [`PUT /send_knock`](https://spec.matrix.org/v1.15/server-server-api/#put_matrixfederationv1send_knockroomideventid)
endpoints in the Server-Server API.

In the definition of the [stripped state](https://spec.matrix.org/v1.15/client-server-api/#stripped-state)
it is recommended to contain the following state events:

- `m.room.create`
- `m.room.name`
- `m.room.avatar`
- `m.room.topic`
- `m.room.join_rules`
- `m.room.canonical_alias`
- `m.room.encryption`

Although these events are useful to be able to present information about the room, they don't
contain information about the event itself.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
contain information about the event itself.
contain information about the invite/knock event itself.


The following information can be useful for clients:

- The sender of the invite, including their display name and avatar, to present to the user.
- The time of the invite or knock, to present to the user.
- Whether an invite event was preceded by a knock, if the client wants to auto-accept invites that
come from knocking.

_Note that part of this proposed change specifies behavior that has been implemented for a long time
in homeserver implementations and that clients already rely on._


## Proposal

For clients to be able to get details about an invite or knock, the `m.room.member` event that was
created during the invite or knock process MUST be present in the `events` of the `invite_state` or
`knock_state`. Making it mandatory makes sense because this event is the reason why the room appears
in `invite` or `knock` in the first place.

_Note that the example for the response of `GET /sync` already include the stripped `m.room.member`
event although it is not specified._

The stripped state event format is modified in the Client-Server and Server-Server APIs to include
the optional `origin_server_ts`. This property is optional for backwards-compatibility but servers
MUST include the `origin_server_ts` if they have it. It means that in the `/sync` response, stripped
state received over federation might lack this field if the other server didn't send it, but the
`m.room.member` event should always have it, since the server always has this event as a PDU.

In the Client-Server API, the optional `unsigned` property is also added, identical to the one in
other event formats. This property is mostly expected for the `m.room.member` event, for clients to
be able to follow the transitions between membership states by looking at `prev_content`.

Example of an `m.room.member` in `invite_state`:

```json
{
"content": {
"membership": "invite",
"displayname": "Alice"
},
"type": "m.room.member",
"state_key": "@alice:example.org",
"sender": "@bob:example.org",
"origin_server_ts": 1432735824653,
"unsigned": {
"prev_content": {
"membership": "knock",
"displayname": "Alice"
}
}
}
```

Finally, the list of events that should be included in the stripped state is extended with the
stripped `m.room.member` event of the `sender` of the invite. This allows clients to be able to
display information about the sender of an invite, like their display name or avatar.


## Potential issues
Copy link
Contributor

@Gnuxie Gnuxie Sep 8, 2025

Choose a reason for hiding this comment

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

Clients have depended on synapse's behaviour of including the full client formatted event for the m.room.member invite event.

the-draupnir-project/Draupnir#945

In draupnir this dependency is more than just a parsing issue. The matrix-protection-suite provides a handle called handleExternalMembership which invitations are fed into from the matrix-protection-suite's conception of the timeline, which is closer to appservice push than /sync. As appservice push also pushed invitations this way.

Deduplication and buffering is then coordinated using the event id as it is identical to any other client formatted event and not special cased. So this is a deep dependency.

https://github.com/Gnuxie/matrix-protection-suite/blob/a85102c0aaa6ba73beae8fdb35600eec0999eada/src/Protection/Protection.ts#L176
https://github.com/the-draupnir-project/Draupnir/blob/main/src/protections/invitation/JoinRoomsOnInviteProtection.tsx#L86-L96
https://github.com/the-draupnir-project/Draupnir/blob/f1dad52288ff32a096cbd3ff5be110648574c51f/src/Draupnir.ts#L355
https://github.com/Gnuxie/matrix-protection-suite/blob/453e55b182282aad59db430f53549d89541a8e21/src/ClientManagement/StandardClientRooms.ts#L95-L126
https://github.com/the-draupnir-project/Draupnir/blob/f1dad52288ff32a096cbd3ff5be110648574c51f/src/DraupnirBotMode.ts#L97-L99
https://github.com/the-draupnir-project/Draupnir/blob/f1dad52288ff32a096cbd3ff5be110648574c51f/src/appservice/AppService.ts#L355

Copy link
Contributor

Choose a reason for hiding this comment

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

The change complicates clients that allow either /sync or appservice push to be their event source. Since now invitations sourced from /sync will be a different format to those provided in appservice push. See #4319 (comment).

Copy link
Member

Choose a reason for hiding this comment

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

Additionally, the appservice event can't be stripped, because then you'd have two different data formats in the events array of /transactions

In this specific context, the AS API is closer to S-S than C-S /sync, as both AS and S-S send full invite events with stripped state in unsigned -> invite_room_state, while /sync flattens invite event + stripped state into an invite_state array

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have changed the proposal in 1b81df4 to send the full event in a different place to attempt to take care of two concerns at once: the event formats should not be mixed in invite_state, and implementations rely on all fields of the full event format.

Copy link
Member

Choose a reason for hiding this comment

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

It feels like including the event in stripped format in invite_room_state doesn't satisfy anyone, if clients are relying on having full events there? It neither provides backwards-compatibility, nor gives us a clean API. We're just going to end up continuing to be in a mess.

I think I'd suggest a fairly aggressive timeline like

  • have server implementations continue to include the full event in {invite,knock}_room_state as a temporary, strictly time-limited measure
  • clients that are currently relying on the unspecified behaviour having the invite or knock event in {invite,knock}_room_state switch to using state (with fallback to {invite,knock}_room_state, if they want compat with older servers)
  • after a few months, servers remove the event from {invite,knock}_room_state. Clients still relying on the unspecced behaviour are out of luck.

Interested to hear thoughts from others on this, though


By showing more information about the sender of an invite, users might be subject to undesirable
content like abusive language or images. Mitigating this is out of scope of this MSC, and other MSCs
exist for this, like [MSC4278](https://github.com/matrix-org/matrix-spec-proposals/pull/4278).


## Alternatives

We could put the full `m.room.member` event in the `invite_state` or `knock_state`, but mixing event
formats in a list is undesirable.

We could also put the full `m.room.member` event someplace else, like in a `state` property similar
to rooms under `join` or `leave`. It would have the added benefit that homeservers wouldn't need to
edit the stripped state that was received over federation. However this change would not be
compatible with the current ecosystem where clients already depend on all events being in the
`invite_state` or `knock_state`.


## Security considerations

No potential security issues are known to the author.


## Unstable prefix

None necessary, this is adding existing event types into existing arrays.


## Dependencies

None.