CL.0 request smuggling is the variant where the back end treats the body of a request as if it had length zero, no matter what the Content-Length header says. The front end reads Content-Length honestly and forwards the body, but the back end endpoint on the other side ignores it, so those forwarded body bytes are left over and read as the next request on the connection. For the broader mechanics of how two servers disagree about where a request ends, start with our primer on http request smuggling.
Why the zero in CL.0 request smuggling
The smuggling variants are named by how each server reads length, front end first and back end second. CL.TE means the front end trusts Content-Length and the back end trusts Transfer-Encoding. CL.0 follows the same scheme, but the back end does not trust another header, it simply treats the body length as zero. The front end counts the body and sends it. The back end decides there is no body at all. Everything the front end forwarded as the body is now, from the back end’s point of view, the beginning of a brand new request.
CL.0 is not two parsers reading two different headers. It is one server that forwards a body and another that was never going to read it, so the body becomes a request by default.
Which endpoints ignore the body
Plenty of request handlers have no reason to expect a body and are written as if one can never arrive. Those are the CL.0 candidates:
- Static file handlers. A server returning an image or a stylesheet reads the path and streams the file. It does not read a request body, so a body you attach is ignored and stranded.
- Redirects. A handler whose whole job is to answer with a
Locationheader often replies immediately and never touches the body. - Error and default pages. A 404 or a catch all handler that fires before any routing may answer without consuming what you sent.
- Server level responses. Health checks, redirects to HTTPS, and other responses produced by the web server itself rather than the application behind it frequently treat every request as bodyless.
The common thread is a handler that commits to a response based only on the request line and headers, then returns without draining the body the Content-Length promised.
Why CL.0 request smuggling is per endpoint, not per site
This is the part that makes CL.0 slippery. A classic CL.TE desync is a property of the two servers and their parsers, so it tends to hold across the whole site: if the pair disagrees on one path, it disagrees on most of them. CL.0 is decided by one handler. The same back end can read Content-Length correctly on /api/login, consume the body, and be perfectly safe, while the handler for /static/logo.png on the same server ignores the body and desyncs. One path is exploitable and the path next to it is not. That is why you cannot test a site once and call it clean. You have to find the specific endpoint whose handler throws the body away.
Finding a CL.0 endpoint
Hunting for CL.0 is endpoint by endpoint work. You make a list of paths that are likely to answer without reading a body, static assets, redirects, error pages, and server level routes, then test each one for the desync signature. The test is a single connection carrying two requests. You send a POST to the suspect path with a Content-Length that covers a smuggled request hidden in the body, then send a second, ordinary request on the same connection, and watch which response comes back:
POST /static/logo.png HTTP/1.1 Host: acme-notes.example Content-Length: 34 GET /admin HTTP/1.1 Foo: bar
If the endpoint honors Content-Length, it reads the 34 byte body, the smuggled lines never become a request, and your second request gets its own normal response. If the endpoint ignores the body, the smuggled GET /admin is left in the connection, your second request is appended to it, and the response you get back answers the smuggled request instead. That difference in the second response is the signal that the handler is a CL.0 endpoint. Working through the candidate paths this way is covered in more depth in detecting http request smuggling, and it belongs to the family of injection and input flaws, where bytes meant as data get read as structure.
Shutting it down
The fix lives at the endpoints that ignore bodies. A handler that does not expect a body should either read and discard the whole declared body before responding, or refuse requests that carry one and close the connection so nothing is left stranded. The front end can help by normalizing requests and by not forwarding a body to a route it knows is bodyless, but the durable fix is making every handler account for the Content-Length it was sent, even when it has no use for the body.
CL.0 is a good lesson in how narrow a smuggling bug can be. A site can pass every test on its application routes and still hand an attacker one static path that desyncs, because the flaw is a single handler forgetting a body, not a site wide parser mismatch. Finding it means checking endpoints one by one and asking what each handler assumes about the request it answers. That is the kind of per endpoint reasoning an autonomous researcher is built to carry out, and at UnboundCompute we verify a desync is actually reachable before reporting it, so a reported CL.0 endpoint comes with a proven request sequence rather than a hunch.
Frequently asked questions
What is CL.0 request smuggling?
It is the variant where the back end treats a request body as length zero regardless of the Content-Length header. The front end reads Content-Length and forwards the body, but the back end endpoint ignores it, so the forwarded body bytes are left over and read as the start of the next request on the connection.
Which endpoints are usually vulnerable?
Handlers that have no reason to expect a body: static file handlers, redirects, error and default pages, and responses produced by the web server itself such as health checks. They commit to a response from the request line and headers alone, then return without draining the body that Content-Length promised.
Why is CL.0 per endpoint rather than per site?
A classic CL.TE desync is a property of the two servers’ parsers, so it tends to hold across the whole site. CL.0 is decided by one handler. The same back end can read Content-Length correctly on an application route and ignore it on a static path, so one endpoint is exploitable while the one next to it is safe. You have to find the specific handler that throws the body away.
How do you find a CL.0 endpoint?
List paths likely to answer without reading a body, then test each on a single connection. Send a POST with a Content-Length that covers a smuggled request in the body, follow it with an ordinary request, and watch the second response. If it answers the smuggled request, the handler ignored the body and the endpoint is CL.0.
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.
