From d85f5c117677a152e9cf55c2963bfef42f814bbd Mon Sep 17 00:00:00 2001 From: Pavan Kumar Gondhi Date: Mon, 10 Aug 2026 10:39:22 +0530 Subject: [PATCH] fix(msteams): fail closed on denied group access (#121086) --- .../msteams/src/monitor-handler/access.ts | 2 +- .../message-handler.authz.test.ts | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/extensions/msteams/src/monitor-handler/access.ts b/extensions/msteams/src/monitor-handler/access.ts index a6641d65f238..0099aaee100e 100644 --- a/extensions/msteams/src/monitor-handler/access.ts +++ b/extensions/msteams/src/monitor-handler/access.ts @@ -341,7 +341,7 @@ export async function admitMSTeamsMessage(params: { }); return null; } - if (!senderAccess.allowed && senderAccess.reasonCode === "group_policy_not_allowlisted") { + if (!senderAccess.allowed) { const allowMatch = resolveMSTeamsAllowlistMatch({ allowFrom: effectiveGroupAllowFrom, senderId, diff --git a/extensions/msteams/src/monitor-handler/message-handler.authz.test.ts b/extensions/msteams/src/monitor-handler/message-handler.authz.test.ts index 576fbd44f825..e3a88c83f598 100644 --- a/extensions/msteams/src/monitor-handler/message-handler.authz.test.ts +++ b/extensions/msteams/src/monitor-handler/message-handler.authz.test.ts @@ -641,6 +641,44 @@ describe("msteams monitor handler authz", () => { ).toBe("19:group@thread.tacv2"); }); + it.each([ + { + name: "missing", + accessGroups: undefined, + }, + { + name: "unsupported", + accessGroups: { + operators: { + type: "discord.channelAudience" as const, + guildId: "guild-1", + channelId: "channel-1", + }, + }, + }, + ])("fails closed when a group sender access group is $name", async ({ accessGroups }) => { + resetThreadMocks(); + const { conversationStore, deps } = createDeps({ + accessGroups, + channels: { + msteams: { + groupPolicy: "allowlist", + groupAllowFrom: ["accessGroup:operators"], + requireMention: false, + }, + }, + } as OpenClawConfig); + + const handler = createMSTeamsMessageHandler(deps); + await handler(createAttackerGroupActivity()); + + expect(conversationStore.upsert).not.toHaveBeenCalled(); + expect(runtimeApiMockState.dispatchReplyWithBufferedBlockDispatcher).not.toHaveBeenCalled(); + expect(logMeta(deps.log.info, "dropping group message (not in groupAllowFrom)").sender).toBe( + "attacker-aad", + ); + }); + it("blocks unauthorized text control commands through shared ingress", async () => { resetThreadMocks(); const hasControlCommand = vi.fn(() => true);