Skip to content

fix(net): stream safeFetch body with incremental maxSize enforcement#487

Open
HiddenPuppy wants to merge 2 commits intomainfrom
fix/408-safefetch-streaming-maxsize
Open

fix(net): stream safeFetch body with incremental maxSize enforcement#487
HiddenPuppy wants to merge 2 commits intomainfrom
fix/408-safefetch-streaming-maxsize

Conversation

@HiddenPuppy
Copy link
Copy Markdown
Collaborator

Problem

safeFetch enforces maxSize after await res.arrayBuffer() has already buffered the entire response body into memory. A malicious server can omit or lie about content-length and stream an endless body, causing the main process to consume gigabytes of memory and OOM crash the entire Electron app.

Fix

Replace res.arrayBuffer() with a streaming reader (res.body.getReader()) that accumulates chunks incrementally and aborts the fetch as soon as the accumulated byte count exceeds maxSize. The content-length header pre-check is kept as a cheap short-circuit for well-behaved servers.

Verification

  • pnpm check passes (49 test files, 311 tests)
  • New tests cover: empty response, multi-chunk body, streaming beyond maxSize without content-length header

Closes #408

safeFetch runs SSRF guards (HTTPS-only check + assertNotPrivateHost)
against the initial URL, but Electron's net.fetch follows HTTP redirects
(3xx) by default without re-running any checks. An attacker-controlled
endpoint can respond with 302 Location: http://127.0.0.1/admin to
bypass the SSRF guard entirely.

Fix: pass redirect:'error' to net.fetch so any 3xx response throws
immediately instead of being followed.

Closes #406
safeFetch enforces maxSize after await res.arrayBuffer() has already
buffered the entire response body in memory. A malicious server can
omit or lie about content-length and stream an endless body, causing
the main process to OOM the entire Electron app.

Switch to a streaming reader (res.body.getReader()) that accumulates
chunks and aborts as soon as the accumulated byte count exceeds
maxSize. The content-length pre-check is kept as a cheap short-circuit
for well-behaved servers.

Closes #408
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

Hi @HiddenPuppy,
Thanks for your pull request!
If the PR is ready, use the /auto-cc command to assign Reviewer to Review.
We will review it shortly.

Details

Instructions for interacting with me using comments are available here.
If you have questions or suggestions related to my behavior, please file an issue against the gh-ci-bot repository.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request improves the security and memory efficiency of the safeFetch utility. By switching from buffered to streaming response handling, the application is now protected against malicious servers that could cause out-of-memory crashes by streaming data beyond the configured size limits. Additionally, it tightens network security by explicitly disabling automatic redirects.

Highlights

  • Incremental Size Enforcement: Replaced the monolithic res.arrayBuffer() call with a streaming reader that accumulates chunks and aborts the fetch immediately if the total size exceeds the defined limit.
  • SSRF Protection: Added redirect: 'error' to the fetch options to prevent potential SSRF bypasses via 3xx redirects.
  • Enhanced Test Coverage: Introduced a mock stream reader in the test suite to verify incremental size enforcement, empty responses, and multi-chunk body handling.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors safeFetch to use a streaming approach for reading response bodies, enabling incremental size enforcement and improving security by disabling redirects. Review feedback suggests handling null response bodies gracefully to maintain compatibility with the previous implementation and optimizing the buffer concatenation process by providing the total length and removing unnecessary mappings.

Comment on lines +37 to +38
const reader = res.body?.getReader();
if (!reader) throw new Error('no body');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation throws an error if res.body is null (e.g., for a 204 No Content response). The previous implementation using res.arrayBuffer() would return an empty buffer in such cases. To maintain compatibility and handle body-less responses gracefully, consider returning an empty buffer instead of throwing.

Suggested change
const reader = res.body?.getReader();
if (!reader) throw new Error('no body');
if (!res.body) return Buffer.alloc(0);
const reader = res.body.getReader();

}
chunks.push(value);
}
return Buffer.concat(chunks.map((c) => Buffer.from(c)));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Buffer.concat accepts an array of Uint8Array directly, so mapping chunks to Buffer instances is unnecessary. Additionally, since the total length is already tracked in the total variable, providing it to Buffer.concat avoids an extra iteration to calculate the length.

Suggested change
return Buffer.concat(chunks.map((c) => Buffer.from(c)));
return Buffer.concat(chunks, total);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] safeFetch buffers entire response before enforcing maxSize — memory DoS

1 participant