Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion gateway/platforms/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ async def connect(self) -> bool:
f"For testing without auth, set secret to '{_INSECURE_NO_AUTH}'."
)

app = web.Application()
app = web.Application(client_max_size=self._max_body_bytes)
app.router.add_get("/health", self._handle_health)
app.router.add_post("/webhooks/{route_name}", self._handle_webhook)

Expand Down Expand Up @@ -270,9 +270,17 @@ async def _handle_webhook(self, request: "web.Request") -> "web.Response":
# Read body
try:
raw_body = await request.read()
except web.HTTPRequestEntityTooLarge:
return web.json_response(
{"error": "Payload too large"}, status=413
)
except Exception as e:
logger.error("[webhook] Failed to read body: %s", e)
return web.json_response({"error": "Bad request"}, status=400)
if len(raw_body) > self._max_body_bytes:
return web.json_response(
{"error": "Payload too large"}, status=413
)

# Validate HMAC signature (skip for INSECURE_NO_AUTH testing mode)
secret = route_config.get("secret", self._global_secret)
Expand Down
25 changes: 24 additions & 1 deletion tests/gateway/test_webhook_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def _make_adapter(routes=None, **kwargs):

def _create_app(adapter: WebhookAdapter) -> web.Application:
"""Build the aiohttp Application from the adapter (without starting a full server)."""
app = web.Application()
app = web.Application(client_max_size=adapter._max_body_bytes)
app.router.add_get("/health", adapter._handle_health)
app.router.add_post("/webhooks/{route_name}", adapter._handle_webhook)
return app
Expand Down Expand Up @@ -516,6 +516,29 @@ async def test_oversized_payload_rejected(self):
)
assert resp.status == 413

@pytest.mark.asyncio
async def test_chunked_oversized_payload_rejected(self):
"""Chunked request bodies over the limit still return 413."""
routes = {"big": {"secret": _INSECURE_NO_AUTH, "prompt": "test"}}
adapter = _make_adapter(routes=routes, max_body_bytes=100)
adapter.handle_message = AsyncMock()

async def _chunked_body():
payload = json.dumps({"data": "x" * 500}).encode("utf-8")
for i in range(0, len(payload), 64):
yield payload[i : i + 64]
await asyncio.sleep(0)

app = _create_app(adapter)
async with TestClient(TestServer(app)) as cli:
resp = await cli.post(
"/webhooks/big",
data=_chunked_body(),
headers={"Content-Type": "application/json"},
)
assert resp.status == 413
adapter.handle_message.assert_not_awaited()


# ===================================================================
# INSECURE_NO_AUTH
Expand Down
Loading