Skip to content

[Fix] safeFetch streaming maxSize memory DoS regression test (#408)#534

Merged
github-actions[bot] merged 2 commits into
clawwork-ai:mainfrom
advancedresearcharray:fix/408-safefetch-memory-dos
Jun 10, 2026
Merged

[Fix] safeFetch streaming maxSize memory DoS regression test (#408)#534
github-actions[bot] merged 2 commits into
clawwork-ai:mainfrom
advancedresearcharray:fix/408-safefetch-memory-dos

Conversation

@advancedresearcharray

@advancedresearcharray advancedresearcharray commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #408

Test plan

  • pnpm --filter @clawwork/desktop test -- safe-fetch.test.ts — 25 tests passed
  • New test: does not read the entire oversized body before rejecting — bounded streaming (#408)

release-note

NONE

@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 introduces a regression test for safeFetch to ensure that it properly handles oversized streamed responses. By validating that the fetch operation terminates and cancels the stream reader as soon as the size limit is reached, it confirms the fix for a potential memory denial-of-service vulnerability.

Highlights

  • Regression Testing: Added a new test case to verify that safeFetch correctly enforces maxSize limits during streaming, preventing memory DoS vulnerabilities.
  • Streaming Validation: Ensured that the reader is cancelled and processing stops immediately once the maxSize threshold is exceeded, rather than draining the entire body.
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.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Hi @advancedresearcharray,
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.

clawwork-ai#408)

The streaming maxSize enforcement and reader cancellation were merged in
clawwork-ai#487 and clawwork-ai#523, but issue clawwork-ai#408 remained open. Add a regression test that
verifies safeFetch stops reading an oversized body before draining all
chunks, preventing the memory DoS described in the issue.

Closes clawwork-ai#408

Signed-off-by: Array Agent <advancedresearcharray@array.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@advancedresearcharray advancedresearcharray force-pushed the fix/408-safefetch-memory-dos branch from de3b747 to 34b9ebb Compare June 8, 2026 21:42

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

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.

Code Review

This pull request adds a new test case to verify that safeFetch implements bounded streaming and does not read an entire oversized body before rejecting. The review feedback suggests simplifying the test mock setup by directly spying on the reader returned by streamReaderFrom and returning it, rather than manually reconstructing a duplicate reader object.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +300 to +322
const reader = streamReaderFrom(new ArrayBuffer(bodySize), chunkSize, cancel);
const read = vi.fn(reader.read.bind(reader));
netFetchMock.mockResolvedValue({
ok: true,
status: 200,
headers: new Headers(),
body: {
getReader: () =>
({
read,
cancel,
releaseLock: () => {},
closed: Promise.resolve(undefined),
}) as ReadableStreamDefaultReader<Uint8Array>,
},
} as unknown as Response);

await expect(safeFetch('https://cdn.example.com/huge-stream.bin', { maxSize })).rejects.toThrow('too large');

// 5 chunks (1280 bytes) exceed maxSize; must not drain the full 10 KiB body.
expect(read.mock.calls.length).toBeLessThan(bodySize / chunkSize);
expect(read.mock.calls.length).toBeLessThanOrEqual(Math.ceil(maxSize / chunkSize) + 1);
expect(cancel).toHaveBeenCalledOnce();

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 streamReaderFrom helper function already returns a fully-formed ReadableStreamDefaultReader<Uint8Array> object containing read, cancel, releaseLock, and closed properties. Instead of manually reconstructing a duplicate reader object in the getReader mock and manually binding/wrapping reader.read with vi.fn, you can simplify the test by spying on the reader directly with vi.spyOn(reader, 'read') and returning reader from getReader.

Suggested change
const reader = streamReaderFrom(new ArrayBuffer(bodySize), chunkSize, cancel);
const read = vi.fn(reader.read.bind(reader));
netFetchMock.mockResolvedValue({
ok: true,
status: 200,
headers: new Headers(),
body: {
getReader: () =>
({
read,
cancel,
releaseLock: () => {},
closed: Promise.resolve(undefined),
}) as ReadableStreamDefaultReader<Uint8Array>,
},
} as unknown as Response);
await expect(safeFetch('https://cdn.example.com/huge-stream.bin', { maxSize })).rejects.toThrow('too large');
// 5 chunks (1280 bytes) exceed maxSize; must not drain the full 10 KiB body.
expect(read.mock.calls.length).toBeLessThan(bodySize / chunkSize);
expect(read.mock.calls.length).toBeLessThanOrEqual(Math.ceil(maxSize / chunkSize) + 1);
expect(cancel).toHaveBeenCalledOnce();
const reader = streamReaderFrom(new ArrayBuffer(bodySize), chunkSize, cancel);
const readSpy = vi.spyOn(reader, 'read');
netFetchMock.mockResolvedValue({
ok: true,
status: 200,
headers: new Headers(),
body: {
getReader: () => reader,
},
} as unknown as Response);
await expect(safeFetch('https://cdn.example.com/huge-stream.bin', { maxSize })).rejects.toThrow('too large');
// 5 chunks (1280 bytes) exceed maxSize; must not drain the full 10 KiB body.
expect(readSpy.mock.calls.length).toBeLessThan(bodySize / chunkSize);
expect(readSpy.mock.calls.length).toBeLessThanOrEqual(Math.ceil(maxSize / chunkSize) + 1);
expect(cancel).toHaveBeenCalledOnce();

…lawwork-ai#408)

Use vi.spyOn on streamReaderFrom's read method instead of reconstructing
a duplicate reader object, addressing PR review feedback on clawwork-ai#534.

Signed-off-by: Array Agent <advancedresearcharray@array.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@advancedresearcharray

Copy link
Copy Markdown
Contributor Author

/auto-cc

@advancedresearcharray

Copy link
Copy Markdown
Contributor Author

Friendly follow-up: CI is green on the latest commit and this PR is ready for review/merge when you have a moment. Thanks!

@samzong

samzong commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Thanks @advancedresearcharray!
/lgtm
/approve

@github-actions github-actions Bot added lgtm Looks good to me approved Approved for merge labels Jun 10, 2026
@github-actions github-actions Bot merged commit d7c1a70 into clawwork-ai:main Jun 10, 2026
11 checks passed
@advancedresearcharray advancedresearcharray deleted the fix/408-safefetch-memory-dos branch June 10, 2026 20:03
advancedresearcharray pushed a commit to advancedresearcharray/ClawWork that referenced this pull request Jun 13, 2026
Closes clawwork-ai#408

The streaming maxSize enforcement (clawwork-ai#487), reader cancellation (clawwork-ai#523), and
regression tests (clawwork-ai#534) are already on main. This commit closes the issue
that remained open after squash merges omitted the closing keyword.

Signed-off-by: advancedresearcharray <advancedresearcharray@github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
advancedresearcharray pushed a commit to advancedresearcharray/ClawWork that referenced this pull request Jun 13, 2026
Closes clawwork-ai#408

The streaming maxSize enforcement (clawwork-ai#487), reader cancellation (clawwork-ai#523), and
regression tests (clawwork-ai#534) are already on main. This commit closes the issue
that remained open after squash merges omitted the closing keyword.

Signed-off-by: advancedresearcharray <advancedresearcharray@github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
advancedresearcharray pushed a commit to advancedresearcharray/ClawWork that referenced this pull request Jun 13, 2026
Closes clawwork-ai#408

The streaming maxSize enforcement (clawwork-ai#487), reader cancellation (clawwork-ai#523), and
regression tests (clawwork-ai#534) are already on main. This commit closes the issue
that remained open after squash merges omitted the closing keyword.

Signed-off-by: advancedresearcharray <advancedresearcharray@github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
advancedresearcharray pushed a commit to advancedresearcharray/ClawWork that referenced this pull request Jun 13, 2026
Verified reader.cancel() + controller.abort() on streamed bytes exceeding
maxSize, IPv6 pinned hostname bracketing, and 25 regression tests passing.
Fix already landed on main via clawwork-ai#487, clawwork-ai#523, clawwork-ai#534; this commit closes clawwork-ai#408.

Closes clawwork-ai#408

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Approved for merge lgtm Looks good to me

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants