For years request smuggling was something you ran from your own machine with an intercepting proxy, aimed at a back end that pooled connections between strangers. The technique called browser powered request smuggling flips the sender: the victim’s own browser issues the crafted request, so the smuggled bytes travel on the victim’s connection with the victim’s cookies attached. If the underlying desync is unfamiliar, read the primer on http request smuggling first, then treat this post as the delivery half.
Why browser powered request smuggling hits the victim directly
The old model had a limit. Running the attack from your own client meant the poisoned connection was between you and the server, so you could smuggle a prefix onto a shared back end socket and hope it landed in front of some other user’s request. You never knew whose. Driving the attack from the victim’s browser removes the guessing. The connection belongs to the victim, the cookies are the victim’s, and the request you prefix is the victim’s own next request. You are not fishing on a shared pool. You are steering one person.
Moving the sender into the victim’s browser changes smuggling from an attack on whoever happens to share a connection into an attack on one chosen person, because the poisoned connection is the one their browser already holds.
What the browser will and will not let you send
A browser is not an intercepting proxy. It guards request structure on purpose. JavaScript cannot put a raw carriage return and line feed into a header, cannot set forbidden headers like Host or Content-Length to arbitrary values, and cannot open a raw socket and type bytes. What it can do is set the request body to anything at all. So browser powered smuggling only works when the desync can be triggered from the body alone, with no forged header structure required. That is exactly the shape a client side desync gives you: an endpoint that detaches the body, so a request line you placed inside the body becomes a prefix without any header trickery.
The sending is done with fetch, and a few of its options are what make the connection behave the way the attack needs:
credentials: "include"so the victim’s cookies ride along and the smuggled request is authenticated as them.mode: "no-cors"so the cross origin request is allowed to go out even though the attacker’s page cannot read the response. Reading it is not the point; poisoning the connection is.redirect: "manual"so the browser does not chase a redirect and tear the connection down before the second request lands on it.- a
POSTmethod with a text body that carries the smuggled request line and its headers, which is the prefix you want stranded in the socket.
The two step sequence
The attack is two requests on one connection. The attacker page issues the first fetch to the vulnerable endpoint. That endpoint detaches the body, so the smuggled prefix is left sitting in the connection. Then the page issues a second request to the same origin, or navigates the victim there, and the browser reuses the poisoned connection. The server reads the stranded prefix, then the second request, and runs the attacker’s request carrying the victim’s session. Because the first response is one the attacker does not even need to read, no-cors is no obstacle. Everything hangs on the second request landing on the same socket as the first.
An example pouring a smuggled GET into the body of a request to a body detaching endpoint looks like this from the attacker’s page:
fetch("https://acme-notes.example/redirect", {
method: "POST",
credentials: "include",
mode: "no-cors",
redirect: "manual",
body: "GET /account HTTP/1.1\r\nFoo: bar"
});
The body is plain text to the browser. On the wire it is the request body the vulnerable endpoint forgets to read, and those bytes become the start of the next request the server parses on that connection.
Where it fits and how to contain it
This is a delivery technique, not a new desync. It rides on top of a connection that can be poisoned from the body, which in practice means a client side desync, and we cover that underlying bug in client side desync attack. Read together, one post is the flaw and this one is how an attacker reaches a real victim with it. It belongs to the broader family of injection and input issues, where attacker controlled bytes cross into a place that reads them as structure.
Containment is mostly about the server, not the browser, because the browser is only the courier. Fix the endpoint that detaches bodies so no prefix can be stranded, close connections that answer early, and the courier has nothing to carry. On the browser side, the same origin and connection reuse behavior the attack rides on is standard and not something you want to break, so the durable fix is at the server that desyncs.
Browser powered delivery is a reminder that a server bug and its reach are two different questions. A desync that looked low risk because it needed a shared back end can become a direct attack on a named victim once the browser is the sender. Judging that reach takes more than spotting the quirk; it takes proving the second request really lands on the poisoned connection. That is the kind of end to end proof an autonomous researcher is built to produce, and at UnboundCompute we verify a desync is actually reachable before reporting it, so what you get is a confirmed path rather than a theory.
Frequently asked questions
What is browser powered request smuggling?
It is a delivery technique where the victim’s own browser, driven by JavaScript on an attacker page, sends the crafted request instead of the attacker’s own tool. Because the browser sends it, the smuggled bytes travel on the victim’s connection with the victim’s cookies, which turns a server side desync into a direct attack on one chosen person.
What fetch settings does it rely on?
The attacker page uses fetch with credentials: "include" so the victim’s cookies ride along, mode: "no-cors" so the cross origin request is allowed to leave even though the response cannot be read, and redirect: "manual" so the browser does not follow a redirect and tear down the connection. The request is a POST whose body carries the smuggled request line.
Why must the smuggle work from the body alone?
A browser guards request structure. JavaScript cannot put a raw carriage return and line feed into a header, cannot set forbidden headers to arbitrary values, and cannot open a raw socket. It can set the body to anything. So browser powered smuggling only works on a desync that can be triggered from the body, which is exactly what a client side desync endpoint provides.
How do you contain it?
Fix the server, not the browser. The browser is only the courier, so the durable fix is the endpoint that detaches bodies: read or discard the whole declared body before responding, and close connections that answer early. Once no prefix can be stranded, the browser has nothing to carry.
Put an autonomous researcher on your own systems
UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.
