Medium severity5.3GHSA Advisory· Published May 5, 2026· Updated May 7, 2026
CVE-2026-43572
CVE-2026-43572
Description
OpenClaw versions 2026.4.10 before 2026.4.14 contain a missing authorization vulnerability in the Microsoft Teams SSO invoke handler that fails to apply sender allowlist checks. Attackers can bypass sender authorization by sending SSO invoke requests that are processed without proper validation, allowing unauthorized access to Teams SSO signin functionality.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
openclawnpm | >= 2026.4.10, < 2026.4.14 | 2026.4.14 |
Affected products
2Patches
180b1fa17bfc3fix(msteams): enforce sender allowlist checks on SSO signin invokes [AI] (#66033)
3 files changed · +203 −11
CHANGELOG.md+1 −0 modified@@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- fix(msteams): enforce sender allowlist checks on SSO signin invokes [AI]. (#66033) Thanks @pgondhi987. - fix(config): redact sourceConfig and runtimeConfig alias fields in redactConfigSnapshot [AI]. (#66030) Thanks @pgondhi987. - Agents/context engines: run opt-in turn maintenance as idle-aware background work so the next foreground turn no longer waits on proactive maintenance. (#65233) thanks @100yenadmin
extensions/msteams/src/monitor-handler.sso.test.ts+148 −4 modified@@ -1,5 +1,5 @@ import { beforeAll, describe, expect, it, vi } from "vitest"; -import type { PluginRuntime } from "../runtime-api.js"; +import type { OpenClawConfig, PluginRuntime } from "../runtime-api.js"; import { type MSTeamsActivityHandler, type MSTeamsMessageHandlerDeps, @@ -95,7 +95,20 @@ function createSigninInvokeContext(params: { value: unknown; userAadId?: string; userBfId?: string; + conversationId?: string; + conversationType?: "personal" | "groupChat" | "channel"; + teamId?: string; + channelName?: string; }): MSTeamsTurnContext & { sendActivity: ReturnType<typeof vi.fn> } { + const conversationType = params.conversationType ?? "personal"; + const conversationId = + params.conversationId ?? + (conversationType === "personal" + ? "19:personal-chat" + : conversationType === "channel" + ? "19:channel@thread.tacv2" + : "19:group@thread.tacv2"); + return { activity: { id: "invoke-1", @@ -110,10 +123,16 @@ function createSigninInvokeContext(params: { }, recipient: { id: "bot-id", name: "Bot" }, conversation: { - id: "19:personal-chat", - conversationType: "personal", + id: conversationId, + conversationType, + tenantId: params.teamId ? "tenant-1" : undefined, }, - channelData: {}, + channelData: params.teamId + ? { + team: { id: params.teamId, name: "Team 1" }, + channel: params.channelName ? { name: params.channelName } : undefined, + } + : {}, attachments: [], value: params.value, }, @@ -150,6 +169,69 @@ function createFakeFetch(handlers: Array<(url: string, init?: unknown) => unknow return { fetchImpl, calls }; } +function createBlockedSigninScenarios() { + return [ + { + name: "DM sender outside allowlist", + cfg: { + channels: { + msteams: { + dmPolicy: "allowlist", + allowFrom: ["owner-aad"], + }, + }, + } as OpenClawConfig, + context: { + userAadId: "blocked-dm-aad", + }, + expectedDropLog: "dropping signin invoke (dm sender not allowlisted)", + }, + { + name: "channel outside route allowlist", + cfg: { + channels: { + msteams: { + groupPolicy: "allowlist", + groupAllowFrom: ["blocked-channel-aad"], + teams: { + "team-allowlisted": { + channels: { + "19:allowlisted@thread.tacv2": { requireMention: false }, + }, + }, + }, + }, + }, + } as OpenClawConfig, + context: { + userAadId: "blocked-channel-aad", + conversationType: "channel" as const, + conversationId: "19:blocked-channel@thread.tacv2", + teamId: "team-blocked", + channelName: "General", + }, + expectedDropLog: "dropping signin invoke (not in team/channel allowlist)", + }, + { + name: "group sender outside group allowlist", + cfg: { + channels: { + msteams: { + groupPolicy: "allowlist", + groupAllowFrom: ["owner-aad"], + }, + }, + } as OpenClawConfig, + context: { + userAadId: "blocked-group-aad", + conversationType: "groupChat" as const, + conversationId: "19:group-chat@thread.v2", + }, + expectedDropLog: "dropping signin invoke (group sender not allowlisted)", + }, + ]; +} + describe("msteams signin invoke value parsers", () => { it("parses signin/tokenExchange values", () => { expect( @@ -321,6 +403,18 @@ describe("msteams signin invoke handler registration", () => { installTestRuntime(); }); + const blockedSigninScenarios = createBlockedSigninScenarios(); + const invokeVariants = [ + { + name: "signin/tokenExchange" as const, + value: { id: "x", connectionName: "GraphConnection", token: "exchangeable" }, + }, + { + name: "signin/verifyState" as const, + value: { state: "112233" }, + }, + ]; + it("acks signin invokes even when sso is not configured", async () => { const deps = createDepsWithoutSso(); const { handler, run } = createActivityHandler(); @@ -348,6 +442,56 @@ describe("msteams signin invoke handler registration", () => { ); }); + for (const invoke of invokeVariants) { + for (const scenario of blockedSigninScenarios) { + it(`does not process ${invoke.name} for ${scenario.name}`, async () => { + const { fetchImpl, calls } = createFakeFetch([ + () => ({ + ok: true, + status: 200, + body: { + channelId: "msteams", + connectionName: "GraphConnection", + token: "delegated-graph-token", + expiration: "2030-01-01T00:00:00Z", + }, + }), + ]); + const { sso, tokenStore } = createSsoDeps({ fetchImpl }); + const deps = createDepsWithoutSso({ cfg: scenario.cfg, sso }); + const { handler } = createActivityHandler(); + const registered = registerMSTeamsHandlers(handler, deps) as MSTeamsActivityHandler & { + run: NonNullable<MSTeamsActivityHandler["run"]>; + }; + + const ctx = createSigninInvokeContext({ + name: invoke.name, + value: invoke.value, + ...scenario.context, + }); + + await registered.run(ctx); + + expect(ctx.sendActivity).toHaveBeenCalledWith( + expect.objectContaining({ + type: "invokeResponse", + value: expect.objectContaining({ status: 200 }), + }), + ); + expect(calls).toHaveLength(0); + const stored = await tokenStore.get({ + connectionName: "GraphConnection", + userId: scenario.context.userAadId ?? "aad-user-guid", + }); + expect(stored).toBeNull(); + expect(deps.log.debug).toHaveBeenCalledWith( + scenario.expectedDropLog, + expect.objectContaining({ name: invoke.name }), + ); + }); + } + } + it("invokes the token exchange handler when sso is configured", async () => { const { fetchImpl } = createFakeFetch([ () => ({
extensions/msteams/src/monitor-handler.ts+54 −7 modified@@ -56,10 +56,17 @@ function serializeAdaptiveCardActionValue(value: unknown): string | null { } } -async function isFeedbackInvokeAuthorized( - context: MSTeamsTurnContext, - deps: MSTeamsMessageHandlerDeps, -): Promise<boolean> { +async function isInvokeAuthorized(params: { + context: MSTeamsTurnContext; + deps: MSTeamsMessageHandlerDeps; + deniedLogs: { + dm: string; + channel: string; + group: string; + }; + includeInvokeName?: boolean; +}): Promise<boolean> { + const { context, deps, deniedLogs, includeInvokeName = false } = params; const resolved = await resolveMSTeamsSenderAccess({ cfg: deps.cfg, activity: context.activity, @@ -69,10 +76,13 @@ async function isFeedbackInvokeAuthorized( return true; } + const maybeInvokeName = includeInvokeName ? { name: context.activity.name } : undefined; + if (isDirectMessage && resolved.access.decision !== "allow") { - deps.log.debug?.("dropping feedback invoke (dm sender not allowlisted)", { + deps.log.debug?.(deniedLogs.dm, { sender: senderId, conversationId, + ...maybeInvokeName, }); return false; } @@ -82,25 +92,58 @@ async function isFeedbackInvokeAuthorized( resolved.channelGate.allowlistConfigured && !resolved.channelGate.allowed ) { - deps.log.debug?.("dropping feedback invoke (not in team/channel allowlist)", { + deps.log.debug?.(deniedLogs.channel, { conversationId, teamKey: resolved.channelGate.teamKey ?? "none", channelKey: resolved.channelGate.channelKey ?? "none", + ...maybeInvokeName, }); return false; } if (!isDirectMessage && !resolved.senderGroupAccess.allowed) { - deps.log.debug?.("dropping feedback invoke (group sender not allowlisted)", { + deps.log.debug?.(deniedLogs.group, { sender: senderId, conversationId, + ...maybeInvokeName, }); return false; } return true; } +async function isFeedbackInvokeAuthorized( + context: MSTeamsTurnContext, + deps: MSTeamsMessageHandlerDeps, +): Promise<boolean> { + return isInvokeAuthorized({ + context, + deps, + deniedLogs: { + dm: "dropping feedback invoke (dm sender not allowlisted)", + channel: "dropping feedback invoke (not in team/channel allowlist)", + group: "dropping feedback invoke (group sender not allowlisted)", + }, + }); +} + +async function isSigninInvokeAuthorized( + context: MSTeamsTurnContext, + deps: MSTeamsMessageHandlerDeps, +): Promise<boolean> { + return isInvokeAuthorized({ + context, + deps, + deniedLogs: { + dm: "dropping signin invoke (dm sender not allowlisted)", + channel: "dropping signin invoke (not in team/channel allowlist)", + group: "dropping signin invoke (group sender not allowlisted)", + }, + includeInvokeName: true, + }); +} + /** * Handle fileConsent/invoke activities for large file uploads. */ @@ -481,6 +524,10 @@ export function registerMSTeamsHandlers<T extends MSTeamsActivityHandler>( // the Teams card UI to report "Something went wrong". await ctx.sendActivity({ type: "invokeResponse", value: { status: 200, body: {} } }); + if (!(await isSigninInvokeAuthorized(ctx, deps))) { + return; + } + if (!deps.sso) { deps.log.debug?.("signin invoke received but msteams.sso is not configured", { name: ctx.activity.name,
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/openclaw/openclaw/commit/80b1fa17bfc3f6a668492f0326ea52f48bb89776nvdPatchWEB
- github.com/advisories/GHSA-gc9r-867r-j85fghsaADVISORY
- github.com/openclaw/openclaw/security/advisories/GHSA-gc9r-867r-j85fnvdVendor AdvisoryWEB
- www.vulncheck.com/advisories/openclaw-missing-sender-authorization-in-microsoft-teams-sso-invoke-handlernvdThird Party Advisory
- github.com/openclaw/openclaw/pull/66033ghsaWEB
News mentions
8- 30 ClawHub skills secretly turn AI agents into a crypto swarmThe Register Security · Apr 29, 2026
- 27th April – Threat Intelligence ReportCheck Point Research · Apr 27, 2026
- Agents that remember: introducing Agent MemoryCloudflare Blog · Apr 17, 2026
- The Increasing Role of AI in Vulnerability ResearchWordfence Blog · Apr 10, 2026
- 16th March – Threat Intelligence ReportCheck Point Research · Mar 16, 2026
- How AI Assistants are Moving the Security GoalpostsKrebs on Security · Mar 8, 2026
- Risky Business #827 -- Iranian cyber threat actors are down but not outRisky Business · Mar 4, 2026
- Risky Business #826 -- A week of AI mishaps and skulduggeryRisky Business · Feb 25, 2026