Skip to content

Commit 856e0df

Browse files
committed
Reject oversized proxy responses up front via Content-Length
The streaming cap in readBoundedBody catches oversized bodies, but only after we've started reading. When the upstream is already telling us the body is 50 MiB before we read a byte, there's no reason to spend that bandwidth: we'll bail anyway once the cap trips. Check Content-Length right after the Content-Type allowlist and short-circuit to 404 when it exceeds MAX_BYTES. A missing or malformed header still falls through to the streaming cap, which keeps enforcing the limit byte by byte. New test confirms a 64 MiB advertised body is dropped before the body read happens. #483 (comment) Assisted-by: Claude Code:claude-opus-4-7
1 parent 739d3ed commit 856e0df

2 files changed

Lines changed: 38 additions & 0 deletions

File tree

src/proxy.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,30 @@ describe.sequential("proxy route", () => {
192192
expect(response.status).toBe(404);
193193
});
194194

195+
it("rejects oversized bodies advertised via Content-Length up front", async () => {
196+
expect.assertions(1);
197+
198+
// 64 MiB declared, which is well past the 32 MiB cap. We never read
199+
// the body in this path, so the actual payload size doesn't matter
200+
// for the assertion.
201+
fetchMock.mockResolvedValue(
202+
new Response(new Uint8Array([0, 0, 0, 0]).buffer as ArrayBuffer, {
203+
status: 200,
204+
headers: {
205+
"Content-Type": "image/png",
206+
"Content-Length": String(64 * 1024 * 1024),
207+
},
208+
}),
209+
);
210+
211+
const app = createProxyApp("proxy");
212+
const { sig, b64url } = signProxyUrl("https://remote.example/huge.png");
213+
214+
const response = await app.request(`/${sig}/${b64url}`);
215+
216+
expect(response.status).toBe(404);
217+
});
218+
195219
it("writes the cache on first hit and reuses it on the second in cache mode", async () => {
196220
expect.assertions(4);
197221

src/proxy.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,20 @@ export function createProxyApp(mode: MediaProxyMode = MEDIA_PROXY): Hono {
264264
return c.notFound();
265265
}
266266

267+
// When the upstream tells us the body length up front we can
268+
// short-circuit oversized responses without spending bandwidth on
269+
// the read. A missing or malformed header just falls through to
270+
// the streaming cap inside readBoundedBody, which still enforces
271+
// the limit.
272+
const contentLengthHeader = upstream.headers.get("Content-Length");
273+
if (contentLengthHeader != null) {
274+
const declaredLength = Number.parseInt(contentLengthHeader, 10);
275+
if (Number.isFinite(declaredLength) && declaredLength > MAX_BYTES) {
276+
await discardBody(upstream);
277+
return c.notFound();
278+
}
279+
}
280+
267281
const body = await readBoundedBody(upstream, MAX_BYTES);
268282
if (body == null) return c.notFound();
269283

0 commit comments

Comments
 (0)