diff --git a/CHANGELOG.md b/CHANGELOG.md index f1100b53fa1..28c0e17c7c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,7 @@ Docs: https://docs.openclaw.ai - Brave Search: report malformed web and LLM-context API JSON with provider-owned errors instead of leaking raw parser failures. - xAI tools: report malformed web search, X search, and code execution JSON with provider-owned errors instead of leaking raw parser failures. - Nextcloud Talk: report malformed room-info and bot-admin JSON with channel-owned errors instead of leaking raw parser failures. +- Microsoft Teams: report malformed Graph and delegated OAuth JSON with channel-owned errors instead of leaking raw parser failures. - Twilio voice-call: report malformed successful API JSON responses with provider-owned errors instead of leaking raw parser failures. - Voice-call provider APIs: report malformed successful guarded JSON responses with provider-prefixed errors instead of leaking raw parser failures. - Realtime transcription: report malformed provider websocket JSON frames with owned parser errors instead of leaking raw `SyntaxError` objects. diff --git a/extensions/msteams/src/graph.test.ts b/extensions/msteams/src/graph.test.ts index 613938b304e..affd368db1a 100644 --- a/extensions/msteams/src/graph.test.ts +++ b/extensions/msteams/src/graph.test.ts @@ -193,6 +193,19 @@ describe("msteams graph helpers", () => { }), "Graph /teams/team-1/channels failed (403): forbidden", ); + + mockTextFetchResponse("{ nope", { + status: 200, + headers: { "content-type": "application/json" }, + }); + + await expectRejectsToThrow( + fetchGraphJson({ + token: graphToken, + path: "/teams/team-1/channels", + }), + "Graph /teams/team-1/channels failed: malformed JSON response", + ); }); it("posts Graph JSON to v1 and beta roots and treats empty mutation responses as undefined", async () => { diff --git a/extensions/msteams/src/graph.ts b/extensions/msteams/src/graph.ts index 2e9f00303aa..48de11c2c66 100644 --- a/extensions/msteams/src/graph.ts +++ b/extensions/msteams/src/graph.ts @@ -1,3 +1,4 @@ +import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http"; import { fetchWithSsrFGuard, type MSTeamsConfig } from "../runtime-api.js"; import { GRAPH_ROOT } from "./attachments/shared.js"; @@ -118,7 +119,7 @@ export async function fetchGraphAbsoluteUrl(params: { `Graph ${params.url} failed (${response.status}): ${text || "unknown error"}`, ); } - return (await response.json()) as T; + return await readProviderJsonResponse(response, `Graph ${params.url} failed`); } finally { await release(); } diff --git a/extensions/msteams/src/oauth.test.ts b/extensions/msteams/src/oauth.test.ts index 0af342b937e..ea33b5dc391 100644 --- a/extensions/msteams/src/oauth.test.ts +++ b/extensions/msteams/src/oauth.test.ts @@ -228,6 +228,25 @@ describe("exchangeMSTeamsCodeForTokens", () => { }), ).rejects.toThrow(/MSTeams token exchange failed \(400\)/); }); + + it("reports malformed token exchange JSON with a stable OAuth error", async () => { + fetchSpy.mockResolvedValueOnce( + new Response("{ nope", { + status: 200, + headers: { "Content-Type": "application/json" }, + }), + ); + + await expect( + exchangeMSTeamsCodeForTokens({ + tenantId: "t", + clientId: "c", + clientSecret: "s", // pragma: allowlist secret + code: "bad-json", + verifier: "v", + }), + ).rejects.toThrow("MSTeams token exchange failed: malformed JSON response"); + }); }); describe("refreshMSTeamsDelegatedTokens", () => { @@ -310,4 +329,22 @@ describe("refreshMSTeamsDelegatedTokens", () => { }), ).rejects.toThrow(/MSTeams token refresh failed \(401\)/); }); + + it("reports malformed token refresh JSON with a stable OAuth error", async () => { + fetchSpy.mockResolvedValueOnce( + new Response("{ nope", { + status: 200, + headers: { "Content-Type": "application/json" }, + }), + ); + + await expect( + refreshMSTeamsDelegatedTokens({ + tenantId: "t", + clientId: "c", + clientSecret: "s", // pragma: allowlist secret + refreshToken: "bad-json", + }), + ).rejects.toThrow("MSTeams token refresh failed: malformed JSON response"); + }); }); diff --git a/extensions/msteams/src/oauth.token.ts b/extensions/msteams/src/oauth.token.ts index 2ab0bd3ab81..ebfa4f4088d 100644 --- a/extensions/msteams/src/oauth.token.ts +++ b/extensions/msteams/src/oauth.token.ts @@ -1,3 +1,4 @@ +import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http"; import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime"; import { MSTEAMS_DEFAULT_DELEGATED_SCOPES, @@ -65,7 +66,10 @@ async function fetchMSTeamsTokens(params: { const errorText = await response.text(); throw new Error(`MSTeams ${params.failureLabel} failed (${response.status}): ${errorText}`); } - return (await response.json()) as MSTeamsTokenResponse; + return await readProviderJsonResponse( + response, + `MSTeams ${params.failureLabel} failed`, + ); } finally { await release(); }