Rejecting Oversized and Malformed Payloads
The controls in this page are the cheapest security and stability work available at a gateway, and the ones most often left at their defaults. A body size limit, a header size limit, a parse depth limit and a timeout on reading the request together close a family of failures that range from a memory spike to a node falling over — and none of them require knowing anything about the application.
Prerequisite concepts
This sits under request validation and schema enforcement and applies the memory arithmetic from scaling limits and capacity planning.
Reject on the header, not on the byte count
# Kong 3.x / NGINX — the four limits, all of them
client_max_body_size 1m; # rejects from content-length with 413
client_body_buffer_size 16k; # above this, bodies spill to disk
client_header_buffer_size 4k;
large_client_header_buffers 4 8k; # total header budget per request
client_body_timeout 10s; # slow-body attacks end here
client_header_timeout 10s;
# Envoy 1.32+ — equivalents, per listener and per route
common_http_protocol_options:
max_headers_count: 100
headers_with_underscores_action: REJECT_REQUEST
max_request_headers_kb: 60
request_timeout: 15s # header-to-last-byte ceiling
# and per route, where the body is buffered for validation:
typed_per_filter_config:
envoy.filters.http.buffer:
"@type": type.googleapis.com/envoy.extensions.filters.http.buffer.v3.BufferPerRoute
buffer: { max_request_bytes: 1048576 }
request_timeout is the one most often missing. Without it, a client that sends headers and then dribbles a body one byte at a time holds a connection indefinitely — the classic slow-body attack, which needs no bandwidth and defeats a size limit entirely because the body never gets large.
Malformed is not the same as oversized
The duplicate-key case is the subtle one and worth dwelling on. If the gateway’s parser keeps the last occurrence and the service’s keeps the first, a request can pass validation with one value and be processed with another — a validation bypass that requires no cleverness beyond sending the same key twice.
Compression, and the limit that must be applied after it
A request with content-encoding: gzip is small on the wire and arbitrarily large after inflation. A size limit applied to the compressed bytes therefore protects nothing. Either apply a limit to the decompressed size, or refuse compressed request bodies on routes that do not need them — which is most of them.
# Envoy 1.32+ — bound the result of decompression, not the input
- name: envoy.filters.http.decompressor
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.decompressor.v3.Decompressor
decompressor_library:
typed_config:
"@type": type.googleapis.com/envoy.extensions.compression.gzip.decompressor.v3.Gzip
max_inflate_ratio: 100 # refuse anything that expands more than 100x
window_bits: 15
Decision matrix
| Route type | Body limit | Notes |
|---|---|---|
| JSON API, ordinary | 256 KB – 1 MB | generous for real payloads, useless for abuse |
| Bulk import | 10 MB+, streaming | do not buffer; validate at the service |
| File upload | multipart, streamed to storage | the gateway should not hold it |
| Webhook receiver | 64 KB | senders are known and small |
| GraphQL | 64 KB | a large query document is not legitimate |
Gotchas and failure signals
A limit that is too low is a support ticket; a limit that is absent is an outage. Start generous relative to real traffic — measure the 99.9th percentile body size and multiply by two — rather than guessing small.
413 must be returned before the body is read, or the client sees a connection reset instead and retries. A reset also loses the opportunity to tell the caller what the limit is.
Header limits apply to the total, not per header. A request with two thousand small cookies can exceed the budget without any single header being large.
Different limits at different hops produce a request rejected by an inner component with a status the outer one turns into a 502. Make the gateway’s limit the smallest in the path so the rejection is the one you designed.
Validation
- Body limit set per route, derived from measured payload sizes
- Header count and total header size limits set explicitly
- Request timeout set, and a slow-body client tested against it
- Compressed bodies either refused or bounded by inflated size
- Parse depth limited, and a deeply nested payload rejected in a test
- Duplicate-key handling verified to match between gateway and service
- Gateway limits are the smallest in the path, so 413 comes from where you expect
FAQ
Where should the size limit be enforced?
On the declared content length, before any body is read, so an oversized request costs one round trip and no memory. A limit that only trips after buffering means the bytes have already crossed the network and been held. Chunked requests declare no length, so a streaming ceiling is needed as well — both limits, not either.
What stops a slow-body attack?
A request timeout covering headers through last byte. A client that sends headers and then dribbles the body one byte at a time never triggers a size limit, because the body never becomes large; it simply holds a connection until something times out. Without that timeout there is nothing to time out, and a few thousand such clients exhaust the connection budget with negligible bandwidth.
Why does a 40 KB request sometimes use gigabytes of memory?
Compression. A body sent with content-encoding gzip is small on the wire and arbitrarily large after inflation, so a limit applied to the compressed bytes protects nothing. Either refuse compressed request bodies on routes that do not need them, or bound the inflated size with a maximum expansion ratio.
Why do duplicate JSON keys matter?
Because the behaviour is implementation-defined and the gateway and the service may not agree. If one parser keeps the first occurrence and the other keeps the last, a request can pass validation with one value and be processed with another — a validation bypass that needs nothing more sophisticated than sending the same key twice. Reject duplicates outright.
Parent: Request Validation & Schema Enforcement
Related
- Request Validation & Schema Enforcement — the schema layer these limits sit in front of.
- Scaling Limits & Capacity Planning — the memory arithmetic that makes buffering limits necessary.
- Rewriting JSON Payloads on the Fly — buffering versus streaming for transformation, with the same trade-offs.