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 1719001b4 fix(data-mask): keep request header masking effective in the 
log phase (#13839)
1719001b4 is described below

commit 1719001b42b83ffd83ea0d404147c8c85e77bfa1
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Thu Aug 20 15:00:09 2026 +0800

    fix(data-mask): keep request header masking effective in the log phase 
(#13839)
---
 apisix/plugins/data-mask.lua |  27 +++-
 apisix/utils/log-util.lua    |   4 +-
 t/plugin/data-mask.t         | 332 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 358 insertions(+), 5 deletions(-)

diff --git a/apisix/plugins/data-mask.lua b/apisix/plugins/data-mask.lua
index ee390501b..3f49fee98 100644
--- a/apisix/plugins/data-mask.lua
+++ b/apisix/plugins/data-mask.lua
@@ -20,6 +20,8 @@ local next      = next
 local type      = type
 local t_remove  = table.remove
 local re_sub    = ngx.re.sub
+local str_lower = string.lower
+local str_gsub  = string.gsub
 local core      = require("apisix.core")
 local jp        = require("jsonpath")
 
@@ -153,6 +155,17 @@ local function mask_table(tab, conf)
 end
 
 
+-- ngx.req.set_header() is a no-op once a response status exists, so the masked
+-- value is also kept on ctx for the loggers to read
+local function mask_header(ctx, headers, name, value)
+    core.request.set_header(ctx, name, value)
+
+    local key = str_lower(name)
+    headers[key] = value
+    ctx.var["http_" .. str_gsub(key, "-", "_")] = value or ""
+end
+
+
 -- jsonpath index of array starts from 0, lua table index starts from 1
 local function table_index(idx)
     if type(idx) == "number" then
@@ -217,6 +230,7 @@ function _M.log(conf, ctx)
     local body = ngx.req.get_body_data()
     local json_body
     local body_masked = false
+    local masked_headers
 
     if conf.request then
         for _, item in ipairs(conf.request) do
@@ -229,14 +243,17 @@ function _M.log(conf, ctx)
             if item.type == "header" then
                 local header = core.request.header(ctx, item.name)
                 if header then
+                    if not masked_headers then
+                        masked_headers = ngx.req.get_headers()
+                    end
                     if item.action == "remove" then
-                        core.request.set_header(ctx, item.name, nil)
+                        mask_header(ctx, masked_headers, item.name, nil)
                     elseif item.action == "replace" then
-                        core.request.set_header(ctx, item.name, item.value)
+                        mask_header(ctx, masked_headers, item.name, item.value)
                     elseif item.action == "regex" then
                         local new_header, n = regex_replace(header, 
item.regex, item.value)
                         if new_header ~= nil and n > 0 then
-                            core.request.set_header(ctx, item.name, new_header)
+                            mask_header(ctx, masked_headers, item.name, 
new_header)
                         end
                     end
                 end
@@ -294,6 +311,10 @@ function _M.log(conf, ctx)
         end
     end
 
+    if masked_headers then
+        ctx.data_mask_headers = masked_headers
+    end
+
     if query_masked then
         -- for logger plugins
         core.request.set_uri_args(ctx, args)
diff --git a/apisix/utils/log-util.lua b/apisix/utils/log-util.lua
index 241764166..fc8872a78 100644
--- a/apisix/utils/log-util.lua
+++ b/apisix/utils/log-util.lua
@@ -199,7 +199,7 @@ local function get_full_log(ngx, conf)
             url = url,
             uri = var.request_uri,
             method = ngx.req.get_method(),
-            headers = ngx.req.get_headers(),
+            headers = ctx.data_mask_headers or ngx.req.get_headers(),
             querystring = ngx.req.get_uri_args(),
             size = var.request_length
         },
@@ -359,7 +359,7 @@ function _M.get_req_original(ctx, conf)
     local data = {
         ctx.var.request, "\r\n"
     }
-    for k, v in pairs(ngx.req.get_headers()) do
+    for k, v in pairs(ctx.data_mask_headers or ngx.req.get_headers()) do
         core.table.insert_tail(data, k, ": ", v, "\r\n")
     end
     core.table.insert(data, "\r\n")
diff --git a/t/plugin/data-mask.t b/t/plugin/data-mask.t
index a3d114fb8..21d471015 100644
--- a/t/plugin/data-mask.t
+++ b/t/plugin/data-mask.t
@@ -1157,3 +1157,335 @@ success
     }
 --- response_body
 success
+
+
+
+=== TEST 27: header masking with a logger, upstream returns 400
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "data-mask": {
+                                "request": [
+                                    {
+                                        "action": "replace",
+                                        "name": "x-secret",
+                                        "type": "header",
+                                        "value": "*****"
+                                    }
+                                ]
+                            },
+                            "file-logger": {
+                                "path": "mask-header-400.log"
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/specific_status"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+
+
+
+=== TEST 28: verify the header is masked for every response status
+--- config
+    location /t {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local t = require("lib.test_admin").test
+
+            local function req(status)
+                local headers = {}
+                headers["x-secret"] = "PLAINTEXT"
+                headers["x-test-upstream-status"] = status
+                t("/specific_status", ngx.HTTP_GET, "", nil, headers)
+            end
+
+            req("500")
+            req("400")
+            ngx.sleep(0.5)
+
+            local fd, err = io.open("mask-header-400.log", "r")
+            if not fd then
+                core.log.error("failed to open file: ", err)
+                return
+            end
+            for line in fd:lines() do
+                local log = core.json.decode(line)
+                local value = log.request.headers["x-secret"]
+                if value ~= "*****" then
+                    ngx.say("status ", log.response.status, " header mask 
failed: ", value)
+                    fd:close()
+                    return
+                end
+            end
+            fd:close()
+            os.remove("mask-header-400.log")
+            ngx.say("success")
+        }
+    }
+--- response_body
+success
+
+
+
+=== TEST 29: header masking with a custom log_format, upstream returns 400
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "data-mask": {
+                                "request": [
+                                    {
+                                        "action": "replace",
+                                        "name": "x-secret",
+                                        "type": "header",
+                                        "value": "*****"
+                                    }
+                                ]
+                            },
+                            "file-logger": {
+                                "path": "mask-header-fmt-400.log",
+                                "log_format": {
+                                    "status": "$status",
+                                    "secret": "$http_x_secret"
+                                }
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/specific_status"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+
+
+
+=== TEST 30: verify the custom log_format value is masked for every response 
status
+--- config
+    location /t {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local t = require("lib.test_admin").test
+
+            local function req(status)
+                local headers = {}
+                headers["x-secret"] = "PLAINTEXT"
+                headers["x-test-upstream-status"] = status
+                t("/specific_status", ngx.HTTP_GET, "", nil, headers)
+            end
+
+            req("500")
+            req("400")
+            ngx.sleep(0.5)
+
+            local fd, err = io.open("mask-header-fmt-400.log", "r")
+            if not fd then
+                core.log.error("failed to open file: ", err)
+                return
+            end
+            for line in fd:lines() do
+                local log = core.json.decode(line)
+                if log.secret ~= "*****" then
+                    ngx.say("status ", log.status, " header mask failed: ", 
log.secret)
+                    fd:close()
+                    return
+                end
+            end
+            fd:close()
+            os.remove("mask-header-fmt-400.log")
+            ngx.say("success")
+        }
+    }
+--- response_body
+success
+
+
+
+=== TEST 31: header masking when APISIX rewrites the response status to 400
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "data-mask": {
+                                "request": [
+                                    {
+                                        "action": "replace",
+                                        "name": "x-secret",
+                                        "type": "header",
+                                        "value": "*****"
+                                    }
+                                ]
+                            },
+                            "response-rewrite": {
+                                "status_code": 400
+                            },
+                            "file-logger": {
+                                "path": "mask-rewrite-400.log"
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+
+
+
+=== TEST 32: verify the mask holds when the upstream succeeded
+--- config
+    location /t {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local t = require("lib.test_admin").test
+
+            local headers = {}
+            headers["x-secret"] = "PLAINTEXT"
+            t("/hello", ngx.HTTP_GET, "", nil, headers)
+            ngx.sleep(0.5)
+
+            local fd, err = io.open("mask-rewrite-400.log", "r")
+            if not fd then
+                core.log.error("failed to open file: ", err)
+                return
+            end
+            local line = fd:read()
+            fd:close()
+            os.remove("mask-rewrite-400.log")
+
+            local log = core.json.decode(line)
+            if log.request.headers["x-secret"] ~= "*****" then
+                ngx.say("header mask failed: ", 
log.request.headers["x-secret"])
+                return
+            end
+            ngx.say("success")
+        }
+    }
+--- response_body
+success
+
+
+
+=== TEST 33: header masking when APISIX rejects the request with 400
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "data-mask": {
+                                "request": [
+                                    {
+                                        "action": "replace",
+                                        "name": "x-secret",
+                                        "type": "header",
+                                        "value": "*****"
+                                    }
+                                ]
+                            },
+                            "fault-injection": {
+                                "abort": {
+                                    "http_status": 400,
+                                    "body": "rejected"
+                                }
+                            },
+                            "file-logger": {
+                                "path": "mask-abort-400.log"
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+
+
+
+=== TEST 34: verify the mask holds when the upstream is never reached
+--- config
+    location /t {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local t = require("lib.test_admin").test
+
+            local headers = {}
+            headers["x-secret"] = "PLAINTEXT"
+            t("/hello", ngx.HTTP_GET, "", nil, headers)
+            ngx.sleep(0.5)
+
+            local fd, err = io.open("mask-abort-400.log", "r")
+            if not fd then
+                core.log.error("failed to open file: ", err)
+                return
+            end
+            local line = fd:read()
+            fd:close()
+            os.remove("mask-abort-400.log")
+
+            local log = core.json.decode(line)
+            if log.request.headers["x-secret"] ~= "*****" then
+                ngx.say("header mask failed: ", 
log.request.headers["x-secret"])
+                return
+            end
+            ngx.say("success")
+        }
+    }
+--- response_body
+success

Reply via email to