diff --git a/extensions/signal/src/install-signal-cli.test.ts b/extensions/signal/src/install-signal-cli.test.ts index 74c027030aa7..d356a0bf013f 100644 --- a/extensions/signal/src/install-signal-cli.test.ts +++ b/extensions/signal/src/install-signal-cli.test.ts @@ -277,6 +277,38 @@ describe("installSignalCliFromRelease", () => { expect(fetchResult.release).toHaveBeenCalledTimes(1); }); + it("bounds oversized GitHub release metadata and cancels the stream", async () => { + const chunkSize = 1024 * 1024; + const chunkCount = 20; // 20 MiB — over the 16 MiB cap + let readCount = 0; + let canceled = false; + const oversized = new Response( + new ReadableStream({ + pull(controller) { + if (readCount >= chunkCount) { + controller.close(); + return; + } + readCount += 1; + controller.enqueue(new Uint8Array(chunkSize)); + }, + cancel() { + canceled = true; + }, + }), + { status: 200, headers: { "Content-Type": "application/json" } }, + ); + const releaseMock = vi.fn().mockResolvedValue(undefined); + fetchWithSsrFGuardMock.mockResolvedValue({ response: oversized, release: releaseMock }); + + const result = await installSignalCliFromRelease({ log: vi.fn() } as unknown as RuntimeEnv); + + expect(result).toEqual({ ok: false, error: "Failed to parse signal-cli release info." }); + expect(canceled).toBe(true); + expect(readCount).toBeLessThan(chunkCount); + expect(releaseMock).toHaveBeenCalledTimes(1); + }); + it("bounds the release metadata request with an explicit timeout", async () => { const fetchResult = okDownloadResponse(JSON.stringify({ tag_name: "v0.14.3", assets: [] }), { headers: { "content-type": "application/json" }, diff --git a/extensions/signal/src/install-signal-cli.ts b/extensions/signal/src/install-signal-cli.ts index 3b79e653bbd0..ae0f7b471d65 100644 --- a/extensions/signal/src/install-signal-cli.ts +++ b/extensions/signal/src/install-signal-cli.ts @@ -5,6 +5,7 @@ import path from "node:path"; import { Readable, Transform } from "node:stream"; import { pipeline } from "node:stream/promises"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; +import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http"; import { runPluginCommandWithTimeout } from "openclaw/plugin-sdk/run-command"; import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; import { CONFIG_DIR, extractArchive, resolveBrewExecutable } from "openclaw/plugin-sdk/setup-tools"; @@ -303,7 +304,7 @@ export async function installSignalCliFromRelease( }; } try { - payload = (await response.json()) as ReleaseResponse; + payload = await readProviderJsonResponse(response, "signal.release-info"); } catch { return { ok: false,