Build1 publisher2 min readPublished
Chunked DELETE requests can smuggle a hidden second request through Next.js rewrites
Next.js's bundled http-proxy 1.18.1 stamps Content-Length: 0 on chunked DELETE and OPTIONS rewrites, so a hidden second request reaches the backend. Whether it reaches anything sensitive depends on how the next hop frames requests and which internal routes the backend opens to its proxy.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction
What happened
- In the published proof of concept, a chunked DELETE /rewrites/poc carries a complete GET /secret request as plain text inside its body.
- The proxy's deleteLength() step also deletes the Transfer-Encoding header, but pipe() still forwards every body byte to the next hop.
- A downstream proxy or load balancer reads the request as zero bytes long and parses GET /secret as a separate request, so the backend receives two.
- The fix sets Content-Length: 0 only when both Content-Length and Transfer-Encoding are absent, and it stops deleting Transfer-Encoding.
- The same commit hardens setupOutgoing() in http-proxy's common.js to close a connection-reuse bypass.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- exposure Backend routes that trust traffic from the Next.js host, such as internal APIs and admin endpoints, can be hit by requests the Next.js router only ever saw as body bytes.
- constraint Because http-proxy is vendored inside Next.js, pinning or bumping http-proxy in an app's own dependency tree does not change the copy that rewrites() runs.
- decision Until a fixed release is deployed, the trigger can be filtered at the edge, since the bad branch fires only on DELETE or OPTIONS requests that arrive without Content-Length.
According to the dev.to write-up on CVE-2026-29057, the vulnerable branch sits in `lib/http-proxy/passes/web-incoming.js`, and it is short enough to read in one pass [14][2][3]:
```js if ((req.method === 'DELETE' || req.method === 'OPTIONS') && !req.headers['content-length']) { req.headers['content-length'] = '0'; delete req.headers['transfer-encoding']; } ```
The intent is sound. DELETE and OPTIONS requests usually have no body, so the proxy tries to say so explicitly [12]. The test is wrong. A request framed with `Transfer-Encoding: chunked` is supposed to arrive without a Content-Length header, so the check passes for exactly the requests that do carry a body [13]. For a function named `deleteLength()`, it was adding a length and deleting the encoding instead [3].
Next.js itself parses the request correctly. It decodes the chunked body, so at that layer `GET /secret` is just data inside a DELETE [6]. Any routing or access decision made inside Next.js applies to the outer DELETE alone [6]. The second request only becomes a request one hop later.
For this to work against a given deployment, each of these has to hold:
1. The app sends a path to an external backend through `rewrites()`, the code path that runs the vendored http-proxy [1]. 2. A client can reach that path with DELETE or OPTIONS and no Content-Length header [3]. 3. The hop after Next.js frames requests by Content-Length and parses whatever bytes follow the declared end as a new request [7].
The fix is small and correct. With the patched condition, Node.js re-encodes the forwarded body as chunked. Each chunk declares its own size in hex, so a `2E` prefix means 46 bytes, and a terminating zero chunk ends the body [10][1]. The hidden request stays inside a chunk as body data [10]. The companion change in `setupOutgoing()` lowercases each outgoing header name before comparing it to `transfer-encoding`, so the check holds however a client capitalises the header [15].
The write-up does not list which Next.js releases carry the fix [14].
What to watch
- A Next.js advisory or release note naming the versions that ship the patched http-proxy copy for CVE-2026-29057.
- Whether the deleteLength and setupOutgoing fixes reach other projects that depend on http-proxy 1.18.1.
- Reports of the technique being used against admin or internal routes behind rewrites() in production.