fix(signal): release connections after failed CLI downloads (#109442)

* fix(signal): cancel failed CLI download responses

* fix(signal): close all failed installer responses

Co-authored-by: wahaha1223 <0668001153@xydigit.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
wahaha1223
2026-07-17 13:01:53 +08:00
committed by GitHub
parent e873a7f955
commit 0e96e85b19
2 changed files with 37 additions and 0 deletions
@@ -264,6 +264,20 @@ describe("pickAsset", () => {
});
describe("downloadToFile", () => {
it("cancels non-success response bodies before rejecting", async () => {
const response = new Response("service unavailable", { status: 503 });
const cancel = vi.spyOn(response.body!, "cancel").mockRejectedValueOnce(new Error("closed"));
fetchWithSsrFGuardMock.mockResolvedValue({ response, release: vi.fn() });
await withTempFile(async (filePath) => {
await expect(downloadToFile("https://example.com/signal-cli.tgz", filePath)).rejects.toThrow(
"HTTP 503",
);
});
expect(cancel).toHaveBeenCalledOnce();
});
it("downloads through the SSRF guard with an explicit timeout", async () => {
const fetchResult = okDownloadResponse("archive");
fetchWithSsrFGuardMock.mockResolvedValue(fetchResult);
@@ -344,6 +358,23 @@ describe("downloadToFile", () => {
});
describe("installSignalCliFromRelease", () => {
it("cancels non-success release metadata before returning the fetch error", async () => {
const response = new Response("service unavailable", { status: 503 });
const cancel = vi.spyOn(response.body!, "cancel").mockRejectedValueOnce(new Error("closed"));
const release = vi.fn().mockResolvedValue(undefined);
fetchWithSsrFGuardMock.mockResolvedValue({ response, release });
await expect(
installSignalCliFromRelease({ log: vi.fn() } as unknown as RuntimeEnv),
).resolves.toEqual({
ok: false,
error: "Failed to fetch release info (503)",
});
expect(cancel).toHaveBeenCalledOnce();
expect(release).toHaveBeenCalledOnce();
});
it("returns an installer error when GitHub release metadata is malformed JSON", async () => {
const fetchResult = okDownloadResponse("{not json", {
headers: { "content-type": "application/json" },
@@ -83,6 +83,10 @@ function chunkByteLength(chunk: unknown): number {
return Buffer.byteLength(String(chunk));
}
async function cancelUnusedResponseBody(response: Response): Promise<void> {
await response.body?.cancel().catch(() => undefined);
}
/**
* Pick a native release asset from the official GitHub releases.
*
@@ -149,6 +153,7 @@ export async function downloadToFile(
});
try {
if (!response.ok || !response.body) {
await cancelUnusedResponseBody(response);
throw new Error(`HTTP ${response.status || "?"} downloading file`);
}
@@ -313,6 +318,7 @@ export async function installSignalCliFromRelease(
let payload: ReleaseResponse;
try {
if (!response.ok) {
await cancelUnusedResponseBody(response);
return {
ok: false,
error: `Failed to fetch release info (${response.status})`,