Mar 17, 2026·7 min read·4 visits
The ewe library incorrectly permitted trailing HTTP headers to overwrite primary request headers due to a permissive denylist. Attackers could exploit this to forge sensitive headers like Authorization or Cookie, bypassing security controls. The issue is resolved in version 3.0.5 by implementing a strict allowlist.
The ewe web server library for Gleam/Erlang contains a moderate-severity vulnerability in its HTTP/1.1 chunked transfer encoding parser. Prior to version 3.0.5, the library utilized an incomplete denylist for processing HTTP trailer headers, enabling attackers to inject or overwrite critical request headers such as Authorization, Cookie, or X-Forwarded-For. This flaw allows for potential authentication bypass, session hijacking, or identity spoofing depending on the specific application logic deployed atop the library.
The ewe package is a web server library designed for the Gleam and Erlang ecosystems. It handles raw HTTP connection parsing, request routing, and response generation. The vulnerability resides within the library's HTTP/1.1 parsing module, specifically in the routines responsible for handling chunked transfer encoding.
Chunked transfer encoding is a mechanism that allows a server or client to send a data stream in multiple parts without knowing the total content length upfront. The HTTP/1.1 specification allows senders to append "trailer" headers after the final zero-length chunk. These trailers are typically used for metadata generated during the transmission of the body, such as cryptographic checksums or routing diagnostics.
The ewe implementation failed to properly constrain which headers could be supplied in this trailer section. The library categorized incoming trailer fields using a permissive denylist, classified under CWE-184 (Incomplete List of Disallowed Inputs). This architectural decision exposed the server to header injection attacks, where malicious actors could manipulate the internal state of the request object before it reached the application routing layer.
The impact of this flaw is highly dependent on the upstream application architecture. Applications relying on the web framework to validate standard headers for authentication, authorization, or client identification are directly exposed to bypass attacks. By overwriting key headers from the trailer, an attacker controls the security context evaluated by the application.
The root cause of this vulnerability stems from the combination of an incomplete denylist and a permissive header merging strategy. In the src/ewe/internal/http.gleam module, the handle_trailers function is responsible for parsing headers sent after the final chunk. The original implementation attempted to block dangerous headers using the is_forbidden_trailer function.
The is_forbidden_trailer function explicitly blocked only nine standard headers: transfer-encoding, content-length, host, cache-control, expect, max-forwards, pragma, range, and te. Any header not explicitly listed in this function was deemed acceptable. This implementation directly violates the principle of fail-safe defaults, as the HTTP specification contains numerous headers with severe security implications that were omitted from this list.
Once a trailer header bypassed the denylist check, the library invoked request.set_header(req, field, value). In the Gleam HTTP package, set_header is a destructive operation that replaces any existing header with the same name. It does not append the value or isolate the trailer metadata into a separate namespace.
Because of this design, the web server processes the initial request headers, establishes the request context, and then subsequently parses the chunked body. When the parser reaches the trailer section, it systematically overwrites the previously established headers with any attacker-supplied values that bypass the flawed denylist. The resulting manipulated request object is then passed to the user-defined application handlers.
An examination of the vulnerable code reveals the exact mechanism of the bypass. The initial implementation relied on pattern matching against a hardcoded list of forbidden strings. This approach required the maintainers to exhaustively enumerate every standard and non-standard header that could influence application behavior.
fn is_forbidden_trailer(field: String) -> Bool {
case string.lowercase(field) {
"transfer-encoding"
| "content-length"
| "host"
| "cache-control"
| "expect"
| "max-forwards"
| "pragma"
| "range"
| "te" -> True
_ -> False
}
}The fix, introduced in commit 94ab6e7bf7293e987ae98b4daa51ea131c2671ba, replaces the denylist with a strict allowlist. The maintainers discarded the is_forbidden_trailer function and implemented is_allowed_trailer. This forces a positive security model where only explicitly permitted metadata headers are merged into the main request object.
fn is_allowed_trailer(field: String) -> Bool {
case field {
"server-timing" | "content-digest" | "repr-digest" -> True
_ -> False
}
}The patched logic restricts trailer merging to exactly three low-risk headers. These headers are exclusively used for diagnostic timing or payload integrity verification. By explicitly dropping all other trailer fields, the parser successfully isolates the primary request context from any trailing data, neutralizing the injection vector.
Exploiting this vulnerability requires sending a carefully structured HTTP request using chunked transfer encoding. The attacker must first declare their intention to send a specific trailer header by including the Trailer header in the initial request block. The HTTP specification mandates this declaration, and many parsers require it to correctly process trailing fields.
The attacker constructs the payload by dividing the request body into chunks. The sensitive header target, such as Authorization, is transmitted after the zero-length chunk that signals the end of the HTTP body. The network transmission proceeds in a single TCP stream, but the parser processes the payload sequentially.
POST /api/admin-action HTTP/1.1
Host: target.example.com
Transfer-Encoding: chunked
Trailer: Authorization
Content-Type: application/json
11
{"action":"drop"}
0
Authorization: Bearer admin_token_here
> [!NOTE] > The trailing line breaks after the injected header are critical. The server must recognize the end of the trailer section to complete the parsing operation and forward the request to the application layer.
When the ewe server parses this transmission, it initially sees no Authorization header or a legitimate, low-privileged one. As it consumes the chunked body, it reaches the 0 chunk and begins parsing the trailers. The parser encounters the Authorization header, passes it through the incomplete denylist, and calls set_header. The application logic subsequently evaluates the attacker-supplied admin_token_here as the definitive authentication context.
The impact of this vulnerability is inherently contextual, dictated by the security posture of the downstream application. However, because the vulnerability allows arbitrary manipulation of primary request headers, the theoretical impact floor is high. Applications that rely on header values for critical state management are severely compromised.
The most prominent risk is authentication bypass. If an application uses bearer tokens or session IDs passed via headers, an attacker can supply an unauthenticated request initially, satisfying edge-level checks, and then inject a privileged token in the trailer. This effectively subverts identity verification controls operating before the full request body is parsed.
Another significant risk vector involves IP spoofing and rate-limit evasion. Load balancers and reverse proxies typically append metadata headers like X-Forwarded-For or X-Real-IP to identify the originating client. By injecting these headers in the trailer section, an attacker can overwrite the values provided by the proxy infrastructure. This allows the attacker to mimic internal network traffic or rotate apparent IP addresses to bypass volumetric defenses.
The severity is amplified by the fact that this exploitation occurs pre-routing. The malicious modifications happen entirely within the internal ewe HTTP parser before any application-level middleware or business logic is executed. Developers auditing their application code would see no obvious flaws, as the injected headers appear completely indistinguishable from legitimate client input.
The definitive remediation for this vulnerability is upgrading the ewe library to version 3.0.5 or later. The patch completely removes the vulnerable denylist implementation and substitutes a strict allowlist. This positive security model prevents any arbitrary header from being processed in the trailer section, fundamentally closing the attack vector.
For systems where immediate patching is not technically feasible, operators must implement compensating controls at the network edge. Reverse proxies or Web Application Firewalls (WAFs) can be configured to strip the Trailer header from incoming requests. Alternatively, edge infrastructure can be configured to reject chunked HTTP requests entirely if the application API does not require streaming data ingest.
Developers using ewe should systematically review their dependency trees to ensure the vulnerable package version is not transitively included. Verification involves checking the gleam.toml manifest file and confirming that the resolution lockfile enforces the fixed version. Post-upgrade, no application code modifications are required, as the fix operates transparently within the library's internal parser.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
ewe vshakitskiy | < 3.0.5 | 3.0.5 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-184 |
| Attack Vector | Network |
| CVSS v3.1 Score | 6.5 |
| Impact | Header Injection / Authentication Bypass |
| Exploit Status | poc |
| CISA KEV | False |
The product relies on a list of inputs that are not allowed, but the list is incomplete, allowing an attacker to bypass intended restrictions.