From d8563ec5054b78173aad0d3fc89470b9a60e2bba Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:48:39 +0100 Subject: [PATCH] fix(federation): validate source authority on membership mutation receivers --- packages/server/src/routes/federation.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/server/src/routes/federation.ts b/packages/server/src/routes/federation.ts index f4e4fb0d..9a4fbf90 100644 --- a/packages/server/src/routes/federation.ts +++ b/packages/server/src/routes/federation.ts @@ -1640,6 +1640,12 @@ function processMemberAddEvent( return; } + // Validate authority: only the owner's instance can add members + if (channel.ownerHomeInstance && sourceInstance !== channel.ownerHomeInstance) { + rejected.push({ messageId: event.messageId, reason: 'unauthorized_source' }); + return; + } + // Cancel soft-delete if channel was pending GC if (channel.deletedAt) { db.update(schema.dmChannels) @@ -1713,6 +1719,12 @@ function processMemberRemoveEvent( return; } + // Validate authority: owner's instance for kicks, any instance for self-leave + if (event.membership.reason !== 'leave' && channel.ownerHomeInstance && sourceInstance !== channel.ownerHomeInstance) { + rejected.push({ messageId: event.messageId, reason: 'unauthorized_source' }); + return; + } + const localUser = resolveLocalUser(event.membership.user.homeUserId, db); if (!localUser) { accepted.push(event.messageId); @@ -1782,6 +1794,12 @@ function processOwnershipTransferEvent( return; } + // Validate authority: only the current owner's instance can transfer ownership + if (channel.ownerHomeInstance && sourceInstance !== channel.ownerHomeInstance) { + rejected.push({ messageId: event.messageId, reason: 'unauthorized_source' }); + return; + } + // Resolve new owner to local user (for local ownerId) const newOwnerLocal = resolveLocalUser(event.ownership.newOwner.homeUserId, db);