fix(federation): validate source authority on membership mutation receivers

This commit is contained in:
Jannis Braun
2026-03-26 20:48:39 +01:00
parent 31729f360e
commit d8563ec505
+18
View File
@@ -1640,6 +1640,12 @@ function processMemberAddEvent(
return; 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 // Cancel soft-delete if channel was pending GC
if (channel.deletedAt) { if (channel.deletedAt) {
db.update(schema.dmChannels) db.update(schema.dmChannels)
@@ -1713,6 +1719,12 @@ function processMemberRemoveEvent(
return; 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); const localUser = resolveLocalUser(event.membership.user.homeUserId, db);
if (!localUser) { if (!localUser) {
accepted.push(event.messageId); accepted.push(event.messageId);
@@ -1782,6 +1794,12 @@ function processOwnershipTransferEvent(
return; 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) // Resolve new owner to local user (for local ownerId)
const newOwnerLocal = resolveLocalUser(event.ownership.newOwner.homeUserId, db); const newOwnerLocal = resolveLocalUser(event.ownership.newOwner.homeUserId, db);