The HTTP/1 server request parser had three framing primitives that could make HTTP.jl disagree with a fronting proxy about message boundaries on a reused keep-alive connection. (1) _readline_crlf tolerated a bare LF on its buffered fast path but required CRLF on the slow path, so the accepted header grammar depended on TCP segmentation and an absorbed bare LF could silently merge headers. (2) _parse_chunk_size delegated to Base.parse(Int64, ...; base=16), which tolerates a leading sign, a 0x prefix, and whitespace padding (including a trailing bare CR). (3) For HTTP/1.0, Transfer-Encoding was stripped with a fallback to Content-Length while the connection could stay open, and a request carrying both Transfer-Encoding and Content-Length was silently treated as chunked.
These parsing discrepancies enabled HTTP request smuggling past a fronting proxy on pooled/keep-alive connections.
Fixed in HTTP.jl v2.4.0. Both line-reading paths now require a strict CRLF and reject a bare LF; chunk sizes use a strict byte-by-byte 1*HEXDIG parser (rejecting signs, prefixes, and whitespace); HTTP/1.0 messages carrying Transfer-Encoding are rejected; and any request carrying both Transfer-Encoding and Content-Length is rejected. All rejections surface as a 400 with the connection force-closed so no ambiguous trailing bytes remain.
Reported to the JuliaLang security team through Anthropic's Coordinated Vulnerability Disclosure program.
{
"license": "CC-BY-4.0"
}