Classic request smuggling needs two servers that disagree and a connection shared between strangers. A client side desync attack throws out both requirements. The desync happens on the one connection the victim’s own browser holds open to the server, so there is no shared back end hop to abuse and no second server that has to parse length differently. If the ideas here feel new, our primer on http request smuggling sets up the vocabulary first.
What makes a client side desync attack different
In the usual story a front end forwards your request to a back end over a connection many users share, and the attack is a disagreement between those two servers. A client side desync removes the middle. It only needs one server that mishandles the body of a request: a server that sends its response and moves on without reading the whole body you declared. The bytes you declared but the server never consumed do not disappear. They sit in the connection, waiting to be read as the beginning of whatever comes next.
The connection in question is the one between the victim’s browser and the server. Browsers keep connections alive and reuse them for several requests to the same origin. So if the first request leaves unread bytes in that socket, the browser’s next request on the same connection lands right behind them. The server reads the attacker’s leftover bytes first, then the browser’s real request, and fuses the two into one request it then runs.
The poisoned connection does not live on a server shared by many users. It lives inside the victim’s own browser, which is why a single endpoint that forgets to read a body is enough to pull off the whole attack.
How a single server ends up desynced
The flaw is a server that commits to a response before it finishes reading the request. A common shape is an endpoint that replies early for certain requests, a redirect, a static asset, or an error page, while the client is still allowed to send a body the server said it would read. The server declared through its handling that a body of some length was coming, then answered without draining it. Now the body is stranded in the connection.
An attacker builds a request where that stranded body is not junk but a valid request line and headers. Suppose the vulnerable endpoint is a redirect that ignores the body it was told to expect. The attacker sends a POST to it with a Content-Length that covers an extra request hidden in the body:
POST /redirect HTTP/1.1 Host: acme-notes.example Content-Length: 34 GET /private HTTP/1.1 Foo: bar
The server answers the redirect and never reads the 34 byte body. Those 34 bytes, the smuggled GET /private and a dangling header, stay in the connection. When the browser sends its next request to the same origin, that request is appended to the smuggled lines and the server runs the attacker’s GET /private carrying the victim’s session.
Why it reaches sites a classic desync cannot
Front end desync bugs have been getting fixed, and plenty of sites now run a single well behaved server or a chain that agrees on length. A client side desync does not care about any of that. It needs only one request handler on one server that answers before draining the body. Because the vulnerable behavior is per endpoint, a site that is clean everywhere else can still expose one redirect or one static path that detaches the body, and that single path is enough. The attack also reaches the victim directly, since the poisoned connection is theirs. You are not waiting to land a prefix on a random stranger sharing a back end socket. You are steering one specific person’s browser.
That direct reach is what makes it dangerous. Once the attacker controls the first request on the victim’s connection, they can prefix the victim’s next navigation with a request to any path, capture the victim’s own request and its cookies into a stored field, or redirect the victim’s browser to a response the attacker chose. It is a close relative of capturing other users requests, except the captured user is the one the attacker lured to a page rather than whoever happened to share a connection.
Finding and shutting it down
You confirm a client side desync by checking whether one request leaves the connection in a state where the next request is misread. A safe way to test is to open a single connection, send the suspect request followed by a plain request, and watch whether the second response answers the smuggled line instead of the plain one. If it does, the server detached the body and the socket is poisoned. This is an injection and input problem at heart: bytes that were supposed to be an inert body get read as a request because the server stopped reading at the wrong place.
The fix is to make the server read or discard the entire declared body before it reuses or closes the connection, and to close the connection outright whenever it answers early rather than leaving unread bytes behind. A handler that will not consume a body should refuse the request or drain it, never reply and leave the remainder parked in the socket.
A client side desync is easy to miss with a scanner because every byte in the attacker’s request is individually legal and the bug only appears when one real browser reuses one real connection. You find it by asking what a specific endpoint assumes about the body it was promised, then arranging for that promise to be broken. That is the kind of per endpoint assumption an autonomous researcher is built to test, and at UnboundCompute we verify a desync is actually reachable before reporting it, so a finding is a proven path and not a guess. The delivery side, how an attacker page actually makes the browser send these bytes, is the subject of browser powered request smuggling.
Frequently asked questions
What is a client side desync attack?
It is a desync that happens on the single connection between a victim’s own browser and a server, instead of on a connection shared by many users behind a front end. One server mishandles a request body, leaving bytes stranded in the connection, and the browser’s next request on that same connection is read as a continuation of the attacker’s stranded bytes. Because it is the victim’s connection, the smuggled request carries the victim’s cookies.
How is it different from classic request smuggling?
Classic smuggling needs a front end and a back end that read length differently and a connection shared between strangers. A client side desync needs neither. One server that answers before reading the whole body is enough, and the poisoned connection is the victim’s own, so the attacker targets one chosen person rather than whoever happens to share a back end socket.
Why does it reach sites with no front end desync?
Because the vulnerable behavior is a single endpoint that detaches the body, not a parser mismatch between two servers. A site can run one well behaved server, or a chain that agrees on length everywhere, and still expose one redirect or static path that answers without draining the body. That single path is all a client side desync needs.
How do you test for it safely?
Open one connection, send the suspect request followed by a plain request, and check whether the second response answers the smuggled line instead of the plain request. If it does, the server detached the body and left the socket poisoned. A safe probe smuggles an inert request so you confirm the desync without causing harm.
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.
