From db6114db0a6db81287df8cacc083a0f15fe81e50 Mon Sep 17 00:00:00 2001 From: luvs01 Date: Sun, 16 Aug 2026 19:43:38 +0900 Subject: [PATCH] fix: enforce pinned HTTP header deadline --- src/lib/pinned-http.ts | 6 ++--- tests/pinned-http.test.ts | 48 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 tests/pinned-http.test.ts diff --git a/src/lib/pinned-http.ts b/src/lib/pinned-http.ts index 97e8d91a9c..3037311417 100644 --- a/src/lib/pinned-http.ts +++ b/src/lib/pinned-http.ts @@ -199,7 +199,7 @@ function pinnedHttpRequest( req.on("socket", (socket) => { const connectedEvent = parsed.protocol === "https:" ? "secureConnect" : "connect"; if (!socket.connecting) { - if (!usesLegacyIdleTimeout) startFirstByteTimer(); + startFirstByteTimer(); return; } if (connectTimeoutMs !== undefined) { @@ -210,7 +210,7 @@ function pinnedHttpRequest( } socket.once(connectedEvent, () => { clearConnectTimer(); - if (!usesLegacyIdleTimeout) startFirstByteTimer(); + startFirstByteTimer(); }); socket.once("error", () => { clearConnectTimer(); @@ -262,4 +262,4 @@ export function pinnedHttpPost( options?: PinnedHttpRequestOptions, ): Promise { return pinnedHttpRequest(url, pinned, "POST", body, signal, options); -} \ No newline at end of file +} diff --git a/tests/pinned-http.test.ts b/tests/pinned-http.test.ts new file mode 100644 index 0000000000..f47a0a3f67 --- /dev/null +++ b/tests/pinned-http.test.ts @@ -0,0 +1,48 @@ +import { createServer, type Server, type Socket } from "node:net"; +import { afterEach, describe, expect, test } from "bun:test"; +import { pinnedHttpGet } from "../src/lib/pinned-http"; + +let server: Server | undefined; +const sockets = new Set(); + +afterEach(async () => { + for (const socket of sockets) socket.destroy(); + sockets.clear(); + if (server) { + const closing = server; + server = undefined; + await new Promise((resolve) => closing.close(() => resolve())); + } +}); + +describe("pinned HTTP timeouts", () => { + test("legacy idle timeout remains an absolute response-header deadline", async () => { + server = createServer((socket) => { + sockets.add(socket); + socket.on("close", () => sockets.delete(socket)); + socket.write("HTTP/1.1 200 OK\r\nX-Slow: "); + const drip = setInterval(() => socket.write("x"), 10); + socket.on("close", () => clearInterval(drip)); + }); + const port = await new Promise((resolve, reject) => { + server!.once("error", reject); + server!.listen(0, "127.0.0.1", () => { + const address = server!.address(); + if (!address || typeof address === "string") { + reject(new Error("test server did not expose a TCP port")); + return; + } + resolve(address.port); + }); + }); + + const request = pinnedHttpGet( + `http://slow-header.invalid:${port}/`, + { address: "127.0.0.1", family: 4 }, + undefined, + { idleTimeoutMs: 50 }, + ); + + await expect(request).rejects.toThrow(/timed out/); + }); +});