mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(google-meet): bound Drive document export reads to prevent OOM (#97620)
* fix(google-meet): bound Drive document export reads to prevent OOM
* test(google-meet): return full guarded fetch result
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
(cherry picked from commit 2001b15f5b)
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// Google Meet tests cover bounded Drive document export response reads.
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { exportGoogleDriveDocumentText } from "./drive.js";
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
|
||||
fetchWithSsrFGuard: vi.fn(),
|
||||
}));
|
||||
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
|
||||
const mockFetch = vi.mocked(fetchWithSsrFGuard);
|
||||
|
||||
function makeStreamResponse(sizeBytes: number, status = 200): Response {
|
||||
const chunk = new Uint8Array(Math.min(sizeBytes, 65536)).fill(0x78); // 'x'
|
||||
let sent = 0;
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
pull(controller) {
|
||||
if (sent >= sizeBytes) {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
const remaining = sizeBytes - sent;
|
||||
const toSend = Math.min(chunk.length, remaining);
|
||||
controller.enqueue(chunk.subarray(0, toSend));
|
||||
sent += toSend;
|
||||
},
|
||||
});
|
||||
return new Response(stream, {
|
||||
status,
|
||||
headers: { "Content-Type": "text/plain" },
|
||||
});
|
||||
}
|
||||
|
||||
describe("exportGoogleDriveDocumentText bound", () => {
|
||||
it("returns document text when response is within the 16 MiB cap", async () => {
|
||||
const UNDER_CAP = 256;
|
||||
const response = makeStreamResponse(UNDER_CAP);
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
response,
|
||||
finalUrl: "https://www.googleapis.com/drive/v3/files/doc-id/export?mimeType=text%2Fplain",
|
||||
release: vi.fn(async () => undefined),
|
||||
});
|
||||
|
||||
const result = await exportGoogleDriveDocumentText({
|
||||
accessToken: "tok",
|
||||
documentId: "doc-id",
|
||||
});
|
||||
|
||||
expect(typeof result).toBe("string");
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("rejects with a size error when response exceeds 16 MiB cap (fail-closed)", async () => {
|
||||
const OVER_CAP = 17 * 1024 * 1024; // 17 MiB
|
||||
const response = makeStreamResponse(OVER_CAP);
|
||||
const release = vi.fn(async () => undefined);
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
response,
|
||||
finalUrl: "https://www.googleapis.com/drive/v3/files/doc-id/export?mimeType=text%2Fplain",
|
||||
release,
|
||||
});
|
||||
|
||||
await expect(
|
||||
exportGoogleDriveDocumentText({ accessToken: "tok", documentId: "doc-id" }),
|
||||
).rejects.toThrow(/exceeds/i);
|
||||
|
||||
expect(release).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("negative-control: bare response.text() buffers the full oversized body (no protection)", async () => {
|
||||
const OVER_CAP = 17 * 1024 * 1024; // 17 MiB
|
||||
const response = makeStreamResponse(OVER_CAP);
|
||||
// Calling response.text() directly buffers everything without throwing.
|
||||
const text = await response.text();
|
||||
expect(text.length).toBeGreaterThan(16 * 1024 * 1024);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
// Google Meet plugin module implements drive behavior.
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { readProviderTextResponse } from "openclaw/plugin-sdk/provider-http";
|
||||
import { googleApiError } from "./google-api-errors.js";
|
||||
|
||||
const GOOGLE_DRIVE_API_BASE_URL = "https://www.googleapis.com/drive/v3";
|
||||
@@ -64,7 +65,7 @@ export async function exportGoogleDriveDocumentText(params: {
|
||||
scopes: [GOOGLE_DRIVE_MEET_SCOPE],
|
||||
});
|
||||
}
|
||||
return await response.text();
|
||||
return await readProviderTextResponse(response, "Google Drive files.export");
|
||||
} finally {
|
||||
await release();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user