react/http: Unbounded HTTP Client Response Header Buffering Leads to Memory Exhaustion DoS
CVE-2026-104827 · GHSA-g4f2-2pf3-2pwj · published 15 Oct 2026
How serious is it
- Severity: high, CVSS score 7.5 out of 10.
- No exploit prediction (EPSS) score yet.
- Not on the CISA list of exploited vulnerabilities.
- No public exploit in Exploit-DB.
Is your app affected by CVE-2026-104827?
Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.
Who is affected
react/http(Packagist) >= 1.0.0, <= 1.11.0
How to check
- Run
composer show react/httpin your project to see the installed version, including copies pulled in by other packages. - Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.
How to fix
Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
- Upgrade
react/httpto 1.11.1 or later, commit the lockfile and redeploy. - If a vulnerable version was live, check your logs and rotate the secrets that server could read.
The advisory
### Summary
`React\Http\Browser` and its lower-level HTTP stack impose no limit on the size of a response's header section while it is being received. `ClientRequestStream::handleData()` appends every incoming byte to an internal buffer and only scans that buffer for the `\r\n\r\n` (or `\n\n`) terminator, without ever checking its length. A remote server that withholds the terminator and keeps streaming can grow the client's memory without bound until PHP's `memory_limit` is exhausted and the process crashes.
This is an unauthenticated, network-reachable denial of service against any application that uses `react/http` as a client to fetch untrusted or attacker-influenced URLs, including via redirects.
### Details
`ClientRequestStream::handleData()` is bound directly to the underlying connection's `data` event, so every byte the peer sends is appended with no size check:
```php
public function handleData($data)
{
$this->buffer .= $data;
// buffer until double CRLF (or double LF for compatibility with legacy servers)
$eom = \strpos($this->buffer, "\r\n\r\n");
$eomLegacy = \strpos($this->buffer, "\n\n");
if ($eom !== false || $eomLegacy !== false) {
```
The only exit from the buffering state is finding the header terminator or a connection close, so until it arrives the buffer grows monotonically for as long as the peer keeps sending.
The codebase already applies this class of limit elsewhere, but not symmetrically. `RequestHeaderParser` caps incoming **request** headers when acting as a server, and `Transaction::bufferResponse()` caps the response **body**, but only once the header section has already been parsed into a `Response`. Response header reception was the one stage with no limit at all.
### PoC
An HTTP server that sends a valid status line and a partial header, then streams indefinitely without ever terminating the header section:
```bash
php -r '$s=stream_socket_server("tcp://127.0.0.1:18080"); $c=stream_socket_accept($s); fread($c,8192); fwrite($c,"HTTP/1.1 200 OK\r\nX-Fill: "); while (true) { fwrite($c, str_repeat("A",1048576)); usleep(10000); }'
```
A client fetching it, with `memory_limit=64M`:
```php
$browser = new React\Http\Browser();
$browser->get("http://127.0.0.1:18080/")->then(
function () { echo "ok\n"; },
function ($e) { echo get_class($e) . ": " . $e->getMessage() . "\n"; }
);
```
Client memory grows steadily until the process dies with `Allowed memory size ... exhausted` inside `ClientRequestStream::handleData()`.
### Impact
Remotely triggerable, unauthenticated denial of service against any process using `Browser` to fetch from an attacker-controlled, compromised or otherwise untrusted endpoint, including one reached through a redirect. No user interaction and no special privileges are required. Because there is no upper bound on the header buffer, a single cheap connection can drive memory arbitrarily high and crash the process, taking down whatever event loop or worker it was running. Confidentiality and integrity are unaffected; availability is fully impacted for that process.
Check your whole app
Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.
References
- github.com/reactphp/http/security/advisories/GHSA-g4f2-2pf3-2pwj
- github.com/reactphp/http/commit/2558622d0b76227c60c61eac8a61572cb6638e8c
- github.com/reactphp/http/releases/tag/v1.11.1
- github.com/advisories/GHSA-g4f2-2pf3-2pwj
Merged from GitHub Advisory Database. Last updated 23 Oct 2026. All vulnerabilities · RSS · JSON