Repository navigation
v7.6.0
Bounded body reading: chunked requests are scanned again and response body rules work for the first time (v7.6.0)
Special thanks to @arpitjain099 for reporting GHSA-c2r5-9jw9-m8q5 and https://github.com/rennf93/fastapi-guard/security/advisories/GHSA-xv6g-49vj-7w9c ! Thank you for your reports, your clarity and cooperation. And for making fastapi-guard and guard-core more secure!
- Added -
StarletteGuardRequestimplements guard-core 3.12.0'sBoundedBodyReadercapability. A request without a usableContent-Length, which is everyTransfer-Encoding: chunkedrequest, previously reached penetration detection as an unreadable body and was waved through unscanned; with guard-core older than 3.12.0 the same request instead triggered the unbounded buffering that GHSA-c2r5-9jw9-m8q5 closed.read_body_prefixaggregates at mostmax_bytes(each chunk is sliced before it is collected, locked in by an allocation-tracing regression test); an oversized single ASGI message the server has already delivered is retained once, by reference, for replay, never duplicated. The replaying receive is installed before the first pull, so a timed-out read cannot lose captured data, and the downstream endpoint observes the byte-identical full body provided the middleware is the first reader of the request stream, which it is in every supported configuration (Starlette's request body is single-consumer; an outer ASGI middleware that partially consumes the stream first forfeits that prefix for everyone downstream, adapter or not). Both properties are locked in by end-to-end tests through a real FastAPI app: an attack in a chunked body is blocked with400 Suspicious activity detected, and a benign chunked body arrives at the endpoint whole. - Fixed - Body-based
return_patternrules have never worked in any shipped fastapi-guard; they work now, as an opt-in. Starlette'sBaseHTTPMiddleware.call_next()always returns a_StreamingResponse, whose constructor never sets.body; the adapter'sbodyproperty raisedAttributeError, guard-core's oldhasattr(response, "body")gate swallowed the exception, and every body rule silently evaluated to no-match whilestatus:rules kept working. Anyone who wrote a body rule got silence and a passing config, with no way to discover the rule was dead.StarletteGuardResponsenow implementsBoundedResponseBodyReaderas a tee: it reads the first streamed chunk (a non-streaming response is scanned whole) up tobehavior_max_response_body_inspect_bytes, never pulls a second chunk from an indefinite stream such as SSE, and replays everything captured so the client receives the response unchanged. End-to-end tests prove a matching body rule now escalates to a ban and a streaming response arrives intact while being scanned. - Behaviour (three cases, stated exactly) -
behavior_scan_response_bodydefaults to False, so nothing changes for anyone who does not opt in. A body-basedreturn_patternrule with the flag off now raisesValueErrorat config or decorator construction naming the offending pattern, instead of shipping a rule that can never match. Only a deliberate opt-in on an adapter without the protocol (any fastapi-guard before this release) skips the rule, with guard-core's throttled could-not-evaluate warning naming it. - Compatibility (lockstep, do not soften) - Upgrading guard-core to 3.12.0 without upgrading fastapi-guard to 7.6.0 silently drops every
return_patternbody rule even withbehavior_scan_response_body=Trueexplicitly set. Upgrading fastapi-guard first is safe in either order: with older guard-core the new adapter methods are simply never called. guard-core stays an unconstrained dependency inpyproject.tomlper this project's policy, so fresh installs already resolve 3.12.0; this release is what makes that pairing whole. - Compatibility (guard-core 3.12.0 breaking changes surfaced here) - Nine
SecurityConfigcollection fields are now immutable (whitelist,blacklist,trusted_proxiesas tuples;enabled_detection_categories,muted_event_types,muted_metric_types,muted_check_logs,block_cloud_providersas frozensets;threat_ban_configas a read-only mapping), andglobal_behavior_rulesis a tuple: in-place mutation raises, so reassign a whole new value instead; construction still accepts plain lists and sets.block_cloud_providersnow raisesValueErroron an unrecognized provider name instead of silently dropping it. fastapi-guard's own code, tests and examples were audited for both: nothing mutated these fields in place, and every reconfiguration already reassigns. - Fixed - Both example apps crashed at import under guard-core 3.12.0, and their
pattern="404"rules had never fired.examples/simple_appandexamples/advanced_appusedreturn_monitor(pattern="404")meaning HTTP status 404; under 3.12.0 that raises at decoration time, and as written the rule was also dead, a body substring match against a body that was never readable. Both now usestatus:404, which is what they always meant. The behavioral decorator tests opt intobehavior_scan_response_body=True, which makes them the first end-to-end exercise of body matching this repository has ever run. - Internal - The geo_ip_handler inert-config warning test asserted a warning guard-core 3.12.0 removed. The warning was undecidable at the
SecurityConfiglayer (route-level country lists, geo rate limits and dynamic rules all consume the handler without the global lists being set), so 3.12.0 removed it. The test file now asserts the warning's absence at both construction and middleware creation, renamed totest_geo_ip_handler_no_inert_warning.pyto describe the behavior it locks in. - Documentation - Field types updated for the immutable collections, the
exclude_pathsdescription corrected (detection and behavioral tracking are skipped on excluded paths; IP bans, blacklists, blocked countries, blocked cloud providers and rate limits are still enforced there), and every behavioral-rules example annotated with the flag and version requirement so no documented snippet raises under guard-core 3.12.0.
What's Changed
Full Changelog: 7.5.1...7.6.0