Skip to content

Commit ebe27a9

Browse files
author
lizeyu-xydt
committed
fix(msteams): bound response read in callUserTokenService
Replace unbounded response.json() with readResponseWithLimit to cap the Bot Framework User Token service response at 16 MB, preventing OOM on oversized responses. Error path was already bounded via readMSTeamsHttpErrorDetail; this aligns the success path with the same defensive pattern used across 50+ provider integrations. Adds 3 new test cases: readResponseWithLimit normal read, overflow rejection, and non-JSON response error handling.
1 parent d9aedc3 commit ebe27a9

2 files changed

Lines changed: 48 additions & 1 deletion

File tree

extensions/msteams/src/monitor-handler.sso.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Msteams tests cover monitor handler.sso plugin behavior.
22
import { describe, expect, it, vi } from "vitest";
3+
import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
34
import {
45
makeMSTeamsSsoTokenStoreKey,
56
type MSTeamsSsoStoredToken,
@@ -66,7 +67,51 @@ function createFakeFetch(handlers: Array<(url: string, init?: unknown) => unknow
6667
return { fetchImpl, calls };
6768
}
6869

70+
describe("readResponseWithLimit integration", () => {
71+
it("reads a small JSON response successfully", async () => {
72+
const body = JSON.stringify({ token: "test", connectionName: "test" });
73+
const response = new Response(body, {
74+
headers: { "content-type": "application/json" },
75+
});
76+
const buffer = await readResponseWithLimit(response, 16 * 1024 * 1024);
77+
const parsed = JSON.parse(buffer.toString());
78+
expect(parsed).toEqual({ token: "test", connectionName: "test" });
79+
});
80+
81+
it("rejects an oversized response", async () => {
82+
const oversized = "x".repeat(17 * 1024 * 1024);
83+
const response = new Response(oversized, {
84+
headers: { "content-type": "application/json" },
85+
});
86+
await expect(
87+
readResponseWithLimit(response, 16 * 1024 * 1024),
88+
).rejects.toThrow();
89+
});
90+
});
91+
6992
describe("handleSigninTokenExchangeInvoke", () => {
93+
it("returns an error for non-JSON response from the User Token service", async () => {
94+
const { fetchImpl } = createFakeFetch([
95+
() => ({
96+
ok: true,
97+
status: 200,
98+
body: "<html>not json</html>",
99+
}),
100+
]);
101+
const { sso } = createSsoDeps({ fetchImpl });
102+
103+
const result = await handleSigninTokenExchangeInvoke({
104+
value: { id: "flow-1", connectionName: "GraphConnection", token: "exchangeable-token" },
105+
user: { userId: "aad-user-guid", channelId: "msteams" },
106+
deps: sso,
107+
});
108+
109+
expect(result.ok).toBe(false);
110+
if (!result.ok) {
111+
expect(result.code).toBe("unexpected_response");
112+
expect(result.message).toContain("invalid JSON");
113+
}
114+
});
70115
it("exchanges the Teams token and persists the result", async () => {
71116
const { fetchImpl, calls } = createFakeFetch([
72117
() => ({

extensions/msteams/src/sso.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import type { MSTeamsAccessTokenProvider } from "./attachments/types.js";
2828
import { readMSTeamsHttpErrorDetail } from "./http-error.js";
29+
import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
2930
import type { MSTeamsSsoTokenStore } from "./sso-token-store.js";
3031
import { buildUserAgent } from "./user-agent.js";
3132

@@ -130,7 +131,8 @@ async function callUserTokenService(
130131
}
131132
let parsed: unknown;
132133
try {
133-
parsed = await response.json();
134+
const buffer = await readResponseWithLimit(response, 16 * 1024 * 1024);
135+
parsed = JSON.parse(buffer.toString());
134136
} catch {
135137
return { error: "invalid JSON from User Token service", status: response.status };
136138
}

0 commit comments

Comments
 (0)