fix(webhook): enforce body-size limit when Content-Length is absent - #56829
fix(webhook): enforce body-size limit when Content-Length is absent#56829Tamsi wants to merge 1 commit into
Conversation
The generic webhook adapter only checked ``request.content_length`` before reading the body, so a request using chunked transfer-encoding (or a spoofed small Content-Length) reported ``content_length is None`` and skipped the guard entirely. The full body was then buffered regardless of the route's ``max_body_bytes`` — a size-limit bypass reachable by an unauthenticated caller (the body must be read before HMAC verification), and a DoS vector on an internet-exposed endpoint. Re-check the limit against the bytes actually read, mirroring the Feishu and WeCom webhook adapters (which already guard the missing-Content-Length case and have regression tests for it). The module docstring is updated to match the real guarantee. Adds a regression test that drives a chunked-style request (Content-Length None) with an over-limit body and asserts 413.
Competing-PR cluster for the same generic-webhook chunked-body bypass: this PR (post-read |
|
Thanks @Tamsi — reviewed against current |
Summary
The generic webhook adapter (
gateway/platforms/webhook.py) enforced its per-routemax_body_byteslimit only via theContent-Lengthheader:A request using
Transfer-Encoding: chunked(or a spoofed smallContent-Length) makesrequest.content_lengthNone, soNone or 0 == 0and the check is skipped entirely. The full body is then buffered into memory regardless of the route's configuredmax_body_bytes.This is reachable by an unauthenticated caller — the body must be read before HMAC signature verification — so it bypasses the documented size guard and is a DoS vector on an internet-exposed webhook endpoint. The module docstring even claims "Body size limits checked before reading payload", which chunked requests defeat.
The sibling adapters Feishu (
plugins/platforms/feishu/adapter.py) and WeCom already guard this exact case with a post-read length check and have regression tests forcontent_length=None— the generic adapter was the odd one out.Fix
len(raw_body) > self._max_body_bytes→ 413), keeping theContent-Lengthfast path.Content-LengthNone) with an over-limit body, asserting 413.Test plan
tests/gateway/test_webhook_adapter.py— 69 passed (68 existing + 1 new)test_webhook_adapter,test_webhook_signature_rate_limit,test_webhook_integration,test_webhook_dynamic_routes,test_webhook_deliver_only) — 103 passed202instead of413) and passes with it