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 196d58517 fix(log-sanitize): return raw tables to avoid double
delay_encode (#13605)
196d58517 is described below
commit 196d585176c1f080151e1a60e411a52eef047665
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Mon Jun 29 17:18:26 2026 +0800
fix(log-sanitize): return raw tables to avoid double delay_encode (#13605)
---
apisix/utils/log-sanitize.lua | 5 +-
t/utils/log-sanitize.t | 119 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 122 insertions(+), 2 deletions(-)
diff --git a/apisix/utils/log-sanitize.lua b/apisix/utils/log-sanitize.lua
index 9e7ebfc49..03fe02acf 100644
--- a/apisix/utils/log-sanitize.lua
+++ b/apisix/utils/log-sanitize.lua
@@ -48,14 +48,15 @@ function _M.redact_params(params)
redacted.headers = safe_headers
end
- return core.json.delay_encode(redacted, true)
+ -- return a raw table; call sites wrap it once in delay_encode
+ return redacted
end
function _M.redact_extra_opts(extra_opts)
local redacted = core.table.deepcopy(extra_opts)
redacted.auth = nil
- return core.json.delay_encode(redacted, true)
+ return redacted
end
diff --git a/t/utils/log-sanitize.t b/t/utils/log-sanitize.t
new file mode 100644
index 000000000..2ca3e3fe0
--- /dev/null
+++ b/t/utils/log-sanitize.t
@@ -0,0 +1,119 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+
+add_block_preprocessor(sub {
+ my ($block) = @_;
+
+ if (!defined $block->request) {
+ $block->set_value("request", "GET /t");
+ }
+
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: redact_params returns a raw table (not a delay_encode slot)
+--- config
+ location /t {
+ content_by_lua_block {
+ local core = require("apisix.core")
+ local log_sanitize = require("apisix.utils.log-sanitize")
+
+ local redacted = log_sanitize.redact_params({
+ method = "POST",
+ scheme = "https",
+ host = "api.openai.com",
+ port = 443,
+ path = "/v1/chat/completions",
+ headers = {
+ ["Authorization"] = "Bearer sk-secret",
+ ["Content-Type"] = "application/json",
+ },
+ })
+
+ -- a raw table has no __tostring metamethod and no wrapper fields
+ ngx.say("has_tostring_mt: ", tostring(getmetatable(redacted) ~= nil
+ and
getmetatable(redacted).__tostring ~= nil))
+ ngx.say("authorization: ", redacted.headers["Authorization"])
+ ngx.say("content_type: ", redacted.headers["Content-Type"])
+ }
+ }
+--- response_body
+has_tostring_mt: false
+authorization: [REDACTED]
+content_type: application/json
+
+
+
+=== TEST 2: wrapping once in delay_encode logs the redacted JSON, not the
wrapper
+--- config
+ location /t {
+ content_by_lua_block {
+ local core = require("apisix.core")
+ local log_sanitize = require("apisix.utils.log-sanitize")
+
+ -- mirrors the AI plugin call sites: one delay_encode at the call
site
+ local slot = core.json.delay_encode(log_sanitize.redact_params({
+ method = "POST",
+ host = "api.openai.com",
+ headers = { ["api-key"] = "secret" },
+ }), true)
+
+ local decoded = core.json.decode(tostring(slot))
+ ngx.say("api_key: ", decoded.headers["api-key"])
+ ngx.say("host: ", decoded.host)
+ -- if double-wrapped the output would be the {data=,force=} wrapper
+ ngx.say("is_wrapper: ", tostring(decoded.data ~= nil))
+ }
+ }
+--- response_body
+api_key: [REDACTED]
+host: api.openai.com
+is_wrapper: false
+
+
+
+=== TEST 3: redact_extra_opts strips auth and returns a raw table
+--- config
+ location /t {
+ content_by_lua_block {
+ local core = require("apisix.core")
+ local log_sanitize = require("apisix.utils.log-sanitize")
+
+ local opts = {
+ model = "gpt-4",
+ auth = { header = { ["Authorization"] = "Bearer sk-secret" } },
+ }
+ local redacted = log_sanitize.redact_extra_opts(opts)
+
+ ngx.say("auth: ", tostring(redacted.auth))
+ ngx.say("model: ", redacted.model)
+ -- source must not be mutated
+ ngx.say("source_auth: ", tostring(opts.auth ~= nil))
+ }
+ }
+--- response_body
+auth: nil
+model: gpt-4
+source_auth: true