Skip to content

MSC4311: Use full PDU's in stripped state (like invite_room_state) over federation and always include m.room.create event#19723

Open
MadLittleMods wants to merge 33 commits into
developfrom
madlittlemods/remove-flawed-msc4311-partial-implementation
Open

MSC4311: Use full PDU's in stripped state (like invite_room_state) over federation and always include m.room.create event#19723
MadLittleMods wants to merge 33 commits into
developfrom
madlittlemods/remove-flawed-msc4311-partial-implementation

Conversation

@MadLittleMods

@MadLittleMods MadLittleMods commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Background

This PR was originally just trying to remove the flawed MSC4311 partial implementation as client side API's like /sync should still use stripped events. But it turns out we were just re-using the client logic for the federation side and things might break if we didn't include the full m.room.create event so this PR now introduces MSC4311 support to use full PDU's in the invite_room_state/knock_room_state in the federation API's.

The flawed implementation was originally introduced in 0eb7252 (no PR I assume because part of Hydra security fix)

Spawning from reviewing #19722 and noticing that we have TestMSC4311FullCreateEventOnStrippedState in Complement which already passes even though that test looks flawed:

I think this test is mixing up what MSC4311 proposes. Perhaps these were changes to the MSC that came after?

For the client API's like /sync, it only proposes that m.room.create is a required stripped state event.

For the federation API's, alongside requiring m.room.create, it also mandates using the full event PDU format for all events in the invite_room_state/knock_room_state on m.room.member events (in unsigned)

What does this PR do?

  1. Always use stripped state for client API's
    1. Remove flawed MSC4311 partial implementation (as explained above)
    2. Sanitize stripped state when we receive events over federation
  2. Use full PDU's when sending invite_room_state/knock_room_state over federation
  3. Validate PDU's and warn when receiving invite_room_state/knock_room_state over federation

Complement tests: matrix-org/complement#796


Part of #19414

Dev notes

COMPLEMENT_DIR=../complement ./scripts-dev/complement.sh -run TestMSC4311FullEventsOnStrippedStateFederation
COMPLEMENT_DIR=../complement ./scripts-dev/complement.sh -run TestMSC4311StrippedStateClientApi

Todo

Pull Request Checklist

  • Pull request is based on the develop branch
  • Pull request includes a changelog file. The entry should:
    • Be a short description of your change which makes sense to users. "Fixed a bug that prevented receiving messages from other servers." instead of "Moved X method from EventStore to EventWorkerStore.".
    • Use markdown where necessary, mostly for code blocks.
    • End with either a period (.) or an exclamation mark (!).
    • Start with a capital letter.
    • Feel free to credit yourself, by adding a sentence "Contributed by @github_username." or "Contributed by [Your Name]." to the end of the entry.
  • Code style is correct (run the linters)

@MadLittleMods MadLittleMods changed the title Client side API's like /sync should still use stripped events with MSC4311 Remove flawed MSC4311 partial implementation: Client side API's like /sync should still use stripped events Apr 23, 2026
@MadLittleMods MadLittleMods changed the title Remove flawed MSC4311 partial implementation: Client side API's like /sync should still use stripped events Remove flawed MSC4311 partial implementation: Client-side API's like /sync should still use stripped events Apr 23, 2026
Comment thread synapse/events/utils.py
and event.get_state_key() == ""
):
return event.get_pdu_json()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removing the flawed MSC4311 partial implementation


return [strip_event(e) for e in state_to_include.values()]

async def get_stripped_room_state_ids_from_event_context(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Split some logic out of get_stripped_room_state_from_event_context(...) into get_stripped_room_state_ids_from_event_context(...)

This way we can use the same event selection logic but fetch/format them however we see fit.

Comment on lines -515 to -516
# TODO(paul): assert that room_id/event_id parsed from path actually
# match those given in content

@MadLittleMods MadLittleMods May 4, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed as we actually do this now in FederationServer.on_invite_request(...) (added in this PR)

@MadLittleMods MadLittleMods changed the title Remove flawed MSC4311 partial implementation: Client-side API's like /sync should still use stripped events MSC4311: Use full PDU's in stripped state (like invite_room_state) over federation and always include m.room.create event May 4, 2026
Comment on lines +464 to +467
# Add the invite itself
#
# FIXME: Doesn't seem to be in the spec
invited_state.append(strip_event(room.invite))

@MadLittleMods MadLittleMods May 22, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Found that we were accidentally providing the full invite event in this case. This is just something that's been happening since inception (2015)

The Complement tests check to make sure the client sees stripped state which is how I caught this.

Additionally, just documenting this existing behavior.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

To note, the stripped state event fields cover the use cases described in matrix-org/synapse#6739 (comment). The client has the invite sender to show who invited you.

# the client with:
#
# * A knock state event that they can use for easier internal tracking
# * The rough timestamp of when the knock occurred contained within the event

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed the note about the timestamp as a stripped state event doesn't have origin_server_ts like the full PDU would.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There is some previous discussion in matrix-org/synapse#6739 (comment) where this is both argued for and against.

The timestamp use case seems to be theoretical vs something the client actually does so I'm minded to just to align with the spec. If it's useful, it deserves its own MSC.

invited_state.append(invite)
# Add the invite itself
#
# FIXME: Doesn't seem to be in the spec

@MadLittleMods MadLittleMods May 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As a note, in the spec /sync example under invite_state: it does actually include the m.room.member invite:

{
  // ...
  "rooms": {
    "invite": {
      "!696r7674:example.com": {
        "invite_state": {
          "events": [
            {
              "content": {
                "name": "My Room Name"
              },
              "sender": "@alice:example.com",
              "state_key": "",
              "type": "m.room.name"
            },
            {
              "content": {
                "membership": "invite"
              },
              "sender": "@alice:example.com",
              "state_key": "@bob:example.com",
              "type": "m.room.member"
            }
          ]
        }
      }
    },
  // ...
}

But m.room.member is not mentioned in the list stripped state event types in https://spec.matrix.org/v1.18/client-server-api/#stripped-state

And my guess is that the example is just copy-pasted from Synapse though so I don't think it's a great beacon to hang our hat on. The idea makes sense and should get its own MSC.

Comment thread changelog.d/19723.bugfix
@@ -0,0 +1 @@
Remove flawed [MSC4311](https://github.com/matrix-org/matrix-spec-proposals/pull/4311) partial implementation: Client-side API's like `/sync` should still use stripped events.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The Complement tests are expected to fail ❌ as we removed the flawed partial implementation in this PR.

The Complement tests have been updated in matrix-org/complement#796 and pass locally. We will merge both PRs at the same time.

Comment on lines -192 to -193
# Ensure the event has been stripped
self.assertNotIn("signatures", event)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed as these can be full PDU's now (that's the whole point of MSC4311)

"(either one could be at fault).",
Codes.UNKNOWN,
additional_fields={
"cause": err.msg,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't think we have to worry about leaking anything by passing this error message along. Calling this out so the reviewer can sanity check this assumption

@MadLittleMods MadLittleMods marked this pull request as ready for review May 27, 2026 21:04
@MadLittleMods MadLittleMods requested a review from a team as a code owner May 27, 2026 21:04
@anoadragon453 anoadragon453 requested a review from Copilot May 28, 2026 15:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR implements MSC4311 handling for federation invite/knock room state while keeping client APIs on stripped-state formats. It adds full-PDU federation payloads, inbound validation/sanitization, and a room-version capability flag for version 12+ behavior.

Changes:

  • Restores client stripped-state serialization and updates /sync invite/knock rendering.
  • Sends full PDUs for federation invite_room_state/knock_room_state and validates received state.
  • Adds storage helpers, room-version metadata, changelog, and test updates for new call signatures/behavior.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
changelog.d/19723.bugfix Records the MSC4311 client stripped-state bugfix.
rust/src/room_versions.rs Adds the MSC4311 room-version capability flag.
synapse/events/utils.py Removes full-PDU special casing from strip_event and adds stripped-state serialization.
synapse/federation/federation_client.py Builds federation invite room state as full PDUs and maps MSC4311 remote errors.
synapse/federation/federation_server.py Validates invite path IDs and returns full-PDU knock room state.
synapse/federation/transport/server/federation.py Passes expected invite path IDs through transport handlers.
synapse/handlers/federation.py Adds inbound invite/knock room-state validation and sanitization.
synapse/handlers/message.py Passes invite event context into federation invite sending.
synapse/rest/client/sync.py Ensures client invite/knock events remain stripped in sync responses.
synapse/storage/databases/main/events_worker.py Splits stripped-state event ID retrieval from stripped event serialization.
synapse/synapse_rust/room_versions.pyi Exposes the new room-version flag to Python typing.
tests/events/test_auto_accept_invites.py Updates invite handler calls to keyword arguments.
tests/federation/transport/test_knocking.py Updates knock federation expectations for full-PDU state.
tests/handlers/test_federation.py Updates invite handler calls to keyword arguments.
tests/handlers/test_room_member.py Updates invite handler calls to keyword arguments.
tests/handlers/test_room_summary.py Updates invite handler calls to keyword arguments.
tests/test_visibility.py Updates federation invite test to pass expected path IDs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1437 to +1441
_, content = await self.transport_layer.send_invite_v1(
destination=destination,
room_id=pdu.room_id,
event_id=pdu.event_id,
content=pdu.get_pdu_json(time_now),
# validation above. The only reason this is here is because the validation
# can fail for non-compliant servers but we should still use stripped state.
stripped_room_state_for_client = self._minimal_parse_stripped_room_state(
stripped_room_state=event.unsigned.get("knock_room_state"),
raise SynapseError(
500,
f"Received {HTTPStatus.BAD_REQUEST} {Codes.MISSING_PARAM} response from remote homeserver "
"while trying to send the invite over federation. This indicates a compatability problem "
raise SynapseError(
500,
f"Received {HTTPStatus.BAD_REQUEST} {Codes.MISSING_PARAM} response from remote homeserver "
"while trying to send the invite over federation. This indicates a compatability problem "
Comment on lines +1374 to +1376
# Find the full events based on the state at the time of the invite
state_ids = await self.store.get_stripped_room_state_ids_from_event_context(
context, self._room_prejoin_state_types
@MadLittleMods MadLittleMods removed the request for review from a team May 28, 2026 16:02
jason-famedly added a commit to famedly/synapse-upstreaming that referenced this pull request Jun 18, 2026
…c/test_rooms_invites.SlidingSyncRoomsInvitesTestCase to use 'stripped state' to compare against responses instead of fleshing out a full PDU
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants