CVE-2026-48595
Description
Tesla's FollowRedirects middleware leaks credentials via case-sensitive header filtering on cross-origin redirects.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Tesla's FollowRedirects middleware leaks credentials via case-sensitive header filtering on cross-origin redirects.
Vulnerability
Improper handling of case sensitivity in Tesla.Middleware.FollowRedirects allows credential leakage on cross-origin redirects. The middleware uses a case-sensitive string comparison against a lowercase filter list (@filter_headers ["authorization", "host"]). Since HTTP header names are case-insensitive per RFC 7230, but Tesla preserves header keys verbatim, a header set with canonical casing (e.g., {"Authorization", "Bearer …"}) does not match the lowercase filter and is forwarded to the redirect destination. This affects tesla versions from 1.4.0 before 1.18.3 [2].
Exploitation
An attacker can exploit this vulnerability by controlling or influencing a Location: response seen by the client. The attacker needs to set up an endpoint that returns a redirect to a different origin. When the tesla client follows this redirect, if the Authorization header was sent with canonical casing (e.g., Authorization), it will be included in the request to the attacker-controlled origin. No special configuration is required beyond using the standard header casing [4].
Impact
Successful exploitation leads to the leakage of sensitive credentials, such as bearer tokens, to a third-party origin. This can result in unauthorized access to resources or services. The vulnerability has a high severity rating (CVSS v4.0: 8.2) [3]. Any application using tesla versions 1.4.0 through 1.18.2 with Tesla.Middleware.FollowRedirects and a non-lowercase Authorization header is affected [4].
Mitigation
The vulnerability is fixed in tesla version 1.18.3 [1]. A workaround is to normalize all header keys to lowercase before passing them to tesla, specifically using "authorization" as the header key [4].
AI Insight generated on Jun 2, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1- Range: >=1.4.0 <1.18.3
Patches
1db963dba6765Merge commit from fork
2 files changed · +241 −10
lib/tesla/middleware/follow_redirects.ex+63 −10 modified@@ -62,7 +62,7 @@ defmodule Tesla.Middleware.FollowRedirects do env = %{env | opts: res.opts} env - |> filter_headers(prev_uri, next_uri) + |> filter_headers(prev_uri, next_uri, status) |> new_request(status, URI.to_string(next_uri)) |> redirect(next, left - 1) end @@ -90,14 +90,67 @@ defmodule Tesla.Middleware.FollowRedirects do defp parse_location("http://" <> _rest = location, _env), do: URI.parse(location) defp parse_location(location, env), do: env.url |> URI.parse() |> URI.merge(location) - # See https://github.com/teamon/tesla/issues/362 - # See https://github.com/teamon/tesla/issues/360 - @filter_headers ["authorization", "host"] - defp filter_headers(env, prev, next) do - if next.host != prev.host || next.port != prev.port || next.scheme != prev.scheme do - %{env | headers: Enum.filter(env.headers, fn {k, _} -> k not in @filter_headers end)} - else - env - end + # Header filtering on redirect, per RFC 9110 §15.4. + # https://www.rfc-editor.org/rfc/rfc9110.html#section-15.4-5 + + # Hop-by-hop and cache-validator headers do not carry over to a new request. + @always_strip ~w( + connection + keep-alive + proxy-connection + te + trailer + transfer-encoding + upgrade + if-match + if-modified-since + if-none-match + if-range + if-unmodified-since + ) + + # Resource-, origin-, and proxy-specific headers are stripped on cross-origin + # redirects to avoid leaking credentials or sending values bound to the + # previous origin. + @cross_origin_strip ~w( + authorization + cookie + host + origin + proxy-authorization + referer + ) + + # Representation metadata describes the original request body and is no + # longer applicable when the redirect changes the method (303 -> GET). + @method_change_strip ~w( + content-encoding + content-language + content-length + content-location + content-type + digest + last-modified + ) + + defp filter_headers(env, prev, next, status) do + drop = + @always_strip + |> add_if(cross_origin?(prev, next), @cross_origin_strip) + |> add_if(method_changes?(status), @method_change_strip) + + %{env | headers: Enum.reject(env.headers, &dropped?(&1, drop))} + end + + defp dropped?({key, _value}, drop), do: String.downcase(key) in drop + + defp add_if(list, true, extra), do: list ++ extra + defp add_if(list, false, _extra), do: list + + defp cross_origin?(prev, next) do + next.host != prev.host || next.port != prev.port || next.scheme != prev.scheme end + + defp method_changes?(303), do: true + defp method_changes?(_), do: false end
test/tesla/middleware/follow_redirects_test.exs+178 −0 modified@@ -205,6 +205,12 @@ defmodule Tesla.Middleware.FollowRedirectsTest do {:ok, %{env | status: 301, headers: [{"location", "http://example.net/next"}], body: ""}} + %{url: "http://example.com/see-other-same", headers: headers} = env -> + send(self(), headers) + + {:ok, + %{env | status: 303, headers: [{"location", "http://example.com/next"}], body: ""}} + %{url: "http://example.com/next", headers: headers} = env -> send(self(), headers) {:ok, %{env | status: 200, headers: [], body: "ok com"}} @@ -297,5 +303,177 @@ defmodule Tesla.Middleware.FollowRedirectsTest do # Next request does not receive host header assert_receive [] end + + test "Strip Authorization header (canonical casing) on redirect to a different domain", %{ + client: client + } do + assert {:ok, _env} = + Tesla.post(client, "http://example.com/drop", "", + headers: [ + {"content-type", "text/plain"}, + {"Authorization", "Basic Zm9vOmJhcg=="} + ] + ) + + assert_receive [ + {"content-type", "text/plain"}, + {"Authorization", "Basic Zm9vOmJhcg=="} + ] + + assert_receive [ + {"content-type", "text/plain"} + ] + end + + test "Strip Host header (canonical casing) on redirect to a different domain", %{ + client: client + } do + assert {:ok, _env} = + Tesla.post(client, "http://example.com/drop", "", + headers: [ + {"Host", "example.xyz"} + ] + ) + + assert_receive [ + {"Host", "example.xyz"} + ] + + assert_receive [] + end + + test "Strip resource- and origin-specific headers on cross-origin redirect", %{ + client: client + } do + assert {:ok, _env} = + Tesla.post(client, "http://example.com/drop", "", + headers: [ + {"Authorization", "Bearer token"}, + {"Cookie", "session=abc"}, + {"Proxy-Authorization", "Basic xyz"}, + {"Referer", "http://example.com/start"}, + {"Origin", "http://example.com"}, + {"x-custom", "keep"} + ] + ) + + assert_receive [ + {"Authorization", "Bearer token"}, + {"Cookie", "session=abc"}, + {"Proxy-Authorization", "Basic xyz"}, + {"Referer", "http://example.com/start"}, + {"Origin", "http://example.com"}, + {"x-custom", "keep"} + ] + + assert_receive [{"x-custom", "keep"}] + end + + test "Keep resource-specific headers on same-origin redirect", %{client: client} do + assert {:ok, _env} = + Tesla.post(client, "http://example.com/keep", "", + headers: [ + {"Cookie", "session=abc"}, + {"Referer", "http://example.com/start"} + ] + ) + + assert_receive [ + {"Cookie", "session=abc"}, + {"Referer", "http://example.com/start"} + ] + + assert_receive [ + {"Cookie", "session=abc"}, + {"Referer", "http://example.com/start"} + ] + end + + test "Strip hop-by-hop headers on any redirect", %{client: client} do + assert {:ok, _env} = + Tesla.post(client, "http://example.com/keep", "", + headers: [ + {"Connection", "close"}, + {"Keep-Alive", "timeout=5"}, + {"TE", "trailers"}, + {"x-custom", "keep"} + ] + ) + + assert_receive [ + {"Connection", "close"}, + {"Keep-Alive", "timeout=5"}, + {"TE", "trailers"}, + {"x-custom", "keep"} + ] + + assert_receive [{"x-custom", "keep"}] + end + + test "Strip cache validator headers on any redirect", %{client: client} do + assert {:ok, _env} = + Tesla.post(client, "http://example.com/keep", "", + headers: [ + {"If-None-Match", "\"etag\""}, + {"If-Modified-Since", "Tue, 01 Jan 2030 00:00:00 GMT"}, + {"If-Match", "\"etag\""}, + {"If-Unmodified-Since", "Tue, 01 Jan 2030 00:00:00 GMT"}, + {"If-Range", "\"etag\""}, + {"x-custom", "keep"} + ] + ) + + assert_receive [ + {"If-None-Match", "\"etag\""}, + {"If-Modified-Since", "Tue, 01 Jan 2030 00:00:00 GMT"}, + {"If-Match", "\"etag\""}, + {"If-Unmodified-Since", "Tue, 01 Jan 2030 00:00:00 GMT"}, + {"If-Range", "\"etag\""}, + {"x-custom", "keep"} + ] + + assert_receive [{"x-custom", "keep"}] + end + + test "Strip representation metadata when 303 changes method to GET", %{client: client} do + assert {:ok, _env} = + Tesla.post(client, "http://example.com/see-other-same", "body", + headers: [ + {"Content-Type", "application/json"}, + {"Content-Length", "4"}, + {"Content-Encoding", "gzip"}, + {"Content-Language", "en"}, + {"Content-Location", "/orig"}, + {"Digest", "sha-256=abc"}, + {"Last-Modified", "Tue, 01 Jan 2030 00:00:00 GMT"}, + {"x-custom", "keep"} + ] + ) + + assert_receive [ + {"Content-Type", "application/json"}, + {"Content-Length", "4"}, + {"Content-Encoding", "gzip"}, + {"Content-Language", "en"}, + {"Content-Location", "/orig"}, + {"Digest", "sha-256=abc"}, + {"Last-Modified", "Tue, 01 Jan 2030 00:00:00 GMT"}, + {"x-custom", "keep"} + ] + + assert_receive [{"x-custom", "keep"}] + end + + test "Keep representation metadata when method is preserved (301)", %{client: client} do + assert {:ok, _env} = + Tesla.post(client, "http://example.com/keep", "body", + headers: [ + {"Content-Type", "application/json"} + ] + ) + + assert_receive [{"Content-Type", "application/json"}] + assert_receive [{"Content-Type", "application/json"}] + end end end
Vulnerability mechanics
Root cause
"The middleware incorrectly performed case-sensitive comparisons when filtering security-sensitive headers during cross-origin redirects."
Attack vector
An attacker can control or influence a Location header in a redirect response. If the victim client follows this redirect to a different origin, and the original request included security-sensitive headers like 'Authorization' or 'Host' with canonical casing (e.g., 'Authorization'), these headers will be forwarded to the attacker-controlled destination. This occurs because the middleware's filter list uses lowercase strings and compares them case-sensitively against the verbatim header keys [ref_id=2].
Affected code
The vulnerability resides in the `Tesla.Middleware.FollowRedirects` module, specifically within the `filter_headers` function and the associated `@filter_headers` list [ref_id=2]. The patch modifies `lib/tesla/middleware/follow_redirects.ex` to implement case-insensitive filtering and a more comprehensive header stripping strategy [patch_id=4524235].
What the fix does
The patch modifies the `filter_headers` function to perform case-insensitive comparisons when checking headers against the filter list [patch_id=4524235]. It also expands the list of headers to be stripped on redirects, including hop-by-hop, cache-validator, and representation metadata headers when appropriate, aligning with RFC 9110 [patch_id=4524235]. This ensures that security-sensitive headers are correctly identified and stripped regardless of their casing, preventing credential leakage.
Preconditions
- configThe Tesla client must be configured with `Tesla.Middleware.FollowRedirects`.
- inputThe original request must include security-sensitive headers like 'Authorization' or 'Host' with canonical casing (e.g., 'Authorization').
- networkThe client must receive a redirect response (e.g., 301, 302, 303) with a Location header pointing to a different origin.
Reproduction
1. Configure a Tesla client with `Tesla.Middleware.FollowRedirects`. 2. Set the `Authorization` header using canonical casing (e.g., `{"Authorization", "Bearer <token>"}`). 3. Make a request to an endpoint that returns a redirect (e.g., 302) to a different origin. 4. Observe that the `Authorization` header with its value is present in the request delivered to the redirect destination [ref_id=2].
Generated on Jun 2, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.