VYPR
Unrated severityNVD Advisory· Published May 25, 2026

HTTP/3 redirect handler leaks Authorization and Cookie headers to cross-origin redirect target in hackney

CVE-2026-47070

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.

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

2
  • Benoitc/Hackneyinferred2 versions
    >=3.1.1,<4.0.1+ 1 more
    • (no CPE)range: >=3.1.1,<4.0.1
    • (no CPE)range: >=3.1.1 <4.0.1

Patches

1
c58d5b50bade

fix(security): strip credentials on cross-origin H3 redirect (GHSA-h73q)

https://github.com/benoitc/hackneyBenoit ChesneauMay 20, 2026via body-scan
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

News mentions

0

No linked articles in our index yet.