diff --git a/apisix/plugins/ai-prompt-decorator.lua b/apisix/plugins/ai-prompt-decorator.lua index ba9cbe6a5e42..a48cd7debb86 100644 --- a/apisix/plugins/ai-prompt-decorator.lua +++ b/apisix/plugins/ai-prompt-decorator.lua @@ -40,6 +40,13 @@ local prompts = { local schema = { type = "object", properties = { + max_req_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum request body size in bytes buffered into " + .. "memory; larger request bodies are rejected", + }, prepend = prompts, append = prompts, }, @@ -64,8 +71,8 @@ function _M.check_schema(conf) end -local function get_request_body_table() - local body, err = core.request.get_body() +local function get_request_body_table(max_size) + local body, err = core.request.get_body(max_size) if not body then return nil, { message = "could not get body: " .. (err or "request body is empty") } end @@ -80,7 +87,7 @@ end function _M.rewrite(conf, ctx) - local body_tab, err = get_request_body_table() + local body_tab, err = get_request_body_table(conf.max_req_body_size) if not body_tab then return 400, err end diff --git a/apisix/plugins/ai-prompt-guard.lua b/apisix/plugins/ai-prompt-guard.lua index 56cec2f8ab3e..909c6bf0463c 100644 --- a/apisix/plugins/ai-prompt-guard.lua +++ b/apisix/plugins/ai-prompt-guard.lua @@ -28,6 +28,13 @@ local plugin_name = "ai-prompt-guard" local schema = { type = "object", properties = { + max_req_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum request body size in bytes buffered into " + .. "memory; larger request bodies are rejected", + }, match_all_roles = { type = "boolean", default = false, @@ -98,7 +105,7 @@ end function _M.access(conf, ctx) - local body = core.request.get_body() + local body = core.request.get_body(conf.max_req_body_size) if not body then core.log.error("Empty request body") return 400, {message = "Empty request body"} diff --git a/apisix/plugins/ai-prompt-template.lua b/apisix/plugins/ai-prompt-template.lua index 0fe47617e3b1..2c9451be82aa 100644 --- a/apisix/plugins/ai-prompt-template.lua +++ b/apisix/plugins/ai-prompt-template.lua @@ -41,6 +41,13 @@ local prompts = { local schema = { type = "object", properties = { + max_req_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum request body size in bytes buffered into " + .. "memory; larger request bodies are rejected", + }, templates = { type = "array", minItems = 1, @@ -90,8 +97,8 @@ function _M.check_schema(conf) end -local function get_request_body_table() - local body, err = core.request.get_body() +local function get_request_body_table(max_size) + local body, err = core.request.get_body(max_size) if not body then return nil, { message = "could not get body: " .. (err or "request body is empty") } end @@ -115,7 +122,7 @@ local function find_template(conf, template_name) end function _M.rewrite(conf, ctx) - local body_tab, err = get_request_body_table() + local body_tab, err = get_request_body_table(conf.max_req_body_size) if not body_tab then return 400, err end diff --git a/apisix/plugins/ai-proxy/schema.lua b/apisix/plugins/ai-proxy/schema.lua index 5ffd6ef856fd..a7833f0c1894 100644 --- a/apisix/plugins/ai-proxy/schema.lua +++ b/apisix/plugins/ai-proxy/schema.lua @@ -248,7 +248,7 @@ _M.ai_proxy_schema = { minimum = 1, default = 67108864, description = "maximum request body size in bytes the plugin reads " - .. "into memory; larger requests are rejected with 413. " + .. "into memory; larger request bodies are rejected. " .. "Prevents unbounded memory buffering of large bodies.", }, max_stream_duration_ms = { @@ -373,7 +373,7 @@ _M.ai_proxy_multi_schema = { minimum = 1, default = 67108864, description = "maximum request body size in bytes the plugin reads " - .. "into memory; larger requests are rejected with 413. " + .. "into memory; larger request bodies are rejected. " .. "Prevents unbounded memory buffering of large bodies.", }, max_stream_duration_ms = { diff --git a/apisix/plugins/ai-request-rewrite.lua b/apisix/plugins/ai-request-rewrite.lua index 5bef20771582..a2b9b41fd170 100644 --- a/apisix/plugins/ai-request-rewrite.lua +++ b/apisix/plugins/ai-request-rewrite.lua @@ -60,6 +60,13 @@ local model_options_schema = { local schema = { type = "object", properties = { + max_req_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum request body size in bytes buffered into " + .. "memory; larger request bodies are rejected", + }, prompt = { type = "string", description = "The prompt to rewrite client request." @@ -172,7 +179,7 @@ function _M.check_schema(conf) end function _M.access(conf, ctx) - local client_request_body, err = core.request.get_body() + local client_request_body, err = core.request.get_body(conf.max_req_body_size) if err then core.log.warn("failed to get request body: ", err) return HTTP_BAD_REQUEST diff --git a/apisix/plugins/authz-keycloak.lua b/apisix/plugins/authz-keycloak.lua index b75f334f732d..c1ea887230f4 100644 --- a/apisix/plugins/authz-keycloak.lua +++ b/apisix/plugins/authz-keycloak.lua @@ -27,6 +27,13 @@ local pairs = pairs local schema = { type = "object", properties = { + max_req_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum request body size in bytes buffered into " + .. "memory; larger request bodies are rejected", + }, discovery = {type = "string", minLength = 1, maxLength = 4096}, token_endpoint = {type = "string", minLength = 1, maxLength = 4096}, resource_registration_endpoint = {type = "string", minLength = 1, maxLength = 4096}, @@ -694,7 +701,7 @@ end local function generate_token_using_password_grant(conf,ctx) log.debug("generate_token_using_password_grant Function Called") - local body, err = core.request.get_body() + local body, err = core.request.get_body(conf.max_req_body_size) if err or not body then log.error("Failed to get request body: ", err) return 503 diff --git a/apisix/plugins/body-transformer.lua b/apisix/plugins/body-transformer.lua index b0debcbea76a..fefc6221dd41 100644 --- a/apisix/plugins/body-transformer.lua +++ b/apisix/plugins/body-transformer.lua @@ -48,6 +48,20 @@ local schema = { properties = { request = transform_schema, response = transform_schema, + max_req_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum request body size in bytes buffered into " + .. "memory; larger request bodies are rejected", + }, + max_resp_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum response body size in bytes buffered into " + .. "memory; larger responses are truncated", + }, }, anyOf = { {required = {"request"}}, @@ -233,7 +247,11 @@ function _M.rewrite(conf, ctx) local request_method = ngx.var.request_method conf = core.table.deepcopy(conf) ctx.body_transformer_conf = conf - local body = core.request.get_body() + local body, err = core.request.get_body(conf.max_req_body_size) + if err then + core.log.error("failed to get request body: ", err) + return 413 + end set_input_format(conf, "request", ctx.var.http_content_type, request_method) local out, status, err = transform(conf, body, "request", ctx, request_method) if not out then @@ -262,7 +280,7 @@ function _M.body_filter(_, ctx) return end if conf.response then - local body = core.response.hold_body_chunk(ctx) + local body = core.response.hold_body_chunk(ctx, false, conf.max_resp_body_size) if ngx.arg[2] == false and not body then return end diff --git a/apisix/plugins/cas-auth.lua b/apisix/plugins/cas-auth.lua index f179c9f92206..dfc166037d70 100644 --- a/apisix/plugins/cas-auth.lua +++ b/apisix/plugins/cas-auth.lua @@ -40,6 +40,13 @@ local plugin_name = "cas-auth" local schema = { type = "object", properties = { + max_req_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum request body size in bytes buffered into " + .. "memory; larger request bodies are rejected", + }, idp_uri = {type = "string"}, cas_callback_uri = { type = "string", @@ -358,7 +365,7 @@ function _M.access(conf, ctx) end if method == "POST" and uri == cas_callback_path then - local data = core.request.get_body() + local data = core.request.get_body(conf.max_req_body_size) local ticket = data and data:match("(.+)") if ticket == nil then return ngx.HTTP_BAD_REQUEST, diff --git a/apisix/plugins/degraphql.lua b/apisix/plugins/degraphql.lua index e47a276a8068..f7e2b5b0f2b8 100644 --- a/apisix/plugins/degraphql.lua +++ b/apisix/plugins/degraphql.lua @@ -25,6 +25,13 @@ local type = type local schema = { type = "object", properties = { + max_req_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum request body size in bytes buffered into " + .. "memory; larger request bodies are rejected", + }, query = { type = "string", minLength = 1, @@ -75,7 +82,7 @@ end local function fetch_post_variables(conf) - local req_body, err = core.request.get_body() + local req_body, err = core.request.get_body(conf.max_req_body_size) if err ~= nil then core.log.error("failed to get request body: ", err) return nil, 503 @@ -146,7 +153,7 @@ function _M.access(conf, ctx) if meth == "POST" then if not conf.variables then -- the set_body_data requires to read the body first - core.request.get_body() + core.request.get_body(conf.max_req_body_size) end core.request.set_header(ctx, "Content-Type", "application/json") diff --git a/apisix/plugins/grpc-transcode.lua b/apisix/plugins/grpc-transcode.lua index 625018fb71fc..3b5d58046eef 100644 --- a/apisix/plugins/grpc-transcode.lua +++ b/apisix/plugins/grpc-transcode.lua @@ -47,6 +47,13 @@ local pb_option_def = { local schema = { type = "object", properties = { + max_resp_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum response body size in bytes buffered into " + .. "memory for transcoding; larger responses are truncated", + }, proto_id = schema_def.id_schema, service = { description = "the grpc service name", @@ -200,7 +207,8 @@ function _M.body_filter(conf, ctx) end local err = response(ctx, proto_obj, conf.service, conf.method, conf.pb_option, - conf.show_status_in_body, conf.status_detail_type) + conf.show_status_in_body, conf.status_detail_type, + conf.max_resp_body_size) if err then core.log.error("transform response error: ", err) return diff --git a/apisix/plugins/grpc-transcode/response.lua b/apisix/plugins/grpc-transcode/response.lua index 9dd6780f049d..fd8dbfa6c384 100644 --- a/apisix/plugins/grpc-transcode/response.lua +++ b/apisix/plugins/grpc-transcode/response.lua @@ -93,8 +93,9 @@ local function handle_error_response(status_detail_type, proto) end -return function(ctx, proto, service, method, pb_option, show_status_in_body, status_detail_type) - local buffer = core.response.hold_body_chunk(ctx) +return function(ctx, proto, service, method, pb_option, show_status_in_body, status_detail_type, + max_resp_body_size) + local buffer = core.response.hold_body_chunk(ctx, false, max_resp_body_size) if not buffer then return nil end diff --git a/apisix/plugins/grpc-web.lua b/apisix/plugins/grpc-web.lua index da059822e8a3..8ca0c3f88a21 100644 --- a/apisix/plugins/grpc-web.lua +++ b/apisix/plugins/grpc-web.lua @@ -41,6 +41,13 @@ local plugin_name = "grpc-web" local schema = { type = "object", properties = { + max_req_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum request body size in bytes buffered into " + .. "memory; larger request bodies are rejected", + }, cors_allow_headers = { description = "multiple header use ',' to split. default: content-type,x-grpc-web,x-user-agent.", @@ -143,7 +150,7 @@ function _M.access(conf, ctx) end -- set grpc body - local body, err = core.request.get_body() + local body, err = core.request.get_body(conf.max_req_body_size) if err or not body then core.log.error("failed to read request body, err: ", err) return exit(ctx, 400) diff --git a/apisix/plugins/http-dubbo.lua b/apisix/plugins/http-dubbo.lua index f068654babc4..c4be4c19ceaf 100644 --- a/apisix/plugins/http-dubbo.lua +++ b/apisix/plugins/http-dubbo.lua @@ -32,6 +32,13 @@ local plugin_name = "http-dubbo" local schema = { type = "object", properties = { + max_req_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum request body size in bytes buffered into " + .. "memory; larger request bodies are rejected", + }, service_name = { type = "string", minLength = 1, @@ -174,7 +181,12 @@ local function get_dubbo_request(conf, ctx) serialized = serialization_header == "true" end if serialized then - params = core.request.get_body() + local body, err = core.request.get_body(conf.max_req_body_size) + if err then + core.log.error("failed to get request body: ", err) + return nil, 413 + end + params = body if params then local end_of_params = core.string.sub(params, -1) if end_of_params ~= "\n" then @@ -182,7 +194,11 @@ local function get_dubbo_request(conf, ctx) end end else - local body_data = core.request.get_body() + local body_data, err = core.request.get_body(conf.max_req_body_size) + if err then + core.log.error("failed to get request body: ", err) + return nil, 413 + end if body_data then local lua_object = core.json.decode(body_data); for _, v in pairs(lua_object) do @@ -231,7 +247,11 @@ function _M.before_proxy(conf, ctx) core.log.error("failed to connect to upstream ", err) return 502 end - local request = get_dubbo_request(conf, ctx) + local request, err_code = get_dubbo_request(conf, ctx) + if not request then + sock:close() + return err_code + end local bytes, _ = sock:send(request) if bytes > 0 then local header, _ = sock:receiveany(16); diff --git a/apisix/plugins/oas-validator.lua b/apisix/plugins/oas-validator.lua index 0cdd5b4a018c..602585d5d713 100644 --- a/apisix/plugins/oas-validator.lua +++ b/apisix/plugins/oas-validator.lua @@ -35,6 +35,13 @@ local DEFAULT_SPEC_URL_TTL = 3600 local schema = { type = "object", properties = { + max_req_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum request body size in bytes buffered into " + .. "memory; larger request bodies are rejected", + }, spec = { description = "schema against which the request/response will be validated", type = "string", @@ -244,7 +251,7 @@ function _M.access(conf, ctx) local req_body if not conf.skip_request_body_validation then - local body, body_err = core.request.get_body() + local body, body_err = core.request.get_body(conf.max_req_body_size) if body_err ~= nil then core.log.error("failed reading request body, err: " .. body_err) return 500, {message = "error reading the request body. err: " .. body_err} diff --git a/apisix/plugins/openwhisk.lua b/apisix/plugins/openwhisk.lua index f1399254c0aa..035bc61f36de 100644 --- a/apisix/plugins/openwhisk.lua +++ b/apisix/plugins/openwhisk.lua @@ -25,6 +25,13 @@ local name_pattern = [[\A([\w]|[\w][\w@ .-]*[\w@.-]+)\z]] local schema = { type = "object", properties = { + max_req_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum request body size in bytes buffered into " + .. "memory; larger request bodies are rejected", + }, api_host = {type = "string"}, ssl_verify = { type = "boolean", @@ -77,9 +84,14 @@ end function _M.access(conf, ctx) + local body, err = core.request.get_body(conf.max_req_body_size) + if err then + core.log.error("failed to get request body: ", err) + return 413 + end local params = { method = "POST", - body = core.request.get_body(), + body = body, query = { blocking = "true", result = tostring(conf.result), diff --git a/apisix/plugins/proxy-cache/init.lua b/apisix/plugins/proxy-cache/init.lua index 3ccacf7cd6fd..cf23b05bd23a 100644 --- a/apisix/plugins/proxy-cache/init.lua +++ b/apisix/plugins/proxy-cache/init.lua @@ -50,6 +50,14 @@ end local schema = { type = "object", properties = { + max_resp_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum response body size in bytes buffered into " + .. "memory by the memory cache strategy; larger " + .. "responses are streamed through without being cached", + }, cache_zone = { type = "string", minLength = 1, diff --git a/apisix/plugins/proxy-cache/memory_handler.lua b/apisix/plugins/proxy-cache/memory_handler.lua index cba8febef8fb..95599cff39fc 100644 --- a/apisix/plugins/proxy-cache/memory_handler.lua +++ b/apisix/plugins/proxy-cache/memory_handler.lua @@ -525,11 +525,18 @@ function _M.body_filter(conf, ctx) return end - local res_body = core.response.hold_body_chunk(ctx, true) + local res_body = core.response.hold_body_chunk(ctx, true, conf.max_resp_body_size) if not res_body then return end + if conf.max_resp_body_size and #res_body >= conf.max_resp_body_size then + -- response exceeds the buffering cap; stream it through without + -- caching so we never store a truncated body + ctx.cache = nil + return + end + local entry = { status = ngx.status, body = res_body, diff --git a/apisix/plugins/request-validation.lua b/apisix/plugins/request-validation.lua index 02f2f51c3a21..d94a728106de 100644 --- a/apisix/plugins/request-validation.lua +++ b/apisix/plugins/request-validation.lua @@ -25,6 +25,13 @@ local schema = { properties = { header_schema = {type = "object"}, body_schema = {type = "object"}, + max_req_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum request body size in bytes buffered into " + .. "memory; larger request bodies are rejected", + }, rejected_code = {type = "integer", minimum = 200, maximum = 599, default = 400}, rejected_msg = {type = "string", minLength = 1, maxLength = 256} }, @@ -81,7 +88,7 @@ function _M.rewrite(conf, ctx) if conf.body_schema then local req_body - local body, err = core.request.get_body() + local body, err = core.request.get_body(conf.max_req_body_size) if not body then if err then core.log.error("failed to get body: ", err) diff --git a/apisix/plugins/response-rewrite.lua b/apisix/plugins/response-rewrite.lua index b86604a36d60..df4fa889d4a4 100644 --- a/apisix/plugins/response-rewrite.lua +++ b/apisix/plugins/response-rewrite.lua @@ -38,6 +38,14 @@ local lrucache = core.lrucache.new({ local schema = { type = "object", properties = { + max_resp_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum response body size in bytes buffered into " + .. "memory when filters are applied; larger responses " + .. "are truncated", + }, headers = { description = "new headers for response", anyOf = { @@ -257,7 +265,7 @@ function _M.body_filter(conf, ctx) if conf.filters then - local body = core.response.hold_body_chunk(ctx) + local body = core.response.hold_body_chunk(ctx, false, conf.max_resp_body_size) if not body then return end diff --git a/apisix/plugins/serverless/generic-upstream.lua b/apisix/plugins/serverless/generic-upstream.lua index 52a0cb3ea346..5c3a6c58790c 100644 --- a/apisix/plugins/serverless/generic-upstream.lua +++ b/apisix/plugins/serverless/generic-upstream.lua @@ -32,6 +32,13 @@ return function(plugin_name, version, priority, request_processor, authz_schema, local schema = { type = "object", properties = { + max_req_body_size = { + type = "integer", + minimum = 1, + default = 67108864, + description = "maximum request body size in bytes buffered into " + .. "memory; larger request bodies are rejected", + }, function_uri = {type = "string"}, authorization = authz_schema, timeout = {type = "integer", minimum = 100, default = 3000}, @@ -62,7 +69,7 @@ return function(plugin_name, version, priority, request_processor, authz_schema, local uri_args = core.request.get_uri_args(ctx) local headers = core.request.headers(ctx) or {} - local req_body, err = core.request.get_body() + local req_body, err = core.request.get_body(conf.max_req_body_size) if err then core.log.error("error while reading request body: ", err) diff --git a/docs/en/latest/plugins/ai-prompt-decorator.md b/docs/en/latest/plugins/ai-prompt-decorator.md index 454392278f34..ae351dd11d1c 100644 --- a/docs/en/latest/plugins/ai-prompt-decorator.md +++ b/docs/en/latest/plugins/ai-prompt-decorator.md @@ -42,6 +42,7 @@ The `ai-prompt-decorator` Plugin modifies user input prompts by prefixing and ap | Name | Type | Required | Default | Valid values | Description | | --- | --- | --- | --- | --- | --- | +| `max_req_body_size` | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | | `prepend` | array | Conditionally\* | | | An array of prompt objects to be prepended. | | `prepend.role` | string | True | | [`system`, `user`, `assistant`] | Role of the message. | | `prepend.content` | string | True | | length >= 1 | Content of the message (prompt). | diff --git a/docs/en/latest/plugins/ai-prompt-guard.md b/docs/en/latest/plugins/ai-prompt-guard.md index b5ebd17cca63..7950fd3a5eda 100644 --- a/docs/en/latest/plugins/ai-prompt-guard.md +++ b/docs/en/latest/plugins/ai-prompt-guard.md @@ -44,6 +44,7 @@ When both `allow_patterns` and `deny_patterns` are configured, the Plugin first | Name | Type | Required | Default | Valid values | Description | | --- | --- | --- | --- | --- | --- | +| `max_req_body_size` | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | | `match_all_roles` | boolean | False | false | | If `true`, validate messages from all roles. If `false`, validate the message from `user` role only. | | `match_all_conversation_history` | boolean | False | false | | If `true`, concatenate and check all messages in the conversation history. If `false`, only check the content of the last message. | | `allow_patterns` | array | False | [] | | An array of regex patterns that messages should match. When configured, messages must match at least one pattern to be considered valid. | diff --git a/docs/en/latest/plugins/ai-prompt-template.md b/docs/en/latest/plugins/ai-prompt-template.md index 2d3e80de8ddd..b176aec944b5 100644 --- a/docs/en/latest/plugins/ai-prompt-template.md +++ b/docs/en/latest/plugins/ai-prompt-template.md @@ -42,6 +42,7 @@ The `ai-prompt-template` Plugin supports pre-configuring prompt templates that o | Name | Type | Required | Default | Valid values | Description | | --- | --- | --- | --- | --- | --- | +| `max_req_body_size` | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | | `templates` | array | True | | | An array of template objects. | | `templates.name` | string | True | | | Name of the template. When requesting the Route, the request should include the template name that corresponds to the configured template. | | `templates.template` | object | True | | | Template specification. | diff --git a/docs/en/latest/plugins/ai-request-rewrite.md b/docs/en/latest/plugins/ai-request-rewrite.md index df518598d5f1..cdeddb008403 100644 --- a/docs/en/latest/plugins/ai-request-rewrite.md +++ b/docs/en/latest/plugins/ai-request-rewrite.md @@ -42,6 +42,7 @@ The `ai-request-rewrite` Plugin processes client requests by forwarding them to | Name | Type | Required | Default | Valid values | Description | | --- | --- | --- | --- | --- | --- | +| `max_req_body_size` | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | | `prompt` | string | True | | | The prompt to send to the LLM service for rewriting the client request. | | `provider` | string | True | | [openai, deepseek, azure-openai, aimlapi, gemini, vertex-ai, anthropic, openrouter, openai-compatible] | LLM service provider. When set to `aimlapi`, the Plugin uses the OpenAI-compatible driver and proxies the request to `https://api.aimlapi.com/v1/chat/completions`. When set to `openai-compatible`, the Plugin proxies requests to the custom endpoint configured in `override`. When set to `azure-openai`, the Plugin also proxies requests to the custom endpoint configured in `override` and additionally omits the `model` parameter from the request body sent to Azure OpenAI. | | `auth` | object | True | | | Authentication configurations. | diff --git a/docs/en/latest/plugins/authz-keycloak.md b/docs/en/latest/plugins/authz-keycloak.md index 194bdc7702d3..a5a83f22fe04 100644 --- a/docs/en/latest/plugins/authz-keycloak.md +++ b/docs/en/latest/plugins/authz-keycloak.md @@ -45,6 +45,7 @@ While the Plugin was developed for Keycloak, it could theoretically be used with | Name | Type | Required | Default | Valid values | Description | |----------------------------------------------|---------------|----------|-----------------------------------------------|------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | | discovery | string | False | | https://host.domain/realms/foo/.well-known/uma2-configuration | URL to [discovery document](https://www.keycloak.org/docs/latest/authorization_services/index.html) of Keycloak Authorization Services. | | token_endpoint | string | False | | https://host.domain/realms/foo/protocol/openid-connect/token | An OAuth2-compliant token endpoint that supports the `urn:ietf:params:oauth:grant-type:uma-ticket` grant type. If provided, overrides the value from discovery. | | resource_registration_endpoint | string | False | | https://host.domain/realms/foo/authz/protection/resource_set | A UMA-compliant resource registration endpoint. If provided, overrides the value from discovery. | diff --git a/docs/en/latest/plugins/aws-lambda.md b/docs/en/latest/plugins/aws-lambda.md index 3538ec6b76b6..6e094eb95f14 100644 --- a/docs/en/latest/plugins/aws-lambda.md +++ b/docs/en/latest/plugins/aws-lambda.md @@ -44,6 +44,7 @@ The Plugin supports authentication and authorization with AWS via IAM user crede | Name | Type | Required | Default | Valid values | Description | |------------------------------|---------|----------|---------------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | | function_uri | string | True | | | AWS Lambda function URL or Amazon API Gateway endpoint that triggers the Lambda function. | | authorization | object | False | | | Credentials used in authentication and authorization on AWS to invoke Lambda function. | | authorization.apikey | string | False | | | API key for the REST API Gateway when API key is selected as the security mechanism. | diff --git a/docs/en/latest/plugins/azure-functions.md b/docs/en/latest/plugins/azure-functions.md index 07663eb07e37..142b8deb9618 100644 --- a/docs/en/latest/plugins/azure-functions.md +++ b/docs/en/latest/plugins/azure-functions.md @@ -37,6 +37,7 @@ When enabled, the Plugin terminates the ongoing request to the configured URI an | Name | Type | Required | Default | Valid values | Description | |------------------------|---------|----------|---------|--------------|---------------------------------------------------------------------------------------------------------------------------------------| +| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | | function_uri | string | True | | | Azure FunctionS endpoint which triggers the serverless function. For example, `http://test-apisix.azurewebsites.net/api/HttpTrigger`. | | authorization | object | False | | | Authorization credentials to access Azure Functions. | | authorization.apikey | string | False | | | Generated API key to authorize requests. | diff --git a/docs/en/latest/plugins/body-transformer.md b/docs/en/latest/plugins/body-transformer.md index 3fde732ab1b8..195e37a3f6df 100644 --- a/docs/en/latest/plugins/body-transformer.md +++ b/docs/en/latest/plugins/body-transformer.md @@ -40,6 +40,8 @@ The `body-transformer` Plugin performs template-based transformations to transfo | Name | Type | Required | Default | Valid values | Description | | ------------- | ------- | -------- | ------- | ------------ | ------------------------------------------ | +| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | +| max_resp_body_size | integer | False | 67108864 | >= 1 | Maximum response body size in bytes buffered into memory. Larger responses are truncated. | | `request` | object | False | | | Request body transformation configuration. | | `request.input_format` | string | False | | [`xml`,`json`,`encoded`,`args`,`plain`,`multipart`] | Request body original media type. If unspecified, the value would be determined by the `Content-Type` header to apply the corresponding decoder. The `xml` option corresponds to `text/xml` media type. The `json` option corresponds to `application/json` media type. The `encoded` option corresponds to `application/x-www-form-urlencoded` media type. The `args` option corresponds to GET requests. The `plain` option corresponds to `text/plain` media type. The `multipart` option corresponds to `multipart/related` media type. If the media type is neither type, the value would be left unset and the transformation template will be directly applied. | | `request.template` | string | True | | | Request body transformation template. The template uses [lua-resty-template](https://github.com/bungle/lua-resty-template) syntax. See the [template syntax](https://github.com/bungle/lua-resty-template#template-syntax) for more details. You can also use auxiliary functions `_escape_json()` and `_escape_xml()` to escape special characters such as double quotes, `_body` to access request body, and `_ctx` to access context variables. | diff --git a/docs/en/latest/plugins/cas-auth.md b/docs/en/latest/plugins/cas-auth.md index 3d64e226cf8d..d5bb98fa1a59 100644 --- a/docs/en/latest/plugins/cas-auth.md +++ b/docs/en/latest/plugins/cas-auth.md @@ -37,6 +37,7 @@ to do authentication, from the SP (service provider) perspective. | Name | Type | Required | Default | Description | | ----------- | ----------- | ----------- | ----------- | ----------- | +| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | | `idp_uri` | string | True | | URI of IdP. | | `cas_callback_uri` | string | True | | redirect uri used to callback the SP from IdP after login or logout. | | `logout_uri` | string | True | | logout uri to trigger logout. | diff --git a/docs/en/latest/plugins/degraphql.md b/docs/en/latest/plugins/degraphql.md index bd0d55ca86a3..984265539d90 100644 --- a/docs/en/latest/plugins/degraphql.md +++ b/docs/en/latest/plugins/degraphql.md @@ -39,6 +39,7 @@ The `degraphql` Plugin supports communicating with upstream GraphQL services ove | Name | Type | Required | Description | | ---------------- | ------------ | -------- | -------------------------------------------------------------------------------------------------- | +| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | | `query` | string | True | The GraphQL query sent to the Upstream. | | `operation_name` | string | False | The name of the operation, only required if multiple operations are present in the query. | | `variables` | array[string]| False | The names of variables used in the GraphQL query, extracted from the request body or query string. | diff --git a/docs/en/latest/plugins/grpc-transcode.md b/docs/en/latest/plugins/grpc-transcode.md index 425fc95203f3..4b756d363b1d 100644 --- a/docs/en/latest/plugins/grpc-transcode.md +++ b/docs/en/latest/plugins/grpc-transcode.md @@ -42,6 +42,7 @@ With this Plugin enabled, APISIX accepts an HTTP request from the client, transc | Name | Type | Required | Default | Description | |----------------------|--------------------------------------------------------|----------|-----------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| max_resp_body_size | integer | False | 67108864 | >= 1 | Maximum response body size in bytes buffered into memory for transcoding. Larger responses are truncated. | | proto_id | string/integer | True | | ID of the proto resource, which contains the protocol buffer definitions. | | service | string | True | | Name of the gRPC service. | | method | string | True | | Method name of the gRPC service. | diff --git a/docs/en/latest/plugins/grpc-web.md b/docs/en/latest/plugins/grpc-web.md index ff2e4c1a5adb..f1c55663ce29 100644 --- a/docs/en/latest/plugins/grpc-web.md +++ b/docs/en/latest/plugins/grpc-web.md @@ -42,6 +42,7 @@ The `grpc-web` Plugin translates gRPC-Web requests into native gRPC calls and fo | Name | Type | Required | Default | Description | |----------------------|---------|----------|-------------------------------------------|---------------------------------------------------------------------------------------------------------| +| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | | cors_allow_headers | string | False | `content-type,x-grpc-web,x-user-agent` | Comma-separated list of request headers allowed for cross-origin requests. | ## Request Handling diff --git a/docs/en/latest/plugins/http-dubbo.md b/docs/en/latest/plugins/http-dubbo.md index 9cced1bce994..3441536a3c47 100755 --- a/docs/en/latest/plugins/http-dubbo.md +++ b/docs/en/latest/plugins/http-dubbo.md @@ -38,6 +38,7 @@ Dubbo 2.x, the serialization type of the upstream service must be fastjson). | Name | Type | Required | Default | Valid values | Description | |--------------------------|---------|----------|---------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | | service_name | string | True | | | Dubbo service name | | service_version | string | False | 0.0.0 | | Dubbo service version | | method | string | True | | | Dubbo service method name | diff --git a/docs/en/latest/plugins/oas-validator.md b/docs/en/latest/plugins/oas-validator.md index a96fe2c3e0d5..d4292508e64b 100644 --- a/docs/en/latest/plugins/oas-validator.md +++ b/docs/en/latest/plugins/oas-validator.md @@ -43,6 +43,7 @@ The OpenAPI spec can be provided as an inline JSON string or fetched from a remo | Name | Type | Required | Default | Valid values | Description | |------|------|----------|---------|--------------|-------------| +| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | | spec | string | No* | | | Inline OpenAPI 3.x specification in JSON format. Required if `spec_url` is not set. | | spec_url | string | No* | | `^https?://` | URL to fetch the OpenAPI specification from. Required if `spec` is not set. | | spec_url_request_headers | object | No | | | Custom HTTP request headers sent when fetching `spec_url`. Useful for authenticated specification endpoints. | diff --git a/docs/en/latest/plugins/openfunction.md b/docs/en/latest/plugins/openfunction.md index 12340ee1de0c..fa338f076b42 100644 --- a/docs/en/latest/plugins/openfunction.md +++ b/docs/en/latest/plugins/openfunction.md @@ -37,6 +37,7 @@ This Plugin can be configured on a Route and requests will be sent to the config | Name | Type | Required | Default | Valid values | Description | | --------------------------- | ------- | -------- | ------- | ------------ | ---------------------------------------------------------------------------------------------------------- | +| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | | function_uri | string | True | | | function uri. For example, `https://localhost:30858/default/function-sample`. | | ssl_verify | boolean | False | true | | When set to `true` verifies the SSL certificate. | | authorization | object | False | | | Authorization credentials to access functions of OpenFunction. | diff --git a/docs/en/latest/plugins/openwhisk.md b/docs/en/latest/plugins/openwhisk.md index c3371f26102e..78f624eb780b 100644 --- a/docs/en/latest/plugins/openwhisk.md +++ b/docs/en/latest/plugins/openwhisk.md @@ -37,6 +37,7 @@ This Plugin can be configured on a Route and requests will be send to the config | Name | Type | Required | Default | Valid values | Description | | ----------------- | ------- | -------- | ------- | ------------ | ---------------------------------------------------------------------------------------------------------- | +| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | | api_host | string | True | | | OpenWhisk API host address. For example, `https://localhost:3233`. | | ssl_verify | boolean | False | true | | When set to `true` verifies the SSL certificate. | | service_token | string | True | | | OpenWhisk service token. The format is `xxx:xxx` and it is passed through basic auth when calling the API. | diff --git a/docs/en/latest/plugins/proxy-cache.md b/docs/en/latest/plugins/proxy-cache.md index c44e07a3b7a6..e43142a315b0 100644 --- a/docs/en/latest/plugins/proxy-cache.md +++ b/docs/en/latest/plugins/proxy-cache.md @@ -43,6 +43,7 @@ Responses can be conditionally cached based on request HTTP methods, response st | Name | Type | Required | Default | Valid values | Description | |--------------------|----------------|----------|---------------------------|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| max_resp_body_size | integer | False | 67108864 | >= 1 | Maximum response body size in bytes buffered into memory by the `memory` cache strategy. Larger responses are streamed through without being cached. | | cache_strategy | string | False | disk | ["disk","memory"] | Caching strategy. Cache on disk or in memory. | | cache_zone | string | False | disk_cache_one | | Cache zone used with the caching strategy. The value should match one of the cache zones defined in the [configuration files](#static-configurations) and should correspond to the caching strategy. For example, when using the in-memory caching strategy, you should use an in-memory cache zone. | | cache_key | array[string] | False | ["$host", "$request_uri"] | | Key to use for caching. Support [NGINX variables](https://nginx.org/en/docs/varindex.html) and constant strings in values. Variables should be prefixed with a `$` sign. | diff --git a/docs/en/latest/plugins/request-validation.md b/docs/en/latest/plugins/request-validation.md index 83ec7eaf39f5..e136339e5118 100644 --- a/docs/en/latest/plugins/request-validation.md +++ b/docs/en/latest/plugins/request-validation.md @@ -43,6 +43,7 @@ See [JSON schema specification](https://json-schema.org/specification) to learn | Name | Type | Required | Default | Valid values | Description | |---------------|---------|----------|---------|---------------|---------------------------------------------------| +| max_req_body_size | integer | False | 67108864 | >= 1 | Maximum request body size in bytes buffered into memory. Requests with a larger body are rejected. | | header_schema | object | False | | | Schema for the request header data. | | body_schema | object | False | | | Schema for the request body data. | | rejected_code | integer | False | 400 | [200,...,599] | Status code to return when rejecting requests. | diff --git a/docs/en/latest/plugins/response-rewrite.md b/docs/en/latest/plugins/response-rewrite.md index ec3bd8a7a383..74bc7beb75c7 100644 --- a/docs/en/latest/plugins/response-rewrite.md +++ b/docs/en/latest/plugins/response-rewrite.md @@ -51,6 +51,7 @@ You can also use the [redirect](./redirect.md) Plugin to set up redirects. | Name | Type | Required | Default | Valid values | Description | |-----------------|---------|----------|---------|---------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| max_resp_body_size | integer | False | 67108864 | >= 1 | Maximum response body size in bytes buffered into memory when `filters` are applied. Larger responses are truncated. | | status_code | integer | False | | [200, 598] | New HTTP status code in the response. If unset, falls back to the original status code. | | body | string | False | | | New response body. The `Content-Length` header would also be reset. Should not be configured with `filters`. | | body_base64 | boolean | False | false | | If true, decode the response body configured in `body` before sending to client, which is useful for image and protobuf decoding. Note that this configuration cannot be used to decode Upstream response. | diff --git a/t/plugin/request-validation.t b/t/plugin/request-validation.t index 790ccdea71b2..6d525a82254e 100644 --- a/t/plugin/request-validation.t +++ b/t/plugin/request-validation.t @@ -1866,3 +1866,56 @@ POST /echo duplicated content-type --- error_log duplicated Content-Type header + + + +=== TEST 57: add route with a small max_req_body_size +--- 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": { + "request-validation": { + "max_req_body_size": 10, + "body_schema": { + "type": "object", + "required": ["required_payload"], + "properties": { + "required_payload": {"type": "string"} + } + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/echo" + }]]) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 58: body larger than max_req_body_size is rejected +--- more_headers +Content-Type: application/json +--- request +POST /echo +{"required_payload": "this body is well over ten bytes"} +--- error_code: 400 +--- error_log +request size diff --git a/t/plugin/response-rewrite.t b/t/plugin/response-rewrite.t index fda43bedc6b7..bf9645ce8de9 100644 --- a/t/plugin/response-rewrite.t +++ b/t/plugin/response-rewrite.t @@ -733,3 +733,51 @@ GET /hello GET /t --- response_body passed + + + +=== TEST 28: filters with a small max_resp_body_size +--- 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": { + "response-rewrite": { + "max_resp_body_size": 5, + "filters": [ + { + "regex": "hello", + "scope": "once", + "replace": "HELLO" + } + ] + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uris": ["/hello"] + }]] + ) + + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 29: response body is truncated to max_resp_body_size before filtering +--- request +GET /hello +--- response_body chomp +HELLO