From 6b6d351ef8636dbb605e038de11f88ca86e16b4d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 24 Feb 2026 15:35:05 +0200 Subject: [PATCH] Specify basic validation for federation membership endpoints (#2284) Signed-off-by: Tulir Asokan Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --- .../newsfragments/2284.clarification | 1 + content/server-server-api.md | 6 ++- data/api/server-server/invites-v1.yaml | 45 +++++++++-------- data/api/server-server/invites-v2.yaml | 43 +++++++++------- data/api/server-server/joins-v1.yaml | 49 ++++++++++++++++++- data/api/server-server/joins-v2.yaml | 13 +++++ data/api/server-server/knocks.yaml | 35 ++++++++++++- data/api/server-server/leaving-v1.yaml | 35 ++++++++++++- data/api/server-server/leaving-v2.yaml | 22 +++++++++ 9 files changed, 206 insertions(+), 43 deletions(-) create mode 100644 changelogs/server_server/newsfragments/2284.clarification diff --git a/changelogs/server_server/newsfragments/2284.clarification b/changelogs/server_server/newsfragments/2284.clarification new file mode 100644 index 00000000..a494fb56 --- /dev/null +++ b/changelogs/server_server/newsfragments/2284.clarification @@ -0,0 +1 @@ +Specify validation for PDUs passed to and returned from federation membership endpoints. diff --git a/content/server-server-api.md b/content/server-server-api.md index 1ab7e3ba..bc393ae9 100644 --- a/content/server-server-api.md +++ b/content/server-server-api.md @@ -868,8 +868,10 @@ selecting a resident from the candidate list, and using the enough information for the joining server to fill in the event. The joining server is expected to add or replace the `origin`, -`origin_server_ts`, and `event_id` on the templated event received by -the resident server. This event is then signed by the joining server. +`origin_server_ts`, and `event_id` on the templated event received by the +resident server. The joining server MUST also verify that the `type`, `room_id`, +`sender`, `state_key` and `content.membership` fields have the expected values. +This event is then signed by the joining server. To complete the join handshake, the joining server submits this new event to the resident server it used for `GET /make_join`, using the `PUT /send_join` diff --git a/data/api/server-server/invites-v1.yaml b/data/api/server-server/invites-v1.yaml index ec2e56a6..2b97923c 100644 --- a/data/api/server-server/invites-v1.yaml +++ b/data/api/server-server/invites-v1.yaml @@ -36,6 +36,28 @@ paths: Also note that if the remote homeserver is already in the room, it will receive the invite event twice; once through this endpoint, and again through a [federation transaction](/server-server-api/#transactions). + + Servers MUST apply certain validation to ensure they don't accidentally sign non-invite + events from a malicious server. A specific error code is not mandated, but servers SHOULD + return `M_INVALID_PARAM` if: + + * The invite event fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events). + * The event type is not `m.room.member`. + * The `membership` field inside the event content is not `invite`. + * The event sender is not a user ID on the origin server. + * The `state_key` is not a user ID on the receiving server. + + The `invite_room_state` has additional validation, which servers MAY apply to room versions + 1 through 11 and SHOULD apply to all other room versions. As with the above errors, servers + SHOULD return `M_INVALID_PARAM` if: + + * The `m.room.create` event is missing from `invite_room_state`. + * One or more entries in `invite_room_state` are not formatted according + to the room's version. + * One or more events fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events). + * One or more events does not reside in the same room as the invite. + Note: Some room versions may require calculating the room ID for an + event rather than relying on the presence of `room_id`. operationId: sendInviteV1 security: - signedRequest: [] @@ -83,8 +105,7 @@ paths: MUST additionally be formatted according to the room version specification. Servers might need to apply validation to the `invite_room_state` depending - on room version. See the `400 M_MISSING_PARAM` error definition for more - information. + on room version. See endpoint description for more information. Note that events have a different format depending on the room version - check the [room version specification](/rooms) for @@ -178,23 +199,7 @@ paths: } "400": description: |- - The `M_MISSING_PARAM` error code is used to indicate one or more of - the following: - - * The `m.room.create` event is missing from `invite_room_state`. - * One or more entries in `invite_room_state` are not formatted according - to the room's version. - * One or more events fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events). - * One or more events does not reside in the same room as the invite. - Note: Some room versions may require calculating the room ID for an - event rather than relying on the presence of `room_id`. - - Servers MAY apply the validation above to room versions 1 through 11, - and SHOULD apply the validation above to all other room versions. - - If `M_MISSING_PARAM` is returned and the request is associated with a - Client-Server API request, the Client-Server API request SHOULD fail - with a 5xx error rather than being passed through. + The request is invalid in some way. content: application/json: schema: @@ -202,7 +207,7 @@ paths: examples: response: value: { - "errcode": "M_MISSING_PARAM", + "errcode": "M_INVALID_PARAM", "error": "Create event not among invite state entries." } servers: diff --git a/data/api/server-server/invites-v2.yaml b/data/api/server-server/invites-v2.yaml index 9355d00c..b145f339 100644 --- a/data/api/server-server/invites-v2.yaml +++ b/data/api/server-server/invites-v2.yaml @@ -40,6 +40,28 @@ paths: Also note that if the remote homeserver is already in the room, it will receive the invite event twice; once through this endpoint, and again through a [federation transaction](/server-server-api/#transactions). + + Servers MUST apply certain validation to ensure they don't accidentally sign non-invite + events from a malicious server. A specific error code is not mandated, but servers SHOULD + return `M_INVALID_PARAM` if: + + * The invite event fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events). + * The event type is not `m.room.member`. + * The `membership` field inside the event content is not `invite`. + * The event sender is not a user ID on the origin server. + * The `state_key` is not a user ID on the receiving server. + + The `invite_room_state` has additional validation, which servers MAY apply to room versions + 1 through 11 and SHOULD apply to all other room versions. As with the above errors, servers + SHOULD return `M_INVALID_PARAM` if: + + * The `m.room.create` event is missing from `invite_room_state`. + * One or more entries in `invite_room_state` are not formatted according + to the room's version. + * One or more events fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events). + * One or more events does not reside in the same room as the invite. + Note: Some room versions may require calculating the room ID for an + event rather than relying on the presence of `room_id`. operationId: sendInviteV2 security: - signedRequest: [] @@ -84,8 +106,7 @@ paths: MUST additionally be formatted according to the room version specification. Servers might need to apply validation to the `invite_room_state` depending - on room version. See the `400 M_MISSING_PARAM` error definition for more - information. + on room version. See the endpoint description for more information. Note that events have a different format depending on the room version - check the [room version specification](/rooms) for @@ -154,22 +175,8 @@ paths: The error should be passed through to clients so that they may give better feedback to users. - The `M_MISSING_PARAM` error code is used to indicate one or more of - the following: - - * The `m.room.create` event is missing from `invite_room_state`. - * One or more entries in `invite_room_state` are not formatted according - to the room's version. - * One or more events fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events). - * One or more events does not reside in the same room as the invite. - Note: Some room versions may require calculating the room ID for an - event rather than relying on the presence of `room_id`. - - Servers MAY apply the validation above to room versions 1 through 11, - and SHOULD apply the validation above to all other room versions. - - If `M_MISSING_PARAM` is returned and the request is associated with a - Client-Server API request, the Client-Server API request SHOULD fail + If `M_MISSING_PARAM` or `M_INVALID_PARAM` is returned and the request is associated + with a Client-Server API request, the Client-Server API request SHOULD fail with a 5xx error rather than being passed through. content: application/json: diff --git a/data/api/server-server/joins-v1.yaml b/data/api/server-server/joins-v1.yaml index de671ef9..50bd3652 100644 --- a/data/api/server-server/joins-v1.yaml +++ b/data/api/server-server/joins-v1.yaml @@ -23,6 +23,17 @@ paths: description: |- Asks the receiving server to return information that the sending server will need to prepare a join event to get into the room. + + Before signing the returned template and calling `/send_join`, + the sending server MUST verify that: + + * the `room_id` is equal to the `roomId` path parameter. + * both the `sender` and `state_key` are equal to the `userId` path parameter. + * the `type` of the event is `m.room.member`. + * the `membership` field inside `content` is `join`. + + In case any of the above checks fail, the response MUST be treated as malformed and + discarded. The caller MAY try to join through another server. operationId: makeJoin security: - signedRequest: [] @@ -36,7 +47,7 @@ paths: type: string - in: path name: userId - description: The user ID the join event will be for. + description: The user ID the join event will be for. This MUST be a user ID on the origin server. required: true example: "@someone:example.org" schema: @@ -238,6 +249,15 @@ paths: **The request and response body here describe the common event fields in more detail and may be missing other required fields for a PDU.** + + The receiving server MUST apply certain validation before accepting the event. + A specific error code is not mandated, but servers SHOULD return `M_INVALID_PARAM` if: + + * The join event fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events). + * The event type is not `m.room.member`. + * The `membership` field inside the event content is not `join`. + * The event sender is not a user ID on the origin server. + * The `state_key` is not equal to the `sender`. operationId: sendJoinV1 security: - signedRequest: [] @@ -388,6 +408,33 @@ paths: } } ] + "400": + description: |- + The request is invalid in some way. + content: + application/json: + schema: + $ref: ../client-server/definitions/errors/error.yaml + examples: + response: + value: { + "errcode": "M_INVALID_PARAM", + "error": "Not a join event." + } + "403": + description: |- + The room that the joining server is attempting to join does not permit the user + to join. + content: + application/json: + schema: + $ref: ../client-server/definitions/errors/error.yaml + examples: + response: + value: { + "errcode": "M_FORBIDDEN", + "error": "You are not invited to this room" + } servers: - url: "{protocol}://{hostname}{basePath}" variables: diff --git a/data/api/server-server/joins-v2.yaml b/data/api/server-server/joins-v2.yaml index 91e6a83e..c37964b2 100644 --- a/data/api/server-server/joins-v2.yaml +++ b/data/api/server-server/joins-v2.yaml @@ -38,6 +38,15 @@ paths: **The request and response body here describe the common event fields in more detail and may be missing other required fields for a PDU.** + + The receiving server MUST apply certain validation before accepting the event. + A specific error code is not mandated, but servers SHOULD return `M_INVALID_PARAM` if: + + * The join event fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events). + * The event type is not `m.room.member`. + * The `membership` field inside the event content is not `join`. + * The event sender is not a user ID on the origin server. + * The `state_key` is not equal to the `sender`. operationId: sendJoinV2 security: - signedRequest: [] @@ -247,6 +256,10 @@ paths: The error should be passed through to clients so that they may give better feedback to users. + If `M_MISSING_PARAM` or `M_INVALID_PARAM` is returned and the request is associated + with a Client-Server API request, the Client-Server API request SHOULD fail + with a 5xx error rather than being passed through. + New in `v1.2`, the following error conditions might happen: If the room is [restricted](/client-server-api/#restricted-rooms) diff --git a/data/api/server-server/knocks.yaml b/data/api/server-server/knocks.yaml index 38092085..1cc530ff 100644 --- a/data/api/server-server/knocks.yaml +++ b/data/api/server-server/knocks.yaml @@ -23,6 +23,17 @@ paths: description: |- Asks the receiving server to return information that the sending server will need to prepare a knock event for the room. + + Before signing the returned template and calling `/send_knock`, + the sending server MUST verify that: + + * the `room_id` is equal to the `roomId` path parameter. + * both the `sender` and `state_key` are equal to the `userId` path parameter. + * the `type` of the event is `m.room.member`. + * the `membership` field inside `content` is `knock`. + + In case any of the above checks fail, the response MUST be treated as malformed and + discarded. The caller MAY try to knock through another server. operationId: makeKnock security: - signedRequest: [] @@ -36,7 +47,7 @@ paths: type: string - in: path name: userId - description: The user ID the knock event will be for. + description: The user ID the knock event will be for. This MUST be a user ID on the origin server. required: true example: "@someone:example.org" schema: @@ -204,6 +215,15 @@ paths: **The request and response body here describe the common event fields in more detail and may be missing other required fields for a PDU.** + + The receiving server MUST apply certain validation before accepting the event. + A specific error code is not mandated, but servers SHOULD return `M_INVALID_PARAM` if: + + * The knock event fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events). + * The event type is not `m.room.member`. + * The `membership` field inside the event content is not `knock`. + * The event sender is not a user ID on the origin server. + * The `state_key` is not equal to the `sender`. operationId: sendKnock security: - signedRequest: [] @@ -330,6 +350,19 @@ paths: "$ref": "./examples/invite_or_knock_state.json" } } + "400": + description: |- + The request is invalid in some way. + content: + application/json: + schema: + $ref: ../client-server/definitions/errors/error.yaml + examples: + response: + value: { + "errcode": "M_INVALID_PARAM", + "error": "Not a knock event." + } "403": description: |- The knocking server or user is not permitted to knock on the room, such as when the diff --git a/data/api/server-server/leaving-v1.yaml b/data/api/server-server/leaving-v1.yaml index a630f6d7..16ba2262 100644 --- a/data/api/server-server/leaving-v1.yaml +++ b/data/api/server-server/leaving-v1.yaml @@ -23,6 +23,17 @@ paths: description: |- Asks the receiving server to return information that the sending server will need to prepare a leave event to get out of the room. + + Before signing the returned template and calling `/send_leave`, + the sending server MUST verify that: + + * the `room_id` is equal to the `roomId` path parameter. + * both the `sender` and `state_key` are equal to the `userId` path parameter. + * the `type` of the event is `m.room.member`. + * the `membership` field inside `content` is `leave`. + + In case any of the above checks fail, the response MUST be treated as malformed and + discarded. The caller MAY try to leave through another server. operationId: makeLeave security: - signedRequest: [] @@ -36,7 +47,7 @@ paths: type: string - in: path name: userId - description: The user ID the leave event will be for. + description: The user ID the leave event will be for. This MUST be a user ID on the origin server. required: true example: "@someone:example.org" schema: @@ -153,6 +164,15 @@ paths: **The request and response body here describe the common event fields in more detail and may be missing other required fields for a PDU.** + + The receiving server MUST apply certain validation before accepting the event. + A specific error code is not mandated, but servers SHOULD return `M_INVALID_PARAM` if: + + * The leave event fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events). + * The event type is not `m.room.member`. + * The `membership` field inside the event content is not `leave`. + * The event sender is not a user ID on the origin server. + * The `state_key` is not equal to the `sender`. operationId: sendLeaveV1 security: - signedRequest: [] @@ -249,6 +269,19 @@ paths: 200, {} ] + "400": + description: |- + The request is invalid in some way. + content: + application/json: + schema: + $ref: ../client-server/definitions/errors/error.yaml + examples: + response: + value: { + "errcode": "M_INVALID_PARAM", + "error": "Not a leave event." + } servers: - url: "{protocol}://{hostname}{basePath}" variables: diff --git a/data/api/server-server/leaving-v2.yaml b/data/api/server-server/leaving-v2.yaml index 0db16cbe..15fbad75 100644 --- a/data/api/server-server/leaving-v2.yaml +++ b/data/api/server-server/leaving-v2.yaml @@ -38,6 +38,15 @@ paths: **The request and response body here describe the common event fields in more detail and may be missing other required fields for a PDU.** + + The receiving server MUST apply certain validation before accepting the event. + A specific error code is not mandated, but servers SHOULD return `M_INVALID_PARAM` if: + + * The leave event fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events). + * The event type is not `m.room.member`. + * The `membership` field inside the event content is not `leave`. + * The event sender is not a user ID on the origin server. + * The `state_key` is not equal to the `sender`. operationId: sendLeaveV2 security: - signedRequest: [] @@ -134,6 +143,19 @@ paths: examples: response: value: {} + "400": + description: |- + The request is invalid in some way. + content: + application/json: + schema: + $ref: ../client-server/definitions/errors/error.yaml + examples: + response: + value: { + "errcode": "M_INVALID_PARAM", + "error": "Not a leave event." + } servers: - url: "{protocol}://{hostname}{basePath}" variables: