CVE-2026-34715
MEDIUMSeverity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
Primary rating from GitHub Advisory · only source for this CVE.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
Lifecycle Timeline
2DescriptionGitHub Advisory
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. A proof-of-concept demonstrates injection of custom headers through a redirect URL parameter passed directly to the Location header without sanitization.
Technical ContextAI
The ewe web framework (Erlang package) contains an HTTP/1.1 encoder in src/ewe/internal/encoder.gleam that constructs response headers by directly interpolating header keys and values into raw HTTP byte sequences (BitArray) without stripping or validating CRLF characters (\r\n, bytes 0x0D and 0x0A). The root cause is CWE-113 (Improper Neutralization of CRLF Sequences in HTTP Headers), a classic HTTP injection vulnerability. The encode_headers function concatenates user-supplied strings into the BitArray format using UTF-8 encoding without any character validation: <<acc:bits, key:utf8, ": ", value:utf8, "\r\n">>. This directly mirrors the structure of HTTP/1.1 header syntax, so if an attacker controls a header value and embeds \r\n, they can prematurely terminate the current header line and inject additional headers or even a complete second HTTP response. Notably, ewe's HTTP/1.1 request parser (src/ewe/internal/http1.gleam) implements validate_field_value() to sanitize incoming request headers, creating an asymmetric validation posture where inbound requests are protected but outbound responses are not.
RemediationAI
Apply the vendor-released patch by upgrading ewe to the patched version once released by the ewe maintainers; consult the GitHub advisory at https://github.com/vshakitskiy/ewe/security/advisories/GHSA-x2w3-23jr-hrpf for the specific patched version number and release date. The primary fix is to add CRLF validation to the encode_headers function in src/ewe/internal/encoder.gleam, mirroring the existing validate_field_value() function used for incoming request headers. Specifically, header keys and values must be stripped of or rejected if they contain \r (0x0D) or \n (0x0A) bytes before being embedded into the BitArray output. As a temporary mitigation prior to patching, applications should implement application-level validation: sanitize all user-supplied data before passing it to response.set_header() by removing or rejecting any string containing CRLF sequences, and prefer static or framework-controlled header values over user-supplied ones. Developers should also audit all response header assignments in their ewe applications to identify code paths that accept user input.
Same weakness CWE-113 – HTTP Response Splitting
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-x2w3-23jr-hrpf