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