# Http4s Ember Transfer-Encoding value parsing (TE.CL / TE.0 request smuggling)

Ember's case-sensitive `contains("chunked")` check on Transfer-Encoding lets `Chunked` fall back to Content-Length framing, enabling TE.CL/TE.0 request smuggling behind keep-alive intermediaries.

- Payout: Research
- Severity: high
- Program: http4s Ember
- Found by: ERobertGII, rossabaker
- Disclosed: 2026-09-15
- CVE: CVE-2026-69205
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-9998-894r-fwvr
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-9998-894r-fwvr
- Tags: area:web domain:web impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:java technique:parser-differential technique:unicode-normalization vuln:request-smuggling

**Target:** HTTP/1.1 header parser in ember-server and ember-client (HeaderP.parse)
**Root cause:** Transfer-Encoding value is matched with a case-sensitive substring test `hValue.contains("chunked")` instead of RFC 9112 case-insensitive token comparison, so `Chunked` is not recognized and Ember frames by Content-Length (or zero) while a compliant intermediary frames by chunked encoding.
**Steps:**
1. Deploy ember-server as origin behind a keep-alive intermediary that honors Transfer-Encoding case-insensitively and forwards the value verbatim.
2. Send a request with `Transfer-Encoding: Chunked` (capital C) plus a Content-Length header, with a chunked body containing a smuggled second request.
3. Intermediary frames the body as chunked and forwards bytes; Ember does not recognize `Chunked`, uses Content-Length/zero, leaving the smuggled prefix in the connection buffer.
4. Smuggled request is then prepended to the next victim's request on the pooled backend connection.
5. Variant: `Transfer-Encoding: notchunked` inverts the desync (Ember treats as chunked, intermediary rejects).
6. Variant: send `Transfer-Encoding: chun<U+212A>ed` (wire bytes `E2 84 AA` for KELVIN SIGN) which Unicode-case-folds to `k` under UTF-8 decoding.
**How found:** Code review of the header parser: noticed `hValue.contains("chunked")` is a case-sensitive substring test, then reasoned about RFC 9112 case-insensitivity, substring false positives (`notchunked`), and charset-dependent Unicode case folding (KELVIN SIGN).
**Exploitation:** Attacker sends a crafted request through the intermediary; the smuggled prefix poisons the backend keep-alive connection, allowing front-end ACL/auth bypass with attacker-chosen method and headers, hijacking the next victim's request to leak `Cookie`/`Authorization`, or cache poisoning of the smuggled response against the next request key.
**Escalation:** Chained from a single framing mismatch to cross-user credential theft and cache poisoning via keep-alive connection reuse.
**Insight:** When auditing HTTP parsers, check not just case-insensitivity of Transfer-Encoding but also substring vs token matching and the charset used to decode header bytes — Unicode case-folding (e.g. U+212A KELVIN SIGN) can smuggle a `chunked` match past a naive equalsIgnoreCase fix.

