feat(ai-service): enforce HTTP request body size limit (fixes #137)#192
Merged
kilodesodiq-arch merged 1 commit intoJun 22, 2026
Conversation
…rgee#137) Adds MaxRequestBodySizeMiddleware as the outermost raw ASGI middleware on the FastAPI app, rejecting oversized POST/PUT/PATCH payloads with HTTP 413 before the body is read into memory. - New MAX_REQUEST_BODY_BYTES setting (default 10485760 = 10 MiB) driven by pydantic-settings env var; REQUEST_BODY_BYPASS_PATHS lets operators opt specific endpoints out of the limit (exact match by default, trailing '/' for prefix match). Health/docs/metrics are always bypassed. - Eager Content-Length short-circuits with a 413 if the declared size exceeds the cap; an HTTPBodyTooLarge signal from the receive-wrap stream counter is converted into a 413 once the cap is breached on chunked bodies. Path is bypass at the same level as monitor_requests' NEVER throttle list. - 413 responses use the project's ErrorEnvelope shape for consistency with every other error handler and include precise wording distinguishing declared-size from streamed-size rejection. Each rejection logs a structured warning (reason=declared_size|streamed_size) for ops/SIEM correlation. - 17 new pytest cases cover eager, streamed, malformed-Content-Length, GET/HEAD passthrough, bypass exact/prefix, disabled limit, and real main.app wiring (full suite: 17 new pass, 0 regressions in the affected files). Closes ChainForgee#137
Contributor
|
Thanks @gbengaeben — clean implementation of the body-size middleware. AI Service CI is green and the change targets the DoS surface mentioned in #137. Merging now. |
7 tasks
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
Closes #137. Adds a configurable HTTP request body size limit to the AI service to mitigate the memory-exhaustion DoS vector flagged in the security review.
The new
MaxRequestBodySizeMiddlewareis mounted as the outermost raw ASGI middleware on the FastAPI app, so oversized payloads are rejected before any other middleware, route handler, or framework code can buffer the body. Both attack paths are covered:Content-Lengthlarger than the cap) → eager 413 before any bytes are read off the wire.Content-Length, body streams past the cap) → bytes counted via a wrappedreceivecallable; first over-cap chunk raises an internalHTTPBodyTooLargesignal that is caught and converted into a 413 response.413 responses use the project's
ErrorEnvelopeshape (same contract as every other handler) and include precise wording that distinguishes declared-size from streamed-size rejection. Each rejection emits a structuredlogger.warningcarryingmethod,path, byte count, limit, client IP, and rejection reason so ops/SIEM can correlate attack patterns.Files
app/ai-service/config.pymax_request_body_bytes(default10485760= 10 MiB) andrequest_body_bypass_pathssettings, with env-var docstring describing exact-match vs. trailing-slash prefix semantics.app/ai-service/main.pyHTTPBodyTooLargeexception andMaxRequestBodySizeMiddlewareclass, wired viaapp.add_middleware(...). Health/docs/metrics paths are always bypassed. Disabled whenmax_request_body_bytes <= 0.app/ai-service/README.md0 disablescaveat.app/ai-service/tests/test_request_body_limit.pyTests
Coverage:
Content-Lengthfalls through cleanly without crashing ✅GET/HEADare not subject to the limit ✅/healthand other infrastructure paths are bypassed regardless of cap ✅bypass_prefixeswork for both exact match and/prefix/match ✅max_request_body_bytes=0disables the middleware cleanly ✅ErrorEnvelopeshape, with the correct wording for the streamed vs declared branches ✅main.appis wired with the expected 10 MiB cap (wiring test) ✅Content-Length→ 413 ✅Regression check on the files my middleware touches (
test_main.py,tests/test_versioned_routes.py): 0 regressions. (There are 8 pre-existing failures intest_ocr.py,test_preprocessing.py, andtests/test_routes.py::TestHealthDependenciesEndpoint, all of which fail on unmodifiedmainand are unrelated to this change — verified bygit stash+ retest.)Configuration
MAX_REQUEST_BODY_BYTES10485760(10 MiB)0disables (not recommended in production).REQUEST_BODY_BYPASS_PATHS'/'must match the path exactly; entries with a trailing'/'match any path with that prefix. The defaults (/health,/,/ai/metrics,/docs,/redoc,/openapi.json) are always merged in.Security & ops notes
_is_bypassedwas treating/(the root discovery endpoint) as a prefix wildcard; since every URL starts with/, every request was being bypassed. Fixed by excluding exactly/from prefix matching while keeping its exact-match behavior.return await self._send_413(...)was mis-indented and would have fired on everyContent-Length-bearing request once bug REPO-001 [CRITICAL] Remove hardcoded encryption fallback key #1 was fixed. Re-indented so it only fires insideif declared > self.max_bytes:.POST,PUT,PATCH— the only methods with bodies.GET,HEAD,OPTIONS,DELETE, etc. are passed through untouched.MAX_REQUEST_BODY_BYTESper request rather than by the size of the largest body the server has ever buffered.Test commands