HTTP/3 redirect handler leaks Authorization and Cookie headers to cross-origin redirect target in hackney
Description
Sensitive Data Exposure vulnerability in benoitc hackney allows Retrieve Embedded Sensitive Data. The HTTP/3 redirect handler in src/hackney_h3.erl passes the original request headers unchanged to the redirect target without performing any cross-origin check. When a client issues an HTTP/3 request with follow_redirect enabled and includes Authorization or Cookie headers, a server responding with a 3xx redirect to a different host will cause the client to forward those credentials verbatim to the new origin.
The main hackney.erl module has maybe_strip_auth_on_redirect/2 (guarded by the location_trusted option) to address CVE-2018-1000007, but hackney_h3.erl is missing this protection entirely.
This issue affects hackney: from 3.1.1 before 4.0.1.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Hackney HTTP/3 redirect handler forwards Authorization and Cookie headers to cross-origin targets, leaking credentials to untrusted hosts.
Vulnerability
In the Erlang HTTP client hackney, the HTTP/3 redirect handler in src/hackney_h3.erl passes the original request headers unchanged to the redirect target without performing any cross-origin check [1][2]. When a client issues an HTTP/3 request with follow_redirect enabled and includes Authorization or Cookie headers, a server responding with a 3xx redirect to a different host will cause the client to forward those credentials verbatim to the new origin [3][4]. The main hackney.erl module uses maybe_strip_auth_on_redirect/2 (guarded by the location_trusted option) to address CVE-2018-1000007, but hackney_h3.erl is missing this protection entirely [1][2]. The issue affects hackney from version 3.1.1 before 4.0.1 [1][2].
Exploitation
An attacker needs to control or compromise an HTTP/3 server that the hackney client connects to with follow_redirect enabled [4]. The attacker's server responds to an original request (which may include Authorization, Cookie, or Proxy-Authorization headers) with a 3xx redirect status code and a Location header pointing to a different origin (scheme, host, or port) [1][4]. For 307 or 308 redirect responses, the original request body, including any POST data, is also forwarded [4]. The hackney client then opens a new QUIC connection to the attacker-specified host and re-sends the original headers and body verbatim, without stripping credentials [3][4]. No authentication or user interaction beyond the initial client request is needed, and the attacker can be remote with network access to trigger the redirect.
Impact
A successful exploit results in the disclosure of sensitive credentials (e.g., bearer tokens, session cookies, proxy credentials) and potentially the request body to an attacker-controlled origin [1][4]. The attacker gains the ability to retrieve embedded sensitive data (CAPEC-37) [1]. The confidentiality of the HTTP/3 client's requests is compromised, potentially leading to account takeover or unauthorized access to protected resources [4]. The CVSS v4.0 score is 6.0 (MEDIUM) with high confidentiality impact [1][2].
Mitigation
The fix was introduced in commit c58d5b50bade146360b85caf3dc8065807b08246 and is included in version 4.0.1 of hackney [3]. Users should upgrade to 4.0.1 or later. The patch adds a maybe_strip_redirect_headers/4 function in hackney_h3.erl that drops credential headers (authorization, cookie, proxy-authorization) before following a cross-origin redirect unless the location_trusted option is explicitly set to true [3][4]. If upgrading is not immediately possible, users can avoid using the HTTP/3 client with follow_redirect enabled or manually strip sensitive headers from requests to untrusted origins. No other workarounds are documented in the available references.
- HTTP/3 redirect handler leaks Authorization and Cookie headers to cross-origin redirect target in hackney
- OSV - Open Source Vulnerabilities
- fix(security): strip credentials on cross-origin H3 redirect (GHSA-h73q) · benoitc/hackney@c58d5b5
- Cross-origin redirect leaks Authorization, Cookie, and request body
AI Insight generated on May 25, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
1c58d5b50badefix(security): strip credentials on cross-origin H3 redirect (GHSA-h73q)
2 files changed · +88 −1
src/hackney_h3.erl+40 −1 modified@@ -64,6 +64,10 @@ %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). +-ifdef(TEST). +-export([maybe_strip_redirect_headers/4]). +-endif. + -record(state, { h3_conn :: pid() | undefined, conn_ref :: reference(), @@ -174,8 +178,11 @@ handle_redirect(Status, RespHeaders, _RespBody, Method, Url, Headers, Body, Opts get when Method =/= get -> <<>>; _ -> Body end, + %% GHSA-h73q: do not forward credential headers to a different + %% origin unless the caller opted into location_trusted. + NewHeaders = maybe_strip_redirect_headers(Url, NewUrl, Headers, Opts), %% Follow the redirect - do_request_with_redirect(NewMethod, NewUrl, Headers, NewBody, Opts, + do_request_with_redirect(NewMethod, NewUrl, NewHeaders, NewBody, Opts, true, MaxRedirect, RedirectCount + 1); {error, no_location} -> %% No Location header, return as-is @@ -194,6 +201,38 @@ get_redirect_location(Headers) -> end end. +%% @private GHSA-h73q: strip Authorization / Cookie / Proxy-Authorization +%% before following a redirect to a different origin (scheme, host or port). +%% location_trusted lets a caller opt back into forwarding them. +maybe_strip_redirect_headers(OldUrl, NewUrl, Headers, Opts) -> + case maps:get(location_trusted, Opts, false) of + true -> Headers; + false -> + case same_origin(OldUrl, NewUrl) of + true -> Headers; + false -> drop_credential_headers(Headers) + end + end. + +same_origin(UrlA, UrlB) -> + #hackney_url{scheme = SchemeA, host = HostA, port = PortA} = + hackney_url:parse_url(UrlA), + #hackney_url{scheme = SchemeB, host = HostB, port = PortB} = + hackney_url:parse_url(UrlB), + {SchemeA, string:to_lower(HostA), PortA} =:= + {SchemeB, string:to_lower(HostB), PortB}. + +drop_credential_headers(Headers) -> + Sensitive = [<<"authorization">>, <<"cookie">>, <<"proxy-authorization">>], + lists:filter( + fun({Name, _Value}) -> + not lists:member(hackney_bstr:to_lower(to_bin(Name)), Sensitive) + end, Headers). + +to_bin(B) when is_binary(B) -> B; +to_bin(L) when is_list(L) -> list_to_binary(L); +to_bin(A) when is_atom(A) -> atom_to_binary(A, utf8). + %% @private Resolve redirect URL relative to original URL resolve_redirect_url(Location, OriginalUrl) when is_binary(Location) -> case Location of
test/hackney_h3_redirect_tests.erl+48 −0 modified@@ -190,6 +190,54 @@ test_method_preservation() -> ?assertEqual(<<"GET">>, redirect_method(Status, <<"GET">>)) end, [307, 308]). +%%==================================================================== +%% GHSA-h73q: cross-origin credential stripping +%%==================================================================== + +cross_origin_header_strip_test_() -> + [ + {"strips Authorization/Cookie/Proxy-Authorization across origin", + fun() -> + Headers = [{<<"authorization">>, <<"Bearer secret">>}, + {<<"cookie">>, <<"session=abc">>}, + {<<"proxy-authorization">>, <<"Basic xyz">>}, + {<<"user-agent">>, <<"hackney">>}], + Result = hackney_h3:maybe_strip_redirect_headers( + <<"https://victim.example/me">>, + <<"https://attacker.example/collect">>, + Headers, #{}), + ?assertEqual([{<<"user-agent">>, <<"hackney">>}], Result) + end}, + {"keeps headers on same-origin redirect", + fun() -> + Headers = [{<<"authorization">>, <<"Bearer secret">>}, + {<<"user-agent">>, <<"hackney">>}], + Result = hackney_h3:maybe_strip_redirect_headers( + <<"https://victim.example/me">>, + <<"https://victim.example/other">>, + Headers, #{}), + ?assertEqual(Headers, Result) + end}, + {"strips on port change", + fun() -> + Headers = [{<<"cookie">>, <<"s=1">>}], + Result = hackney_h3:maybe_strip_redirect_headers( + <<"https://victim.example:443/me">>, + <<"https://victim.example:8443/me">>, + Headers, #{}), + ?assertEqual([], Result) + end}, + {"location_trusted keeps credentials cross-origin", + fun() -> + Headers = [{<<"authorization">>, <<"Bearer secret">>}], + Result = hackney_h3:maybe_strip_redirect_headers( + <<"https://victim.example/me">>, + <<"https://attacker.example/collect">>, + Headers, #{location_trusted => true}), + ?assertEqual(Headers, Result) + end} + ]. + %%==================================================================== %% TLS Option Tests %%====================================================================
Vulnerability mechanics
Root cause
"Missing cross-origin check in the HTTP/3 redirect handler allows forwarding credential headers to an attacker-controlled redirect target."
Attack vector
An attacker controls an upstream server (malicious, compromised, or reachable via DNS/MITM) that the hackney HTTP/3 client contacts with `follow_redirect` enabled and sensitive headers (Authorization, Cookie, Proxy-Authorization) set [ref_id=2]. The attacker's server responds with a 3xx redirect (including 307/308 which also preserve the request body) with a `Location` header pointing to an attacker-controlled host [ref_id=2]. The client opens a new QUIC connection to that host and forwards the original credential headers and, for 307/308, the request body verbatim, exfiltrating bearer tokens, session cookies, and POST body data [ref_id=2].
Affected code
The vulnerable code is in `src/hackney_h3.erl`, specifically the `handle_redirect/11` function (line 165) which forwards the original `Headers` list unchanged to `do_request_with_redirect/8` without comparing the original URL's scheme, host, or port to the redirect target [ref_id=2]. The HTTP/3 client was added later and never received the `maybe_strip_auth_on_redirect/2` protection that exists in the main `hackney.erl` module for HTTP/1.1 [ref_id=2].
What the fix does
The patch adds `maybe_strip_redirect_headers/4` in `src/hackney_h3.erl` which compares the original URL and redirect target using `same_origin/2` (checking scheme, host, and port) [patch_id=2473701]. If the origins differ and the caller has not set `location_trusted`, the function calls `drop_credential_headers/1` to filter out `Authorization`, `Cookie`, and `Proxy-Authorization` headers [patch_id=2473701]. This matches the existing HTTP/1.1 `maybe_strip_auth_on_redirect` behaviour that was the fix for CVE-2018-1000007 [ref_id=2].
Preconditions
- configClient must use the HTTP/3 client (hackney_h3.erl)
- configClient must set follow_redirect option to true
- inputClient must include sensitive headers (Authorization, Cookie, Proxy-Authorization) in the request
- networkUpstream server must respond with a 3xx redirect to a different origin
Reproduction
Issue an HTTP/3 POST to an attacker-controlled origin with `follow_redirect => true` and an `Authorization: Bearer ...` header. The attacker's server responds `307 Location: https://other.host/collect`. hackney opens a new connection to `other.host` and re-sends the original headers and body, including the bearer token and any Cookie headers [ref_id=2].
Generated on May 25, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/benoitc/hackney/commit/c58d5b50bade146360b85caf3dc8065807b08246mitrepatch
- github.com/benoitc/hackney/security/advisories/GHSA-h73q-4w9q-82h4mitrevendor-advisoryrelated
- cna.erlef.org/cves/CVE-2026-47070.htmlmitrerelated
- osv.dev/vulnerability/EEF-CVE-2026-47070mitrerelated
News mentions
0No linked articles in our index yet.