[Bug] safeFetch buffers entire response before enforcing maxSize — memory DoS
#408 aperta il 15 apr 2026
Metriche repository
- Star
- (519 star)
- Metriche merge PR
- (Metriche PR in attesa)
Descrizione
Problem
safeFetch enforces maxSize in two places, but both checks happen after the full response body has already been materialized in memory:
- The
content-lengthheader check is advisory — a malicious server can omit or lie about it. await res.arrayBuffer()buffers the entire response body into a singleArrayBufferbefore the byte-length check runs. By the time we checkab.byteLength > maxSize, the memory has already been allocated.
A malicious image URL pointing to a server that omits content-length and streams an endlessly large body causes the main process to allocate gigabytes of memory before the check fires, leading to an OOM crash of the entire Electron app.
Location
File: packages/desktop/src/main/net/safe-fetch.ts:21-27
const res = await net.fetch(url, { signal: controller.signal });
if (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);
const cl = Number(res.headers.get('content-length') ?? '0');
if (cl > maxSize) throw new Error('response too large');
const ab = await res.arrayBuffer(); // ← entire body buffered here
if (ab.byteLength > maxSize) throw new Error('response too large');
return Buffer.from(ab);
Fix Approach
Switch to a streaming reader that enforces maxSize incrementally and aborts as soon as the limit is exceeded:
const reader = res.body?.getReader();
if (!reader) throw new Error('no body');
const chunks: Uint8Array[] = [];
let total = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
total += value.byteLength;
if (total > maxSize) {
controller.abort();
throw new Error('response too large');
}
chunks.push(value);
}
return Buffer.concat(chunks.map((c) => Buffer.from(c)));
Keep the initial content-length pre-check as a cheap short-circuit for well-behaved servers.
Verification
- Run
pnpm check— must pass. - Unit test: mock
net.fetchto return a body that streams more thanmaxSizebytes with nocontent-lengthheader; assertsafeFetchthrows before allocating more thanmaxSizebytes. - Manual: point
safeFetchat a test server that streams/dev/urandom; confirm memory usage stays bounded.
Context
- WG: Observability & DX (security — memory DoS)
- Priority: Medium
- Estimated effort: 1 hour