Skip to content

Commit aea208f

Browse files
committed
fix(discord): bound pluralkit error bodies
1 parent 4d37f42 commit aea208f

2 files changed

Lines changed: 53 additions & 1 deletion

File tree

extensions/discord/src/pluralkit.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,28 @@ const buildResponse = (params: { status: number; body?: unknown }): MockResponse
2020
};
2121
};
2222

23+
function cancelTrackedResponse(
24+
text: string,
25+
init: ResponseInit,
26+
): {
27+
response: Response;
28+
wasCanceled: () => boolean;
29+
} {
30+
let canceled = false;
31+
const stream = new ReadableStream<Uint8Array>({
32+
start(controller) {
33+
controller.enqueue(new TextEncoder().encode(text));
34+
},
35+
cancel() {
36+
canceled = true;
37+
},
38+
});
39+
return {
40+
response: new Response(stream, init),
41+
wasCanceled: () => canceled,
42+
};
43+
}
44+
2345
describe("fetchPluralKitMessageInfo", () => {
2446
it("returns null when disabled", async () => {
2547
const fetcher = vi.fn();
@@ -65,4 +87,30 @@ describe("fetchPluralKitMessageInfo", () => {
6587
expect(result?.member?.id).toBe("mem_1");
6688
expect(receivedHeaders?.Authorization).toBe("pk_test");
6789
});
90+
91+
it("bounds PluralKit API error bodies without using response.text()", async () => {
92+
const tracked = cancelTrackedResponse(`${"plural failure ".repeat(1024)}tail`, {
93+
status: 500,
94+
headers: { "content-type": "text/plain" },
95+
});
96+
const textSpy = vi.spyOn(tracked.response, "text").mockRejectedValue(new Error("unbounded"));
97+
const fetcher = vi.fn(async () => tracked.response);
98+
99+
let caught: Error | undefined;
100+
try {
101+
await fetchPluralKitMessageInfo({
102+
messageId: "boom",
103+
config: { enabled: true },
104+
fetcher: fetcher as unknown as typeof fetch,
105+
});
106+
} catch (error) {
107+
caught = error as Error;
108+
}
109+
110+
expect(caught?.message).toContain("PluralKit API failed (500): plural failure");
111+
expect(caught?.message).not.toContain("tail");
112+
expect(caught?.message.length).toBeLessThan(8_400);
113+
expect(tracked.wasCanceled()).toBe(true);
114+
expect(textSpy).not.toHaveBeenCalled();
115+
});
68116
});

extensions/discord/src/pluralkit.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Discord plugin module implements pluralkit behavior.
22
import { resolveFetch } from "openclaw/plugin-sdk/fetch-runtime";
3+
import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
34

45
const PLURALKIT_API_BASE = "https://api.pluralkit.me/v2";
6+
const PLURALKIT_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
57

68
export type DiscordPluralKitConfig = {
79
enabled?: boolean;
@@ -51,7 +53,9 @@ export async function fetchPluralKitMessageInfo(params: {
5153
return null;
5254
}
5355
if (!res.ok) {
54-
const text = await res.text().catch(() => "");
56+
const text = await readResponseTextLimited(res, PLURALKIT_ERROR_BODY_LIMIT_BYTES).catch(
57+
() => "",
58+
);
5559
const detail = text.trim() ? `: ${text.trim()}` : "";
5660
throw new Error(`PluralKit API failed (${res.status})${detail}`);
5761
}

0 commit comments

Comments
 (0)