Repository navigation
Refuse oversized request bodies before buffering them - #10
Draft
ethanzhoucool wants to merge 2 commits into
Draft
ethanzhoucool wants to merge 2 commits into
ethanzhoucool wants to merge 2 commits into
Conversation
Nothing capped the size of a request body. The `code` validators only run after Starlette has read and JSON-parsed the whole body, so a multi-gigabyte POST was held in memory in full just to be rejected, which is enough to take down a small single-worker instance. /v1/detect had no size check at all and ran detection on whatever it got: 10MB took 3s. A small ASGI middleware now returns 413 for a declared Content-Length over 2MB (ten times the 200KB source limit, which leaves room for worst-case JSON escaping) and counts chunked bodies as they arrive. DetectRequest gets the same `code` limit as ExplainRequest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UigC7feaY8NkAy9oay3GDW
Annotating with `Any` instead of `starlette.types` keeps this change off the import block that the SSE threadpool change also edits, so the two can merge in either order without a conflict. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UigC7feaY8NkAy9oay3GDW
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Nothing limited the size of a request body.
ExplainRequest's 200 KBcodevalidator only runs after Starlette has buffered and JSON-parsed the whole body, and uvicorn sets no body limit of its own. So a multi-gigabyte POST to any/v1/*endpoint is held in memory in full just to be rejected. On a small single-worker instance that is an easy way to OOM the service.Separately,
DetectRequesthad nocodelimit at all. Measured onmain,POST /v1/detectwith 10 MB of code returns 200 after 3.1 s of detection work. With the regex fix in #1 that cost is linear; without #1 it is far worse.Changes
api/main.py: a small pure-ASGIBodySizeLimitmiddleware with a limit ofMAX_BODY_BYTES = 10 * MAX_SOURCE_BYTES(2 MB).Content-Lengthover the limit, it gets413 {"error": "payload_too_large", ...}before anything is read.Content-Lengthis counted as it arrives. Once it passes the limit the middleware raisesHTTPException(413)fromreceive, while the endpoint is reading its body, and FastAPI turns that into a normal 413 response.\u0001), so the largest legitimate 200 KB snippet can approach 1.2 MB. A test pins that case.api/schemas.py:DetectRequest.codegets the same size validator asExplainRequest, factored into one helper. Oversized detect requests now return 422, which is what/v1/explainalready returns./v1/analyzealready returned 413 viaSourceTooLargeand is unchanged.Testing
pyteston Python 3.13, 3.12 and 3.11: 151 passed, 3 skipped.ruffis clean, andmypyreports the same 26 errors as before.tests/test_api.py. The first three fail onmain:test_oversized_body_is_refused_before_it_is_read: a 3 MB body with Content-Length gets 413.test_oversized_chunked_body_is_refused: a 3 MB generator body (chunked) gets 413.test_detect_enforces_the_source_limit: 360 KB to/v1/detectgets 422.test_largest_legitimate_snippet_still_fits_the_body_limit: 199,999 control characters (a 1.19 MB JSON body) still get 200.uvicornlocally and sent it a 5 MB body withcurl: 413 with Content-Length, 413 withTransfer-Encoding: chunked, and 200 for a normal request. The server was stopped afterwards.Notes
The in-process rate limiter keys on
request.client.host. Behind Render's proxy that may be the proxy's address rather than the user's, and then all visitors share one 60-requests-per-minute bucket. I couldn't verify Render's networking from here, so I left it alone. If it applies, uvicorn's--forwarded-allow-ips(or readingX-Forwarded-For) would fix it.Generated by Claude Code