diff --git a/CHANGELOG.md b/CHANGELOG.md index 510a403865b..92032a91680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Browser/SSRF: block private-network intermediate redirect hops in strict browser navigation flows and fail closed when remote tab-open paths cannot inspect redirect chains. Thanks @zpbrent. +- MS Teams/authz: keep `groupPolicy: "allowlist"` enforcing sender allowlists even when a team/channel route allowlist is configured, so route matches no longer widen group access to every sender in that route. Thanks @zpbrent. ## 2026.3.8 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 f019287e151..4997b43c754 100644 --- a/extensions/msteams/src/monitor-handler/message-handler.authz.test.ts +++ b/extensions/msteams/src/monitor-handler/message-handler.authz.test.ts @@ -5,7 +5,7 @@ import { setMSTeamsRuntime } from "../runtime.js"; import { createMSTeamsMessageHandler } from "./message-handler.js"; describe("msteams monitor handler authz", () => { - it("does not treat DM pairing-store entries as group allowlist entries", async () => { + function createDeps(cfg: OpenClawConfig) { const readAllowFromStore = vi.fn(async () => ["attacker-aad"]); setMSTeamsRuntime({ logging: { shouldLogVerbose: () => false }, @@ -35,16 +35,7 @@ describe("msteams monitor handler authz", () => { }; const deps: MSTeamsMessageHandlerDeps = { - cfg: { - channels: { - msteams: { - dmPolicy: "pairing", - allowFrom: [], - groupPolicy: "allowlist", - groupAllowFrom: [], - }, - }, - } as OpenClawConfig, + cfg, runtime: { error: vi.fn() } as unknown as RuntimeEnv, appId: "test-app", adapter: {} as MSTeamsMessageHandlerDeps["adapter"], @@ -65,6 +56,21 @@ describe("msteams monitor handler authz", () => { } as unknown as MSTeamsMessageHandlerDeps["log"], }; + return { conversationStore, deps, readAllowFromStore }; + } + + it("does not treat DM pairing-store entries as group allowlist entries", async () => { + const { conversationStore, deps, readAllowFromStore } = createDeps({ + channels: { + msteams: { + dmPolicy: "pairing", + allowFrom: [], + groupPolicy: "allowlist", + groupAllowFrom: [], + }, + }, + } as OpenClawConfig); + const handler = createMSTeamsMessageHandler(deps); await handler({ activity: { @@ -96,4 +102,54 @@ describe("msteams monitor handler authz", () => { }); expect(conversationStore.upsert).not.toHaveBeenCalled(); }); + + it("does not widen sender auth when only a teams route allowlist is configured", async () => { + const { conversationStore, deps } = createDeps({ + channels: { + msteams: { + dmPolicy: "pairing", + allowFrom: [], + groupPolicy: "allowlist", + groupAllowFrom: [], + teams: { + team123: { + channels: { + "19:group@thread.tacv2": { requireMention: false }, + }, + }, + }, + }, + }, + } as OpenClawConfig); + + const handler = createMSTeamsMessageHandler(deps); + await handler({ + activity: { + id: "msg-1", + type: "message", + text: "hello", + from: { + id: "attacker-id", + aadObjectId: "attacker-aad", + name: "Attacker", + }, + recipient: { + id: "bot-id", + name: "Bot", + }, + conversation: { + id: "19:group@thread.tacv2", + conversationType: "groupChat", + }, + channelData: { + team: { id: "team123", name: "Team 123" }, + channel: { name: "General" }, + }, + attachments: [], + }, + sendActivity: vi.fn(async () => undefined), + } as unknown as Parameters[0]); + + expect(conversationStore.upsert).not.toHaveBeenCalled(); + }); }); diff --git a/extensions/msteams/src/monitor-handler/message-handler.ts b/extensions/msteams/src/monitor-handler/message-handler.ts index 2f14945f65e..6fe227537d3 100644 --- a/extensions/msteams/src/monitor-handler/message-handler.ts +++ b/extensions/msteams/src/monitor-handler/message-handler.ts @@ -242,10 +242,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) { } const senderGroupAccess = evaluateSenderGroupAccessForPolicy({ groupPolicy, - groupAllowFrom: - effectiveGroupAllowFrom.length > 0 || !channelGate.allowlistConfigured - ? effectiveGroupAllowFrom - : ["*"], + groupAllowFrom: effectiveGroupAllowFrom, senderId, isSenderAllowed: (_senderId, allowFrom) => resolveMSTeamsAllowlistMatch({