Repository navigation
Angular SSR buffers streaming request bodies when sanitizing untrusted X-Forwarded-* headers #33706
Description
Description
Angular SSR can retain an incoming streaming request body in memory when it removes an untrusted X-Forwarded-* header.
The affected header-sanitization path creates a new request from request.clone():
return headersDeleted
? new Request(request.clone(), {
signal: request.signal,
headers,
})
: request;For a POST, PUT, or other request with a body, Request.clone() tees the underlying stream. If the SSR application consumes REQUEST.body, the second unused branch can buffer the uploaded body in Node.js external / ArrayBuffer memory.
As a result, an operation that normally streams with bounded memory can instead consume memory proportional to the request body size.
This affects Angular SSR deployments that:
- use
AngularNodeAppEngine/AngularAppEngine; - expose a non-
GETSSR route that readsREQUEST.body; - receive an untrusted
X-Forwarded-*header that Angular removes; - allow request bodies large enough to create meaningful memory pressure.
Possible examples include SSR form handlers, webhook-style endpoints, upload handlers, PDF/export routes, or custom server-rendered endpoints that use the request body.
The issue is deployment-dependent because reverse proxies or body-size limits may reduce the impact. However, when those limits are permissive, one or more concurrent requests can cause significant process or container memory growth.
Minimal Reproduction
- Create an Angular SSR application using
AngularNodeAppEngine. - Add a server-rendered route that injects
REQUESTand reads its body as a stream without intentionally buffering it. - Run the production server with memory logging.
- Send the same large request with and without an untrusted forwarded header.
Control request:
curl -X POST -H "Content-Type: application/octet-stream" --data-binary "@body256.bin" http://localhost:4200/Triggering request:
curl -X POST -H "Content-Type: application/octet-stream" -H "X-Forwarded-For: 1.2.3.4" --data-binary "@body256.bin" http://localhost:4200/X-Forwarded-For must not be trusted through trustProxyHeaders, so Angular removes it.
Without the forwarded header, the body is streamed and arrayBuffers returns close to baseline after it is consumed.
With the same body and only the additional untrusted header:
- Angular enters the header-sanitization path;
Request.clone()tees the request body;- the SSR application consumes one branch;
- the unused branch retains approximately the uploaded body size.
In a 256 MB test, arrayBuffers grew to approximately 260 MB and RSS increased from roughly 143 MB to 378 MB, while the V8 heap remained nearly unchanged.
Concurrent requests multiply the memory impact and may exhaust the SSR worker or container.
POC in https://issuetracker.google.com/u/1/issues/523052797
Your Environment
- Angular CLI / Angular SSR: `22.1.0-next.0`
- Node.js: `22.22.3`
- Build: production
- Engine: `AngularNodeAppEngine` / `AngularAppEngine`
- Render mode: `RenderMode.Server`
Anything else relevant?
This was previusly reported in https://issuetracker.google.com/u/1/issues/523052797
Activity
alan-agius4 commentedon Jul 31, 2026
Hi @SkyZeroZx,
Thank you for reporting this and for the clear reproduction!
We agree with your assessment calling
request.clone()when removing untrustedX-Forwarded-*headers teed theReadableStream, causing Node.js/undicito buffer the unread stream branch in memory whenREQUEST.bodywas consumed.We classify this as a performance / resource-handling bug rather than a security issue. Unlike SSRF, Open Redirect, or Header Injection vulnerabilities, this does not bypass authentication or routing integrity. Furthermore, standard production reverse proxies and gateways typically enforce body size limits and strip untrusted forwarded headers before reaching the Node.js worker.
angular-automatic-lock-bot commentedon Sep 13, 2026
This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
This action has been performed automatically by a bot.