Skip to content

fix: preserve X-Forwarded-For from untrusted sources#13611

Open
shreemaan-abhishek wants to merge 1 commit into
apache:masterfrom
shreemaan-abhishek:fix/xff-preserve-untrusted
Open

fix: preserve X-Forwarded-For from untrusted sources#13611
shreemaan-abhishek wants to merge 1 commit into
apache:masterfrom
shreemaan-abhishek:fix/xff-preserve-untrusted

Conversation

@shreemaan-abhishek

Copy link
Copy Markdown
Contributor

Description

When the downstream connection is not listed in apisix.trusted_addresses, handle_x_forwarded_headers() cleared the incoming X-Forwarded-For header outright. For deployments with a multi-tier proxy chain (e.g. client -> front proxy -> APISIX -> upstream) where the front proxy is not added to trusted_addresses, the upstream loses the original client IP and only sees the connection IP rebuilt by $proxy_add_x_forwarded_for. This is a behavior change from older releases and breaks upstreams that read the client IP from X-Forwarded-For.

Unlike X-Forwarded-Proto/Host/Port and the RFC 7239 Forwarded header (which are set wholesale and have no append mechanism, so a forged value would pass straight through), X-Forwarded-For has a safe append model: ngx_tpl appends the trusted connection IP via $proxy_add_x_forwarded_for, so even a client-forged chain always carries the real connection IP as its last hop. Consumers that want the real client IP should anchor on the trusted rightmost hop (this is what the real-ip plugin does).

This PR keeps X-Forwarded-For intact for untrusted sources and continues to overwrite X-Forwarded-Proto/Host/Port and clear Forwarded. This aligns with the default behavior of Kong, Tyk and Envoy edge mode, which preserve the incoming XFF and append the connection IP.

Behavior

For an untrusted source sending X-Forwarded-For: 1.2.3.4:

  • Before: upstream receives X-Forwarded-For: <connection-ip>
  • After: upstream receives X-Forwarded-For: 1.2.3.4, <connection-ip>

X-Forwarded-Proto/Host/Port and Forwarded handling is unchanged.

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)

@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jun 26, 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.

Pull request overview

This PR adjusts APISIX’s handling of forwarded headers for downstream connections that are not in apisix.trusted_addresses, preserving the incoming X-Forwarded-For header so upstreams can still receive the original chain (with the downstream connection IP appended by Nginx).

Changes:

  • Stop clearing X-Forwarded-For for untrusted sources while continuing to override X-Forwarded-Proto/Host/Port and clear Forwarded.
  • Update core tests to assert X-Forwarded-For preservation + append behavior in untrusted scenarios.
  • Update the example configuration comments to document the revised behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
apisix/init.lua Keeps X-Forwarded-For intact for untrusted sources while still sanitizing other forwarded headers.
t/core/trusted-addresses.t Updates expectations to verify preserved incoming XFF plus appended downstream connection IP.
t/plugin/real-ip.t Adjusts behavior/expectations so the real-ip plugin can still honor XFF when global trusted addresses don’t trust the downstream source.
conf/config.yaml.example Documents the updated trusted/untrusted forwarded-header behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread conf/config.yaml.example
Comment on lines +159 to +161
# values and clears the `Forwarded` header. `X-Forwarded-For` is kept
# and the trusted connection IP is appended, so the upstream still
# receives the real client IP as the last hop of the chain.
Comment thread apisix/init.lua
Comment on lines +650 to +652
-- X-Forwarded-For is kept as-is: ngx_tpl appends the trusted
-- connection IP via `$proxy_add_x_forwarded_for`, so the upstream
-- always sees the real client IP as the last hop of the chain.
When the connection is not in `apisix.trusted_addresses`, APISIX cleared
the incoming X-Forwarded-For entirely, so upstreams behind a multi-tier
proxy chain (client -> proxy A -> APISIX -> upstream) lost the real client
IP after upgrading.

X-Forwarded-For has a safe append model: ngx_tpl appends the trusted
connection IP via $proxy_add_x_forwarded_for, so even a forged chain
always carries the real client IP as its last hop. Keep it intact and only
overwrite the headers that have no append mechanism (X-Forwarded-Proto /
Host / Port) and clear the RFC 7239 Forwarded header. This matches the
default behavior of Kong, Tyk and Envoy edge mode.
@shreemaan-abhishek shreemaan-abhishek force-pushed the fix/xff-preserve-untrusted branch from 98c0647 to 9193ec5 Compare June 26, 2026 12:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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