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 e9ff2d266 feat: add max_post_args_readable_size to bound post_arg.* 
body reads (#13601)
e9ff2d266 is described below

commit e9ff2d266a9a57611b3ec15cf0d3c5216cfa2f39
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Mon Jun 29 11:01:32 2026 +0800

    feat: add max_post_args_readable_size to bound post_arg.* body reads 
(#13601)
---
 apisix/cli/config.lua              |   1 +
 apisix/cli/schema.lua              |   7 +++
 apisix/core/ctx.lua                |  26 +++++++-
 conf/config.yaml.example           |   2 +
 docs/en/latest/router-radixtree.md |   9 +++
 t/core/ctx3.t                      | 119 +++++++++++++++++++++++++++++++++++++
 6 files changed, 163 insertions(+), 1 deletion(-)

diff --git a/apisix/cli/config.lua b/apisix/cli/config.lua
index bdc22f107..9b8ef0ff1 100644
--- a/apisix/cli/config.lua
+++ b/apisix/cli/config.lua
@@ -47,6 +47,7 @@ local _M = {
     },
     delete_uri_tail_slash = false,
     normalize_uri_like_servlet = false,
+    max_post_args_readable_size = 64,
     router = {
       http = "radixtree_host_uri",
       ssl = "radixtree_sni"
diff --git a/apisix/cli/schema.lua b/apisix/cli/schema.lua
index 6673e565a..b57b30fed 100644
--- a/apisix/cli/schema.lua
+++ b/apisix/cli/schema.lua
@@ -274,6 +274,13 @@ local config_schema = {
                     },
                     uniqueItems = true
                 },
+                max_post_args_readable_size = {
+                    type = "integer",
+                    minimum = 0,
+                    default = 64,
+                    description = "cap (in MB) on the request body read for 
post_arg.* "
+                                  .. "route matching; 0 disables the limit",
+                },
             }
         },
         nginx_config = {
diff --git a/apisix/core/ctx.lua b/apisix/core/ctx.lua
index 1827254b6..64d109331 100644
--- a/apisix/core/ctx.lua
+++ b/apisix/core/ctx.lua
@@ -45,6 +45,8 @@ local pcall        = pcall
 
 local _M = {version = 0.2}
 local GRAPHQL_DEFAULT_MAX_SIZE       = 1048576               -- 1MiB
+local DEFAULT_MAX_POST_ARGS_SIZE     = 64                    -- 64MiB
+local MB                             = 1024 * 1024
 local GRAPHQL_REQ_DATA_KEY           = "query"
 local GRAPHQL_REQ_METHOD_HTTP_GET    = "GET"
 local GRAPHQL_REQ_METHOD_HTTP_POST   = "POST"
@@ -129,6 +131,27 @@ local function parse_graphql(ctx)
 end
 
 
+-- read the cap (in bytes) for parsing post_arg.* bodies; 0 disables the limit
+local function get_max_post_args_readable_size()
+    local local_conf, err = config_local.local_conf()
+    if not local_conf then
+        log.error("failed to get local conf: ", err)
+        return DEFAULT_MAX_POST_ARGS_SIZE * MB
+    end
+
+    local size = core_tab.try_read_attr(local_conf, "apisix", 
"max_post_args_readable_size")
+    if size == nil then
+        size = DEFAULT_MAX_POST_ARGS_SIZE
+    end
+
+    if size == 0 then
+        return nil
+    end
+
+    return size * MB
+end
+
+
 local function get_parsed_graphql()
     local ctx = ngx.ctx.api_ctx
     if ctx._graphql then
@@ -324,7 +347,8 @@ do
             elseif core_str.has_prefix(key, "post_arg.") then
                 -- trim the "post_arg." prefix (10 characters)
                 local arg_key = sub_str(key, 10)
-                local parsed_body, err = request.get_request_body_table(t._ctx)
+                local max_size = get_max_post_args_readable_size()
+                local parsed_body, err = 
request.get_request_body_table(t._ctx, nil, max_size)
                 if not parsed_body then
                     log.warn("failed to fetch post args value by key: ", 
arg_key, " error: ", err)
                     return nil
diff --git a/conf/config.yaml.example b/conf/config.yaml.example
index efbf20d6b..f83b0e0f8 100644
--- a/conf/config.yaml.example
+++ b/conf/config.yaml.example
@@ -70,6 +70,8 @@ apisix:
   delete_uri_tail_slash: false        # Delete the '/' at the end of the URI
   normalize_uri_like_servlet: false   # If true, use the same path 
normalization rules as the Java
                                       # servlet specification. See 
https://github.com/jakartaee/servlet/blob/master/spec/src/main/asciidoc/servlet-spec-body.adoc#352-uri-path-canonicalization,
 which is used in Tomcat.
+  max_post_args_readable_size: 64     # Cap (in MB) on the request body read 
when matching `post_arg.*`
+                                      # route predicates for JSON and 
multipart requests. Set to 0 to disable the limit.
 
   router:
     http: radixtree_host_uri    # radixtree_host_uri: match route by host and 
URI
diff --git a/docs/en/latest/router-radixtree.md 
b/docs/en/latest/router-radixtree.md
index c375360ef..48ff8b167 100644
--- a/docs/en/latest/router-radixtree.md
+++ b/docs/en/latest/router-radixtree.md
@@ -413,3 +413,12 @@ curl -X POST http://127.0.0.1:9180/_post \
 }'
 
 ```
+
+:::note
+
+Matching `post_arg.*` against JSON or multipart bodies requires APISIX to read 
and parse the
+request body during route matching. To avoid exhausting worker memory on large 
bodies, the read
+is capped by `apisix.max_post_args_readable_size` in `config.yaml` (default 
`64` MB). Bodies larger
+than this cap are not matched. Set it to `0` to disable the limit.
+
+:::
diff --git a/t/core/ctx3.t b/t/core/ctx3.t
index 058f91be6..7e24c62fb 100644
--- a/t/core/ctx3.t
+++ b/t/core/ctx3.t
@@ -227,3 +227,122 @@ hello world
 before rewrite model: gpt-4
 after rewrite model: claude
 after rewrite temperature: 0.9
+
+
+
+=== TEST 7: set route matching on post_arg.model
+--- 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,
+                 [=[{
+                        "methods": ["POST"],
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/hello",
+                        "vars": [
+                            ["post_arg.model", "==", "gpt-4"]
+                        ]
+                }]=]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 8: body over max_post_args_readable_size is not read, route not 
matched
+--- yaml_config
+apisix:
+    node_listen: 1984
+    max_post_args_readable_size: 1
+--- request eval
+"POST /hello
+{\"model\":\"gpt-4\",\"pad\":\"" . ("a" x (2 * 1024 * 1024)) . "\"}"
+--- more_headers
+Content-Type: application/json
+--- error_code: 404
+--- response_body
+{"error_msg":"404 Route Not Found"}
+--- error_log
+is greater than the maximum size
+--- no_error_log
+[alert]
+
+
+
+=== TEST 9: body within max_post_args_readable_size still matches
+--- yaml_config
+apisix:
+    node_listen: 1984
+    max_post_args_readable_size: 1
+--- request
+POST /hello
+{"model":"gpt-4"}
+--- more_headers
+Content-Type: application/json
+--- response_body
+hello world
+--- error_code: 200
+
+
+
+=== TEST 10: body exactly at max_post_args_readable_size still matches 
(boundary)
+--- yaml_config
+apisix:
+    node_listen: 1984
+    max_post_args_readable_size: 1
+--- request eval
+"POST /hello
+{\"model\":\"gpt-4\",\"pad\":\"" . ("a" x (1024 * 1024 - 26)) . "\"}"
+--- more_headers
+Content-Type: application/json
+--- response_body
+hello world
+--- error_code: 200
+
+
+
+=== TEST 11: oversized multipart body is not read, route not matched
+--- yaml_config
+apisix:
+    node_listen: 1984
+    max_post_args_readable_size: 1
+--- request eval
+"POST /hello\n" . "--boundary\r\nContent-Disposition: form-data; 
name=\"model\"\r\n\r\ngpt-4\r\n--boundary\r\nContent-Disposition: form-data; 
name=\"pad\"\r\n\r\n" . ("a" x (2 * 1024 * 1024)) . "\r\n--boundary--\r\n"
+--- more_headers
+Content-Type: multipart/form-data; boundary=boundary
+--- error_code: 404
+--- response_body
+{"error_msg":"404 Route Not Found"}
+--- error_log
+is greater than the maximum size
+--- no_error_log
+[alert]
+
+
+
+=== TEST 12: within-limit multipart body still matches
+--- yaml_config
+apisix:
+    node_listen: 1984
+    max_post_args_readable_size: 1
+--- request eval
+"POST /hello\n" . "--boundary\r\nContent-Disposition: form-data; 
name=\"model\"\r\n\r\ngpt-4\r\n--boundary--\r\n"
+--- more_headers
+Content-Type: multipart/form-data; boundary=boundary
+--- response_body
+hello world
+--- error_code: 200

Reply via email to