Skip to content

v7.6.0

Choose a tag to compare

@rennf93 rennf93 released this 14 Aug 02:34
· 60 commits to master since this release
14d537c

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 - StarletteGuardRequest implements guard-core 3.12.0's BoundedBodyReader capability. A request without a usable Content-Length, which is every Transfer-Encoding: chunked request, 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_prefix aggregates at most max_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 with 400 Suspicious activity detected, and a benign chunked body arrives at the endpoint whole.
  • Fixed - Body-based return_pattern rules have never worked in any shipped fastapi-guard; they work now, as an opt-in. Starlette's BaseHTTPMiddleware.call_next() always returns a _StreamingResponse, whose constructor never sets .body; the adapter's body property raised AttributeError, guard-core's old hasattr(response, "body") gate swallowed the exception, and every body rule silently evaluated to no-match while status: rules kept working. Anyone who wrote a body rule got silence and a passing config, with no way to discover the rule was dead. StarletteGuardResponse now implements BoundedResponseBodyReader as a tee: it reads the first streamed chunk (a non-streaming response is scanned whole) up to behavior_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_body defaults to False, so nothing changes for anyone who does not opt in. A body-based return_pattern rule with the flag off now raises ValueError at 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_pattern body rule even with behavior_scan_response_body=True explicitly 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 in pyproject.toml per 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 SecurityConfig collection fields are now immutable (whitelist, blacklist, trusted_proxies as tuples; enabled_detection_categories, muted_event_types, muted_metric_types, muted_check_logs, block_cloud_providers as frozensets; threat_ban_config as a read-only mapping), and global_behavior_rules is a tuple: in-place mutation raises, so reassign a whole new value instead; construction still accepts plain lists and sets. block_cloud_providers now raises ValueError on 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_app and examples/advanced_app used return_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 use status:404, which is what they always meant. The behavioral decorator tests opt into behavior_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 SecurityConfig layer (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 to test_geo_ip_handler_no_inert_warning.py to describe the behavior it locks in.
  • Documentation - Field types updated for the immutable collections, the exclude_paths description 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

  • release(v7.6.0): bounded body reading for guard-core 3.12.0 by @rennf93 in #119

Full Changelog: 7.5.1...7.6.0