Skip to content

Commit d4669d8

Browse files
fix(chutes-oauth-plugin): add inline bounded-read regression test
Co-Authored-By: Claude <[email protected]>
1 parent aec26cd commit d4669d8

1 file changed

Lines changed: 64 additions & 1 deletion

File tree

extensions/chutes/oauth.test.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
import { describe, expect, it, vi } from "vitest";
33
import { loginChutes } from "./oauth.js";
44

5-
function boundedErrorResponse(body: string, status = 500): {
5+
function boundedErrorResponse(
6+
body: string,
7+
status = 500,
8+
): {
69
response: Response;
710
cancel: ReturnType<typeof vi.fn>;
811
releaseLock: ReturnType<typeof vi.fn>;
@@ -112,4 +115,64 @@ describe("chutes plugin OAuth", () => {
112115
expect(errorResponse.cancel).toHaveBeenCalledTimes(1);
113116
expect(errorResponse.releaseLock).toHaveBeenCalledTimes(1);
114117
});
118+
119+
it("cancels oversized token exchange JSON body via the 16 MiB provider cap", async () => {
120+
const ONE_MIB = 1024 * 1024;
121+
const TOTAL_CHUNKS = 32;
122+
const chunk = new Uint8Array(ONE_MIB);
123+
124+
let bytesPulled = 0;
125+
let canceled = false;
126+
const oversizedTokenJson = new Response(
127+
new ReadableStream<Uint8Array>({
128+
pull(controller) {
129+
if (bytesPulled >= TOTAL_CHUNKS * ONE_MIB) {
130+
controller.close();
131+
return;
132+
}
133+
bytesPulled += chunk.length;
134+
controller.enqueue(chunk);
135+
},
136+
cancel() {
137+
canceled = true;
138+
},
139+
}),
140+
{ status: 200, headers: { "Content-Type": "application/json" } },
141+
);
142+
143+
const fetchFn = vi.fn(async (input: RequestInfo | URL) => {
144+
const url =
145+
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
146+
if (url === "https://api.chutes.ai/idp/userinfo") {
147+
return new Response(JSON.stringify({ login: "test", name: "Test" }), {
148+
status: 200,
149+
headers: { "Content-Type": "application/json" },
150+
});
151+
}
152+
if (url === "https://api.chutes.ai/idp/token") {
153+
return oversizedTokenJson;
154+
}
155+
return new Response("not found", { status: 404 });
156+
});
157+
158+
await expect(
159+
loginChutes({
160+
app: {
161+
clientId: "cid_test",
162+
redirectUri: "http://127.0.0.1:1456/oauth-callback",
163+
scopes: ["openid"],
164+
},
165+
manual: true,
166+
createState: () => "state_test",
167+
onAuth: vi.fn(async () => {}),
168+
onPrompt: vi.fn(
169+
async () => "http://127.0.0.1:1456/oauth-callback?code=code_test&state=state_test",
170+
),
171+
fetchFn,
172+
}),
173+
).rejects.toThrow(/Chutes token exchange: JSON response exceeds 16777216 bytes/);
174+
175+
expect(canceled).toBe(true);
176+
expect(bytesPulled).toBeLessThan(TOTAL_CHUNKS * ONE_MIB);
177+
});
115178
});

0 commit comments

Comments
 (0)