From 8a9d3071778eb146109ae6ee7fc09daabe706fd1 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Thu, 12 Mar 2026 08:51:26 +0000 Subject: [PATCH] Matrix: avoid alias trust in group context --- .../matrix/src/matrix/monitor/handler.test.ts | 38 +++++++++++++++++++ .../matrix/src/matrix/monitor/handler.ts | 26 ++++++++++--- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/extensions/matrix/src/matrix/monitor/handler.test.ts b/extensions/matrix/src/matrix/monitor/handler.test.ts index 50c4ecbd9bb..45add7bbbe2 100644 --- a/extensions/matrix/src/matrix/monitor/handler.test.ts +++ b/extensions/matrix/src/matrix/monitor/handler.test.ts @@ -256,6 +256,7 @@ describe("matrix monitor handler pairing account scope", () => { it("skips media downloads for unmentioned group media messages", async () => { const downloadContent = vi.fn(async () => Buffer.from("image")); const getMemberDisplayName = vi.fn(async () => "sender"); + const getRoomInfo = vi.fn(async () => ({ altAliases: [] })); const { handler, resolveAgentRoute } = createMatrixHandlerTestHarness({ client: { downloadContent, @@ -263,6 +264,7 @@ describe("matrix monitor handler pairing account scope", () => { isDirectMessage: false, mentionRegexes: [/@bot/i], getMemberDisplayName, + getRoomInfo, }); await handler("!room:example.org", { @@ -283,6 +285,7 @@ describe("matrix monitor handler pairing account scope", () => { expect(downloadContent).not.toHaveBeenCalled(); expect(getMemberDisplayName).not.toHaveBeenCalled(); + expect(getRoomInfo).not.toHaveBeenCalled(); expect(resolveAgentRoute).not.toHaveBeenCalled(); }); @@ -307,6 +310,7 @@ describe("matrix monitor handler pairing account scope", () => { prevBatch: null, })); const getMemberDisplayName = vi.fn(async () => "sender"); + const getRoomInfo = vi.fn(async () => ({ altAliases: [] })); const { handler, resolveAgentRoute } = createMatrixHandlerTestHarness({ client: { getEvent, @@ -315,6 +319,7 @@ describe("matrix monitor handler pairing account scope", () => { isDirectMessage: false, mentionRegexes: [/@bot/i], getMemberDisplayName, + getRoomInfo, }); await handler("!room:example.org", { @@ -336,6 +341,7 @@ describe("matrix monitor handler pairing account scope", () => { expect(getEvent).not.toHaveBeenCalled(); expect(getRelations).not.toHaveBeenCalled(); expect(getMemberDisplayName).not.toHaveBeenCalled(); + expect(getRoomInfo).not.toHaveBeenCalled(); expect(resolveAgentRoute).not.toHaveBeenCalled(); }); @@ -382,6 +388,38 @@ describe("matrix monitor handler pairing account scope", () => { ); }); + it("uses stable room ids instead of room-declared aliases in group context", async () => { + const { handler, finalizeInboundContext } = createMatrixHandlerTestHarness({ + isDirectMessage: false, + getRoomInfo: async () => ({ + name: "Ops Room", + canonicalAlias: "#spoofed:example.org", + altAliases: ["#alt:example.org"], + }), + getMemberDisplayName: async () => "sender", + dispatchReplyFromConfig: async () => ({ + queuedFinal: false, + counts: { final: 0, block: 0, tool: 0 }, + }), + }); + + await handler( + "!room:example.org", + createMatrixTextMessageEvent({ + eventId: "$group1", + body: "@room hello", + mentions: { room: true }, + }), + ); + + expect(finalizeInboundContext).toHaveBeenCalledWith( + expect.objectContaining({ + GroupSubject: "Ops Room", + GroupChannel: "!room:example.org", + }), + ); + }); + it("routes bound Matrix threads to the target session key", async () => { registerSessionBindingAdapter({ channel: "matrix", diff --git a/extensions/matrix/src/matrix/monitor/handler.ts b/extensions/matrix/src/matrix/monitor/handler.ts index 8b052956772..c73272660b7 100644 --- a/extensions/matrix/src/matrix/monitor/handler.ts +++ b/extensions/matrix/src/matrix/monitor/handler.ts @@ -225,10 +225,6 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam return; } - const roomInfo = await getRoomInfo(roomId); - const roomName = roomInfo.name; - const roomAliases = [roomInfo.canonicalAlias ?? "", ...roomInfo.altAliases].filter(Boolean); - let content = event.content as RoomMessageEventContent; if ( @@ -260,16 +256,32 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam selfUserId, }); const isRoom = !isDirectMessage; + let roomInfoPromise: Promise<{ + name?: string; + canonicalAlias?: string; + altAliases: string[]; + }> | null = null; + const getResolvedRoomInfo = async () => { + roomInfoPromise ??= getRoomInfo(roomId); + return await roomInfoPromise; + }; if (isRoom && groupPolicy === "disabled") { return; } + const roomInfoForConfig = + isRoom && roomsConfig && Object.keys(roomsConfig).some((key) => key.trim().startsWith("#")) + ? await getResolvedRoomInfo() + : undefined; + const roomAliasesForConfig = roomInfoForConfig + ? [roomInfoForConfig.canonicalAlias ?? "", ...roomInfoForConfig.altAliases].filter(Boolean) + : []; const roomConfigInfo = isRoom ? resolveMatrixRoomConfig({ rooms: roomsConfig, roomId, - aliases: roomAliases, + aliases: roomAliasesForConfig, }) : undefined; const roomConfig = roomConfigInfo?.config; @@ -548,6 +560,8 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam return; } const senderName = await getSenderName(); + const roomInfo = isRoom ? await getResolvedRoomInfo() : undefined; + const roomName = roomInfo?.name; const messageId = event.event_id ?? ""; const replyToEventId = content["m.relates_to"]?.["m.in_reply_to"]?.event_id; @@ -622,7 +636,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam SenderId: senderId, SenderUsername: senderId.split(":")[0]?.replace(/^@/, ""), GroupSubject: isRoom ? (roomName ?? roomId) : undefined, - GroupChannel: isRoom ? (roomInfo.canonicalAlias ?? roomId) : undefined, + GroupChannel: isRoom ? roomId : undefined, GroupSystemPrompt: isRoom ? groupSystemPrompt : undefined, Provider: "matrix" as const, Surface: "matrix" as const,