Skip to content

perf(@angular/ssr): avoid buffering request body when sanitizing headers - #33711

Merged
alan-agius4 merged 2 commits into
angular:mainfrom
alan-agius4:fix-33706
Jul 31, 2026
Merged

perf(@angular/ssr): avoid buffering request body when sanitizing headers#33711
alan-agius4 merged 2 commits into
angular:mainfrom
alan-agius4:fix-33706

Conversation

@alan-agius4

Copy link
Copy Markdown
Collaborator

Avoid teeing the incoming request body stream with request.clone() when removing untrusted X-Forwarded-* headers in sanitizeRequestHeaders.

Teeing the stream caused the unconsumed branch to buffer the uploaded body in memory. Passing request directly to new Request(request, { headers }) transfers the stream without teeing or buffering.

Additionally, request.signal is automatically inherited by the new Request instance without needing to pass it explicitly.

Closes #33706

Avoid teeing the incoming request body stream with `request.clone()` when removing untrusted `X-Forwarded-*` headers in `sanitizeRequestHeaders`.

Teeing the stream caused the unconsumed branch to buffer the uploaded body in memory. Passing `request` directly to `new Request(request, { headers })` transfers the stream without teeing or buffering.

Additionally, `request.signal` is automatically inherited by the new `Request` instance without needing to pass it explicitly.

Closes angular#33706
@angular-robot angular-robot Bot added area: performance Issues related to performance area: @angular/ssr labels Jul 31, 2026

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

Copy link
Copy Markdown

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 updates the sanitizeRequestHeaders utility in Angular SSR to avoid cloning the incoming request when removing unallowed proxy headers. Instead of calling request.clone(), the original request is passed directly to the Request constructor, which prevents unnecessary teeing of the request body and preserves the abort signal. A corresponding unit test has been added to verify this behavior. There are no review comments, and I have no additional feedback to provide.

@alan-agius4
alan-agius4 requested a review from dgp1130 July 31, 2026 07:18
@alan-agius4 alan-agius4 added action: review The PR is still awaiting reviews from at least one requested reviewer target: patch This PR is targeted for the next patch release labels Jul 31, 2026
expect(secured.headers.get('forwarded')).toBe('host=proxy.com;proto=https');
});

it('should not tee request body and should preserve abort signal when removing unallowed headers', async () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Question: How exactly does this test that teeing didn't happen? I'm not sure how you'd verify that, and bodyUsed doesn't seem to quite represent that. I wonder if this test should just focus on AbortSignal?

@alan-agius4 alan-agius4 Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

In the WHATWG Fetch specification (request.clone()), calling request.clone() tees the body stream into two branches without marking bodyUsed as true on the original request (which caused the unconsumed branch to buffer the body in memory).

In contrast, passing request directly to new Request(request, ...) (Step 35 of the Request constructor spec) extracts and transfers the underlying readable stream from the source request without teeing it, which immediately marks the source request as disturbed and sets request.bodyUsed to true. So checking expect(req.bodyUsed).toBeTrue() verifies that the original request was consumed/transferred directly rather than cloned/teed (where bodyUsed would remain false).

That said, I agree checking bodyUsed wasn't obvious on its own, and combining both assertions made the test busy. I've updated the test file to split this into two focused unit tests one for AbortSignal preservation and one for stream transfer without teeing—and added an explanatory comment documenting why bodyUsed is checked.

@alan-agius4 alan-agius4 added action: merge The PR is ready for merge by the caretaker and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels Jul 31, 2026
@alan-agius4
alan-agius4 merged commit 3316980 into angular:main Jul 31, 2026
40 checks passed
@alan-agius4
alan-agius4 deleted the fix-33706 branch July 31, 2026 18:04
@alan-agius4

Copy link
Copy Markdown
Collaborator Author

This PR was merged into the repository. The changes were merged into the following branches:

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

Labels

action: merge The PR is ready for merge by the caretaker area: @angular/ssr area: performance Issues related to performance target: patch This PR is targeted for the next patch release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Angular SSR buffers streaming request bodies when sanitizing untrusted X-Forwarded-* headers

2 participants