VYPR
High severityNVD Advisory· Published Jun 2, 2026

CVE-2026-48595

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

Patches

1
db963dba6765

Merge commit from fork

https://github.com/elixir-tesla/teslaYordis PrietoJun 2, 2026via body-scan
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

4

News mentions

0

No linked articles in our index yet.