Skip to content

fix(redirect): compare X-Forwarded-Proto case-insensitively - #13865

Open
nic-6443 wants to merge 1 commit into
apache:masterfrom
nic-6443:fix/redirect-xfp-case-insensitive
Open

fix(redirect): compare X-Forwarded-Proto case-insensitively#13865
nic-6443 wants to merge 1 commit into
apache:masterfrom
nic-6443:fix/redirect-xfp-case-insensitive

Conversation

@nic-6443

@nic-6443 nic-6443 commented Aug 21, 2026

Copy link
Copy Markdown
Member

Description

redirect's http_to_https compares X-Forwarded-Proto against the lowercase literal https, so a proxy that forwards HTTPS gets redirected as if the request had arrived over plaintext HTTP.

That comparison is wrong. A URI scheme is case-insensitive per RFC 3986, Section 3.1, and RFC 7239 requires the proto parameter to "conform to the URI scheme name as defined in Section 3.1 in [RFC3986]". A proxy forwarding HTTPS is stating exactly what one forwarding https states.

Behind a TLS-terminating proxy that does not lowercase the value, this turns into a redirect loop: the client follows the 301 back over HTTPS, the proxy terminates TLS and forwards over HTTP again, and the plugin redirects again. That is the same loop #6242 originally fixed, reintroduced for anyone whose proxy happens to send a different case.

The fix lowercases the value before the comparison. core.request.header() already collapses a duplicated header down to its first value, so there is no table to guard against here.

I also documented how the scheme is determined, including the fact that an inbound X-Forwarded-Proto is only preserved for peers listed in apisix.trusted_addresses — the redirect docs did not mention the header at all, which makes http_to_https behind a load balancer hard to reason about.

Deliberately out of scope: a multi-valued X-Forwarded-Proto such as https, http still does not match. X-Forwarded-Proto is not part of any specification (RFC 7239's Forwarded is the standardized form, and there the chain is expressed as multiple forwarded-elements, each with its own proto), so there is no normative basis for deciding which element of a list is authoritative. That deserves its own discussion rather than riding along with a comparison fix.

Which issue(s) this PR fixes:

N/A

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible (If not, please discuss on the APISIX mailing list first)

A URI scheme is case-insensitive per RFC 3986, Section 3.1, and RFC 7239
requires the `proto` parameter to conform to that definition. A proxy
forwarding `X-Forwarded-Proto: HTTPS` is therefore stating exactly what one
forwarding `https` states, but `http_to_https` compared the raw value against
the lowercase literal and redirected the request anyway.

Behind a TLS-terminating proxy that does not lowercase the value this becomes
a redirect loop: the client follows the 301 back over HTTPS, the proxy
forwards over HTTP again, and the plugin redirects again.

Also document how the scheme is determined, including the fact that an
inbound X-Forwarded-Proto is only preserved for peers in
`apisix.trusted_addresses` -- the plugin docs did not mention the header at
all.
Copilot AI lite review requested due to automatic review settings August 21, 2026 09:34
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Aug 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dosubot dosubot Bot added the bug Something isn't working label Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants