This is an automated email from the ASF dual-hosted git repository.

shreemaan-abhishek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 0e70e6919 fix: reset X-Forwarded-For only for untrusted sources under 
trusted_addresses (#13611)
0e70e6919 is described below

commit 0e70e691947f35594117f63a3e2559f63f69fcc2
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Wed Jul 1 18:54:39 2026 +0800

    fix: reset X-Forwarded-For only for untrusted sources under 
trusted_addresses (#13611)
---
 apisix/init.lua                       | 22 ++++++---
 apisix/utils/trusted-addresses.lua    |  6 +++
 conf/config.yaml.example              |  9 +++-
 docs/en/latest/plugins/chaitin-waf.md |  2 +-
 docs/en/latest/plugins/real-ip.md     |  2 +-
 docs/zh/latest/plugins/chaitin-waf.md |  2 +-
 docs/zh/latest/plugins/real-ip.md     |  2 +-
 t/core/trusted-addresses.t            | 88 +++++++++++++++++++++++++++++++++--
 t/plugin/real-ip.t                    |  3 +-
 9 files changed, 118 insertions(+), 18 deletions(-)

diff --git a/apisix/init.lua b/apisix/init.lua
index 32c9dfa08..51ae3084e 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -660,7 +660,8 @@ local function handle_x_forwarded_headers(api_ctx)
         -- making them highly credible.
         local proto = api_ctx.var.scheme
         local http_host = api_ctx.var.http_host or api_ctx.var.host
-        local _, port_from_host = http_host:match("^(.+):(%d+)$")
+        -- parse_addr handles IPv6 literals and bracketed host:port correctly.
+        local _, port_from_host = core.utils.parse_addr(http_host)
         local host = http_host
         local port = port_from_host or api_ctx.var.server_port
 
@@ -670,18 +671,25 @@ local function handle_x_forwarded_headers(api_ctx)
         core.request.set_header(api_ctx, "X-Forwarded-Proto", proto)
         core.request.set_header(api_ctx, "X-Forwarded-Host", host)
         core.request.set_header(api_ctx, "X-Forwarded-Port", port)
-        -- later processed in ngx_tpl by `$proxy_add_x_forwarded_for`.
-        core.request.set_header(api_ctx, "X-Forwarded-For", nil)
         -- Clear RFC 7239 Forwarded header to prevent forgery.
         core.request.set_header(api_ctx, "Forwarded", nil)
 
+        -- X-Forwarded-For: when a trust boundary is configured but this peer 
is
+        -- untrusted, reset it so the upstream only sees the APISIX-observed
+        -- connection IP via `$proxy_add_x_forwarded_for`, dropping the 
spoofable
+        -- inbound chain. When `trusted_addresses` is unset, keep the 
compatible
+        -- default of preserving the inbound chain (the connection IP is 
appended).
+        if trusted_addresses_util.is_configured() then
+            core.request.set_header(api_ctx, "X-Forwarded-For", nil)
+            api_ctx.var.http_x_forwarded_for = nil
+        end
+
         -- update the cached value in http_x_forwarded_* to the trusted ones.
         -- make sure that the correct values ​​are obtained
         -- in the subsequent stages using `var.http_x_forwarded_*`.
         api_ctx.var.http_x_forwarded_proto = proto
         api_ctx.var.http_x_forwarded_host = host
         api_ctx.var.http_x_forwarded_port = port
-        api_ctx.var.http_x_forwarded_for = nil
         api_ctx.var.http_forwarded = nil
     end
 end
@@ -890,9 +898,11 @@ function _M.http_access_phase()
     end
     span:finish(ngx_ctx)
 
-    _M.handle_upstream(api_ctx, route, enable_websocket)
-
+    -- set before handle_upstream: grpc/dubbo/disable_proxy_buffering exit via
+    -- ngx.exec() and never return, so the trusted values must be applied 
first.
     set_upstream_x_forwarded_headers(api_ctx)
+
+    _M.handle_upstream(api_ctx, route, enable_websocket)
 end
 
 
diff --git a/apisix/utils/trusted-addresses.lua 
b/apisix/utils/trusted-addresses.lua
index df2a314a2..3e1fbe6a5 100644
--- a/apisix/utils/trusted-addresses.lua
+++ b/apisix/utils/trusted-addresses.lua
@@ -49,4 +49,10 @@ function _M.is_trusted(address)
     return trusted_addresses_matcher:match(address)
 end
 
+
+-- whether a trust boundary is configured via `apisix.trusted_addresses`.
+function _M.is_configured()
+    return trusted_addresses_matcher ~= nil
+end
+
 return _M
diff --git a/conf/config.yaml.example b/conf/config.yaml.example
index f83b0e0f8..b7b84d8bb 100644
--- a/conf/config.yaml.example
+++ b/conf/config.yaml.example
@@ -156,8 +156,13 @@ apisix:
                                         # When set to true, it overrides all 
upstream healthcheck configurations and globally disabling healthchecks.
 # trusted_addresses:              # When configured, APISIX will trust the 
`X-Forwarded-*` Headers
 #   - 127.0.0.1                   # passed in requests from the IP/CIDR in the 
list.
-#   - 172.18.0.0/16               # CAUTION: When not configured or the 
request from an untrusted address,
-                                  # APISIX will override `X-Forwarded-*` 
headers with trusted values.
+#   - 172.18.0.0/16               # CAUTION: When not configured or the 
request is from an untrusted
+                                  # address, APISIX overrides 
`X-Forwarded-Proto/Host/Port` with trusted
+                                  # values and clears the `Forwarded` header. 
For `X-Forwarded-For`: when
+                                  # `trusted_addresses` is configured and the 
request is from an untrusted
+                                  # address, it is reset so the upstream only 
sees the APISIX-observed
+                                  # connection IP; when `trusted_addresses` is 
not configured, it is
+                                  # preserved and the connection IP is 
appended (compatible default).
   # fine tune the parameters of LRU cache for some features like secret
   lru:
     secret:
diff --git a/docs/en/latest/plugins/chaitin-waf.md 
b/docs/en/latest/plugins/chaitin-waf.md
index 16a658791..dd8ef5a6a 100644
--- a/docs/en/latest/plugins/chaitin-waf.md
+++ b/docs/en/latest/plugins/chaitin-waf.md
@@ -90,7 +90,7 @@ The examples below demonstrate how you can configure 
chaitin-waf Plugin for diff
 Before proceeding, make sure you have installed [Chaitin WAF 
(SafeLine)](https://docs.waf.chaitin.com/en/GetStarted/Deploy).
 
 :::note
-Only `X-Forwarded-*` headers sent from addresses in the 
`apisix.trusted_addresses` configuration (supports IP and CIDR) will be trusted 
and passed to plugins or upstream. If `apisix.trusted_addresses` is not 
configured or the IP is not within the configured address range, all 
`X-Forwarded-*` headers will be overridden with trusted values.
+Only `X-Forwarded-*` headers sent from addresses in the 
`apisix.trusted_addresses` configuration (supports IP and CIDR) will be trusted 
and passed to plugins or upstream. If `apisix.trusted_addresses` is not 
configured or the IP is not within the configured address range, the 
`X-Forwarded-Proto`, `X-Forwarded-Host`, and `X-Forwarded-Port` headers are 
overridden with trusted values and the `Forwarded` header is cleared. The 
`X-Forwarded-For` header is reset to the APISIX-observed connecti [...]
 :::
 
 :::note
diff --git a/docs/en/latest/plugins/real-ip.md 
b/docs/en/latest/plugins/real-ip.md
index 1d9977906..215cdeddf 100644
--- a/docs/en/latest/plugins/real-ip.md
+++ b/docs/en/latest/plugins/real-ip.md
@@ -49,7 +49,7 @@ The Plugin is functionally similar to NGINX's 
[ngx_http_realip_module](https://n
 | recursive  | boolean | False |  False   |    | If false, replace the 
original client address that matches one of the trusted addresses by the last 
address sent in the configured `source`.<br />If true, replace the original 
client address that matches one of the trusted addresses by the last 
non-trusted address sent in the configured `source`. |
 
 :::note
-Only `X-Forwarded-*` headers sent from addresses in the 
`apisix.trusted_addresses` configuration (supports IP and CIDR) will be trusted 
and passed to plugins or upstream. If `apisix.trusted_addresses` is not 
configured or the IP is not within the configured address range, all 
`X-Forwarded-*` headers will be overridden with trusted values.
+Only `X-Forwarded-*` headers sent from addresses in the 
`apisix.trusted_addresses` configuration (supports IP and CIDR) will be trusted 
and passed to plugins or upstream. If `apisix.trusted_addresses` is not 
configured or the IP is not within the configured address range, the 
`X-Forwarded-Proto`, `X-Forwarded-Host`, and `X-Forwarded-Port` headers are 
overridden with trusted values and the `Forwarded` header is cleared. The 
`X-Forwarded-For` header is reset to the APISIX-observed connecti [...]
 :::
 
 :::note
diff --git a/docs/zh/latest/plugins/chaitin-waf.md 
b/docs/zh/latest/plugins/chaitin-waf.md
index 7a6193f7d..73a29b60d 100644
--- a/docs/zh/latest/plugins/chaitin-waf.md
+++ b/docs/zh/latest/plugins/chaitin-waf.md
@@ -90,7 +90,7 @@ description: chaitin-waf 插件与长亭雷池 WAF 集成,以检测和阻止
 继续操作之前,请确保您已安装 [长亭雷池 WAF](https://docs.waf.chaitin.com/en/GetStarted/Deploy)。
 
 :::note
-只有发送自 `apisix.trusted_addresses` 配置(支持 IP 和 CIDR)地址的 `X-Forwarded-*` 
头才会被信任,并传递给插件或上游。如果未配置 `apisix.trusted_addresses` 或 ip 
不在配置地址范围内的,`X-Forwarded-*` 头将全部被可信值覆盖。
+只有发送自 `apisix.trusted_addresses` 配置(支持 IP 和 CIDR)地址的 `X-Forwarded-*` 
头才会被信任,并传递给插件或上游。如果未配置 `apisix.trusted_addresses` 或 ip 
不在配置地址范围内的,`X-Forwarded-Proto`、`X-Forwarded-Host`、`X-Forwarded-Port` 
头将被可信值覆盖,并清除 `Forwarded` 头。对于 `X-Forwarded-For`:当配置了 `apisix.trusted_addresses` 
且请求来自不可信地址时,该头会被重置,使上游仅获取到 APISIX 观测到的连接 IP;当未配置 `apisix.trusted_addresses` 
时,该头会被保留,并追加连接 IP(兼容的默认行为)。
 :::
 
 :::note
diff --git a/docs/zh/latest/plugins/real-ip.md 
b/docs/zh/latest/plugins/real-ip.md
index c8628b719..96eb8cac0 100644
--- a/docs/zh/latest/plugins/real-ip.md
+++ b/docs/zh/latest/plugins/real-ip.md
@@ -49,7 +49,7 @@ import TabItem from '@theme/TabItem';
 | recursive         | boolean       | 否       | false  |                       
     | 如果为 false,则将匹配可信地址之一的原始客户端地址替换为配置的 `source` 中发送的最后一个地址。<br />如果为 
true,则将匹配可信地址之一的原始客户端地址替换为配置的 `source` 中发送的最后一个非可信地址。 |
 
 :::note
-只有发送自 `apisix.trusted_addresses` 配置 (支持 IP 和 CIDR) 地址的 `X-Forwarded-*` 
头才会被信任,并传递给插件或上游。如果未配置 `apisix.trusted_addresses` 或 ip 
不在配置地址范围内的,`X-Forwarded-*` 头将全部被可信值覆盖。
+只有发送自 `apisix.trusted_addresses` 配置 (支持 IP 和 CIDR) 地址的 `X-Forwarded-*` 
头才会被信任,并传递给插件或上游。如果未配置 `apisix.trusted_addresses` 或 ip 
不在配置地址范围内的,`X-Forwarded-Proto`、`X-Forwarded-Host`、`X-Forwarded-Port` 
头将被可信值覆盖,并清除 `Forwarded` 头。对于 `X-Forwarded-For`:当配置了 `apisix.trusted_addresses` 
且请求来自不可信地址时,该头会被重置,使上游仅获取到 APISIX 观测到的连接 IP;当未配置 `apisix.trusted_addresses` 
时,该头会被保留,并追加连接 IP(兼容的默认行为)。
 :::
 
 :::note
diff --git a/t/core/trusted-addresses.t b/t/core/trusted-addresses.t
index c8a0fc6fa..233bca36c 100644
--- a/t/core/trusted-addresses.t
+++ b/t/core/trusted-addresses.t
@@ -32,7 +32,7 @@ run_tests();
 
 __DATA__
 
-=== TEST 1: without trusted_addresses configuration, X-Forwarded headers 
should be overridden
+=== TEST 1: without trusted_addresses, X-Forwarded-For is preserved while 
others are overridden
 --- yaml_config
 apisix:
     node_listen: 1984
@@ -54,13 +54,14 @@ routes:
 --- request
 GET /old_uri
 --- more_headers
+X-Forwarded-For: 1.2.3.4
 X-Forwarded-Proto: https
 X-Forwarded-Host: example.com
 X-Forwarded-Port: 8443
 --- response_body
 uri: /old_uri
 host: localhost
-x-forwarded-for: 127.0.0.1
+x-forwarded-for: 1.2.3.4, 127.0.0.1
 x-forwarded-host: localhost
 x-forwarded-port: 1984
 x-forwarded-proto: http
@@ -95,13 +96,14 @@ routes:
 --- request
 GET /old_uri
 --- more_headers
+X-Forwarded-For: 1.2.3.4
 X-Forwarded-Proto: https
 X-Forwarded-Host: example.com
 X-Forwarded-Port: 8443
 --- response_body
 uri: /old_uri
 host: localhost
-x-forwarded-for: 127.0.0.1
+x-forwarded-for: 1.2.3.4, 127.0.0.1
 x-forwarded-host: example.com
 x-forwarded-port: 8443
 x-forwarded-proto: https
@@ -319,7 +321,7 @@ x-real-ip: 127.0.0.1
 
 
 
-=== TEST 8: with trusted_addresses configuration, but client not in trusted 
list, X-Forwarded headers should be overridden
+=== TEST 8: client not in trusted list, X-Forwarded-For is reset along with 
the others
 --- yaml_config
 apisix:
     node_listen: 1984
@@ -344,6 +346,7 @@ routes:
 --- request
 GET /old_uri
 --- more_headers
+X-Forwarded-For: 1.2.3.4
 X-Forwarded-Proto: https
 X-Forwarded-Host: example.com
 X-Forwarded-Port: 8443
@@ -358,3 +361,80 @@ x-real-ip: 127.0.0.1
 --- no_error_log
 trusted_addresses is not configured
 trusted_addresses_matcher is not initialized
+
+
+
+=== TEST 9: client not in trusted list, RFC 7239 Forwarded header is cleared
+--- yaml_config
+apisix:
+    node_listen: 1984
+    enable_admin: false
+    trusted_addresses:
+        - "1.0.0.1"
+        - "10.0.0.0/8"
+deployment:
+    role: data_plane
+    role_data_plane:
+        config_provider: yaml
+--- apisix_yaml
+routes:
+  -
+    id: 1
+    uri: /old_uri
+    upstream:
+        nodes:
+            "127.0.0.1:1980": 1
+        type: roundrobin
+#END
+--- request
+GET /old_uri
+--- more_headers
+Forwarded: for=1.2.3.4;host=evil.com;proto=https
+--- response_body
+uri: /old_uri
+host: localhost
+x-forwarded-for: 127.0.0.1
+x-forwarded-host: localhost
+x-forwarded-port: 1984
+x-forwarded-proto: http
+x-real-ip: 127.0.0.1
+
+
+
+=== TEST 10: with `0.0.0.0/0`, RFC 7239 Forwarded header is preserved from 
trusted client
+--- yaml_config
+apisix:
+    node_listen: 1984
+    enable_admin: false
+    trusted_addresses:
+        - "0.0.0.0/0"
+deployment:
+    role: data_plane
+    role_data_plane:
+        config_provider: yaml
+--- apisix_yaml
+routes:
+  -
+    id: 1
+    uri: /old_uri
+    upstream:
+        nodes:
+            "127.0.0.1:1980": 1
+        type: roundrobin
+#END
+--- request
+GET /old_uri
+--- more_headers
+Forwarded: for=1.2.3.4;host=evil.com;proto=https
+X-Forwarded-Proto: https
+X-Forwarded-Host: example.com
+X-Forwarded-Port: 8443
+--- response_body
+uri: /old_uri
+forwarded: for=1.2.3.4;host=evil.com;proto=https
+host: localhost
+x-forwarded-for: 127.0.0.1
+x-forwarded-host: example.com
+x-forwarded-port: 8443
+x-forwarded-proto: https
+x-real-ip: 127.0.0.1
diff --git a/t/plugin/real-ip.t b/t/plugin/real-ip.t
index fafe0b4a5..916a5374e 100644
--- a/t/plugin/real-ip.t
+++ b/t/plugin/real-ip.t
@@ -473,8 +473,7 @@ X-Forwarded-For: 1.1.1.1, 192.128.1.1, 127.0.0.1
 
 
 
-=== TEST 24: trusted in real-ip, but not trusted by `apisix.trusted_addresses`
-should be rejected
+=== TEST 24: untrusted by `apisix.trusted_addresses`, forged X-Forwarded-For 
is stripped before real-ip
 --- yaml_config
 apisix:
     node_listen: 1984

Reply via email to