You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
HTTP response smuggling PRIMITIVE via a trailing HTAB in Transfer-Encoding #876
RFC 9110 §5.6.3 requires trailing OWS (SP or HTAB) to be stripped before a field value is
interpreted. header_value_te_chunked_last accepts SP but not HTAB, so chunked\t falls through
to header_value_te_token and into the generic fallback: F_TRANSFER_ENCODING is set, F_CHUNKED
is not. The message is then framed close-delimited and the chunk framing is handed to the
application as body bytes.
Responses only. On requests the value reaches forbidAfterChunkedInRequest and errors, unless lenient_transfer_encoding is set.
PoC
consthttp=require('http'),net=require('net');constRESP='HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\t\r\n\r\n'+// note the trailing TAB'5\r\nhello\r\n0\r\n\r\n'+// a complete chunked body'HTTP/1.1 200 OK\r\nContent-Length: 9\r\n\r\nINJECTED!';// a second, separate responseconstorigin=net.createServer(c=>c.once('data',()=>{c.write(RESP);setTimeout(()=>c.end(),100);}));origin.listen(0,()=>http.get({port: origin.address().port},res=>{letbody='';res.on('data',c=>body+=c);res.on('end',()=>{console.log("res.headers['transfer-encoding'] =",JSON.stringify(res.headers['transfer-encoding']));console.log('res.complete =',res.complete);console.log('body =',JSON.stringify(body));origin.close();});}));
Node trims the tab before exposing the header, so the application sees transfer-encoding: chunked
and complete: true while llhttp framed the message as identity. rawHeaders carries the same
trimmed value, so the discrepancy is not observable through any public API. The second response is
delivered as body content.
The same bytes, parsed with the tab stripped, yield a different message count:
One message versus two, so an llhttp consumer sharing a byte stream with an OWS-trimming peer
disagrees about where the response ends. keepalive=0 limits this to the connection llhttp owns;
the application still receives attacker-chosen bytes as the body of a response it believes is
chunked.
Activity
MegaManSec commentedon Aug 23, 2026
Fixed in #875