Skip to content

Releases: Guard-Core/fastapi-guard

v8.1.0

Choose a tag to compare

@rennf93 rennf93 released this 10 Oct 15:02
Immutable release. Only release title and notes can be modified.
v8.1.0
fa501d8

v8.1.0 (2026-10-10)

The pure-ASGI release: the full screening pipeline without BaseHTTPMiddleware (v8.1.0)

Added

  • PureASGISecurityMiddleware: the full screening pipeline on raw ASGI (fastapi-guard #156). Runs initialization, CORS preflight, path exclusions, IP extraction, ban lists, rate limiting, user-agent blocking, penetration detection and behavioral usage rules without BaseHTTPMiddleware - no anyio task group wraps the downstream app, so client disconnects can never cancel in-flight database calls, and responses stream through unbuffered. Body reads go through a caching receive that replays to the application; security and CORS headers are injected directly on http.response.start; WebSocket and lifespan scopes pass through untouched. Built for frameworks that ban BaseHTTPMiddleware by policy (open-webui carries a written rationale in main.py) and for the RFC thread asking for exactly this shape.
  • Scope note: decorator-driven response rules (return patterns) and custom_response_modifier need a materialized Response object and stay on the classic middleware; both classes ship side by side and the classic one is unchanged.

v8.0.3

Choose a tag to compare

@rennf93 rennf93 released this 08 Oct 12:03
Immutable release. Only release title and notes can be modified.
f47a3ba

Metadata release for the Guard-Core org migration: PyPI project URLs, documentation canonical domain, and repository links now point at Guard-Core. No code changes.

v8.0.2

Choose a tag to compare

@rennf93 rennf93 released this 26 Sep 13:20
v8.0.2
d520c94

v8.0.2 (2026-09-26)

Community fixes and exempt_ips lockstep: trailing-slash route config, block response Content-Type, live-smoke exempt_ips scenario; guard-core 4.1.0 tracking (v8.0.2)

  • Fixed - Route decorator config now resolves through Starlette's trailing-slash redirect (#142): when the request path matches no route but toggling the trailing slash does, the middleware reads the config of the route Starlette would 307-redirect to, so a route override registered on /path now governs a request to /path/ (and vice versa) instead of silently falling through to the global defaults. Each router decides with its own redirect_slashes (mounted apps for their paths, the including app for an included router's routes), the root path and directly matched requests are untouched.
  • Fixed - Block responses now declare Content-Type: text/plain (#144): the Starlette response factory left Starlette's media type unset, so middleware-generated 403/429/400 pages went out without a content type and clients were left guessing. The factory now passes media_type="text/plain", matching the engine's reference response factory.
  • Tests - New live-smoke scenario tests/live_smoke/scenarios/exempt_ips.py (#145) covering the guard-core exempt_ips skip-list end to end through the live stack: an exempt IP exceeding rate_limit keeps getting 200 across the crossing while no rate-limit bucket is written for it at all (smoke:rate_limit:rate:<ip> stays absent in Redis, pinning that the exempt skip sits ahead of the limiter), a penetration-detection payload from the exempt IP still gets 400 (exemption is not immunity), the identical sequence without exempt_ips throttles at the crossing with 429 and leaves exactly the bucket the exempt run never wrote, and an IP that is both exempt and blacklisted gets 403 (blacklist beats exemption). The scenario merged red against PyPI guard-core (< 4.1.0) by design, the documented lockstep hold; with this release it runs green against the published 4.1.0 engine in CI.
  • Compatibility - The guard-core floor rises from >=4.0.0 to >=4.1.0 and this release tracks the guard-core 4.1.0 engine (the exempt_ips skip-list, the recon leading-separator gate, the raw-view recon scan, the cross-engine interop harness). Both fixed behaviors are adapter-side; no guard-core API fastapi-guard uses changed shape.

v8.0.1

Choose a tag to compare

@rennf93 rennf93 released this 23 Sep 23:33
8.0.1
227222a

CI hardening, live smoke suite, agent docs; guard-core 4.0.4 tracking (v8.0.1)

  • CI - Added a DockSec container scan with SARIF upload to the pipeline (#138) and removed the Slack notification steps from all workflows.
  • CI - New live smoke suite (tests/live_smoke/): a Docker Compose stack (nginx, gunicorn, Redis) running the full example app is exercised against PRs, and a nightly upstream drift gate builds the suite against guard-core master so engine-side regressions surface before a release instead of after (#134).
  • Fixed (tests) - test_cloud_ip_blocking_with_logging (tests/test_middleware/test_security_middleware.py) now awaits the scheduled background cloud-IP refresh before asserting instead of relying on a stale Redis handler left behind by another test to suspend the dispatch (#136).
  • Documentation - Added AGENTS.md/CLAUDE.md agent guidance and extended the fastapi-guard package skill.
  • Compatibility - No production code changed: the delta since 8.0.0 is CI, tests and docs only, hence the patch bump. The guard-core>=4.0.0 floor is unchanged and this release tracks the guard-core 4.0.4 engine.

v8.0.0

Choose a tag to compare

@rennf93 rennf93 released this 04 Sep 19:33
bf9b9b9

Lockstep with guard-core 4.0.0: log_activity assertions, timestamp serialisation, unban wiring, throttle docs, hsts None guard (v8.0.0)

  • Compatibility (dependency floor, breaking for anyone pinned below 4.0.0) - guard-core floor moves from >=3.14.0 to >=4.0.0 in pyproject.toml, no upper bound. guard-core 4.0.0 carries breaking changes of its own (grammar-based secret redaction, require_headers() now enforces an exact value match for any configured value other than "required", excluded_detection_headers no longer fully silences a header) which do not require any change in this release's own code or tests, since fastapi-guard's require_headers() usage in its examples and tests only ever exercises the presence-only "required" value or sends a header value matching what is configured, and neither fastapi-guard's code nor its tests set excluded_detection_headers. See the guard-core 4.0.0 changelog for the full engine-side list.
  • Requires (lockstep) - fastapi-guard 8.0.0 tracks guard-core 4.0.0. guard-core 4.0.0 is not yet published to PyPI as of this release commit; CI resolves guard-core>=4.0.0 fresh from PyPI (uv.lock is gitignored) and will fail until it is. This release is held locally, matching the pattern set by earlier lockstep releases, until guard-core 4.0.0 ships.
  • Tests - test_cloud_ip_blocking_with_logging and test_passive_mode_penetration_detection (tests/test_middleware/test_security_middleware.py) pinned the full log_activity() call via assert_any_call, which broke against guard-core >=3.16.0's added on_block/sensitive_headers/sensitive_params/sensitive_body_fields keyword arguments. Both now check only the kwargs each test cares about as a subset of the actual call (assert_log_activity_called_with), tolerating any further keyword arguments guard-core adds.
  • Fixed (examples) - http_exception_handler and general_exception_handler (examples/advanced_app/app/main.py) dumped ErrorResponse with model_dump(), leaving timestamp as a raw datetime that JSONResponse cannot encode, turning every error response (for example /behavior/return-monitor/404) into an unhandled 500. Both now use model_dump(mode="json").
  • Fixed (examples) - /admin/unban-ip (examples/advanced_app/app/routes/admin.py) only logged the unban and never called guard-core's IP ban manager, so a banned IP stayed banned. The route now calls ip_ban_manager.unban_ip(ip).
  • Documentation (examples) - action="throttle" only logs the violation; guard-core never blocks or delays the request for it. Corrected the /behavior/suspicious-frequency and /behavior/behavior-rules route descriptions and dropped the fabricated 429 response entries that promised otherwise, and corrected the matching lines in docs/tutorial/decorators/behavioral.md and docs/api/behavior-manager.md.
  • Documentation (examples) - /behavior/usage-monitor's responses entry named a "block" action that does not exist in guard-core (the route uses action="log" and never returns 429); the entry is removed. alert was documented as sending a notification; it is a critical-level log call, same as any other action's telemetry event. Corrected in examples/advanced_app/app/routes/behavioral.py and docs/api/behavior-manager.md.
  • Fixed - SecurityMiddleware._configure_security_headers (guard/middleware.py) called headers_config.get("hsts", {}).get("max_age"), which raised AttributeError on every request when security_headers={"hsts": None, ...} was passed explicitly, since .get("hsts", {}) returns None, not {}, for a key present with an explicit None value. Now falls back to {} the same way guard-core's own security_headers_handler treats a missing hsts block. guard-core 4.0.0 separately fixes the same class of bug on its own singleton (a stale hsts/csp/cors configuration surviving a reconfigure), so this guard is defense in depth against both the old and the fixed guard-core behavior.
  • Security - guard_websocket and make_guard_websocket (guard/websocket.py) ran only the IP-ban and rate-limit checks on a WebSocket handshake; an attack riding in the handshake's headers, cookies, query string or path was never detected and never logged. The handshake is now scanned the same way an HTTP request is scanned, through guard-core's own SuspiciousActivityCheck, honouring enable_penetration_detection, passive_mode, exclude_paths and the whitelist and auto-ban rules already in place; a detected attack in active mode closes the connection before accept() with the new WS_CLOSE_SUSPICIOUS_ACTIVITY close reason (code 1008, collected in WS_CLOSE_REASONS), logs and emits the same event and on_block payload the HTTP pipeline emits, and redacts sensitive header, query and cookie values exactly as the HTTP pipeline does; passive mode logs and emits without closing the connection.

What's Changed

  • test: assert guard-core 3.15.0 redis_fail_open semantics by @rennf93 in #127
  • ci: issue-first workflow and Discord contact points by @rennf93 in #125
  • build(deps): bump actions/github-script from 7 to 9 by @dependabot[bot] in #128
  • release(v8.0.0): lockstep with guard-core 4.0.0, example and middleware fixes by @rennf93 in #130

Full Changelog: 7.8.2...8.0.0

v7.8.2

Choose a tag to compare

@rennf93 rennf93 released this 27 Aug 15:03
4dff311

Route-relative path resolution under root_path and mounts, Starlette and FastAPI floors (v7.8.2)

  • Fixed - StarletteGuardRequest.url_path and the WebSocket adapter's _WebSocketGuardRequest.url_path returned request.url.path / websocket.url.path, which under an ASGI root_path (uvicorn --root-path, or a mounted sub-app) includes the mount prefix. guard-core matches that value against SecurityConfig.exclude_paths and endpoint_rate_limits (an exact dict-key match) and passes it to custom_request_check hooks, so a key such as /api/vault/login never matched a request actually served at /mounted/api/vault/login and every path-keyed control silently stopped applying under a mount. Both adapters now return Starlette's own get_route_path(scope), the same route-relative path Starlette's router already matches routes against.
  • Compatibility - If exclude_paths or endpoint_rate_limits keys currently include your root_path mount prefix (a workaround for the bug above), remove the prefix after upgrading: those keys now match the route-relative path and a prefixed key silently stops matching. The same applies to custom_request_check hooks that branch on url_path.
  • Compatibility - starlette>=0.35.0 and fastapi>=0.109.0 are now declared floors (no ceilings). Route-relative path resolution uses starlette.routing.get_route_path, which Starlette added in 0.35.0; 0.109.0 is the first FastAPI release that requires that Starlette. Older pairs fail at import guard with an ImportError.
  • Changed - Detection input and the reason text of the Suspicious activity detected log line now use the route-relative path, while the GET <url> part of the same line still shows the full URL including the mount prefix (url_full is unchanged), so the two differ under a mount by design.

What's Changed

  • release(v7.8.2): route-relative path resolution and dependency floors by @rennf93 in #123

Full Changelog: 7.8.1...7.8.2

v7.8.1

Choose a tag to compare

@rennf93 rennf93 released this 27 Aug 02:22
2f21bab

Websocket guard follow-ups: whitelist parity, explicit-config factory, close-code contract, state.client_ip (v7.8.1)

  • Added - make_guard_websocket(config: SecurityConfig, redis_handler: RedisManager | None = None) returns a Depends-ready WebSocket dependency bound to an explicit SecurityConfig, for a route on an app that never calls app.add_middleware(SecurityMiddleware, config=...); guard_websocket is unchanged and still resolves its config from the registered middleware, raising RuntimeError without one. Both entry points share one private check coroutine, so every rule applies to both.
  • Added - guard exports module-level (code, reason) constants for the five WebSocket close outcomes guard_websocket/make_guard_websocket can emit (WS_CLOSE_IP_BANNED, WS_CLOSE_IP_NOT_ALLOWED, WS_CLOSE_RATE_LIMIT_EXCEEDED, WS_CLOSE_CLIENT_ADDRESS_UNKNOWN, WS_CLOSE_SECURITY_CHECK_FAILED, collected in WS_CLOSE_REASONS), documented as a stable contract in docs/tutorial/websockets.md that a close handler can key logging on.
  • Added - websocket.state.client_ip is now set to the resolved client identity (including "unknown") immediately after resolution, before any check runs, so a route handler can read it after accept().
  • Fixed - guard_websocket and make_guard_websocket now skip the rate-limit check for a client that passes a configured global whitelist, mirroring guard-core's HTTP rule (RateLimitCheck.check returns early once a request is resolved as whitelisted); previously a whitelisted WebSocket client could still be rate-limited.
  • Fixed - The websocket rate-limit check no longer constructs a RedisManager itself. RedisManager is a process-wide singleton; constructing one with a different SecurityConfig rebinds its connection for every other caller in the process, so guard_websocket previously risked overwriting the HTTP pipeline's own Redis manager, and make_guard_websocket on an app without SecurityMiddleware could construct an uninitialized manager and raise GuardRedisError on every connect. guard_websocket now resolves the middleware's already-initialized manager instead of constructing one; make_guard_websocket takes an optional redis_handler parameter and falls back to the in-memory store (logging one warning at dependency creation) when enable_redis=True and none is given.

What's Changed

  • release(v7.8.1): websocket guard follow-ups by @rennf93 in #122

Full Changelog: 7.8.0...7.8.1

v7.8.0

Choose a tag to compare

@rennf93 rennf93 released this 26 Aug 19:52
eb5f284

Lockstep release tracking guard-core 3.14.0: missing-client rejection, forwarded-header joining, clean Redis-down responses, and WebSocket protection (v7.8.0)

Special thanks to @RomanPszonka for the reports and advisories behind this release and the support throughout. Thank you for making the Guard Core ecosystem better!

  • Compatibility (dependency floor, breaking for anyone pinned below 3.14.0) - guard-core changes from an unconstrained dependency to guard-core>=3.14.0 in pyproject.toml. pip install --upgrade fastapi-guard alone could previously leave an older guard-core in place; against guard-core 3.12.0 that is an ImportError at import guard time (check_ip_access/check_rate_limit_by_ip/is_ip_allowed, added in 3.13.0). No upper bound; the floor moves with each lockstep release (fg #120).
  • Security (GHSA-8xvm-856x-7hwp) - StarletteGuardRequest.headers now joins repeated field lines (RFC 7230 5.2) before guard-core resolves the client from X-Forwarded-For; previously only the first line was visible, letting a client's own forged line hide the real proxy-appended one and rotate past rate limiting.
  • Compatibility (GHSA-634g-4wr8-xwxv) - A request with no client address is now rejected by default (fail_secure=True) instead of skipping the entire security pipeline; this is guard-core 3.14.0's own fix, fastapi-guard's test suite and docs are brought in line with it here. New "unix" trusted_proxies token for Unix-socket and serverless deployments.
  • Fixed (#76) - SecurityMiddleware no longer crashes with an unhandled 500 (or fails app startup) when Redis is unreachable at initialization. dispatch now catches GuardRedisError and returns a clean 503 with Retry-After: 5, retrying on the next request; the lifespan warmer logs and continues instead of crashing boot.
  • Security (GHSA-63qv-gh36-52qf) - New guard_websocket dependency (Depends(guard_websocket)) for @app.websocket routes: SecurityMiddleware never runs for WebSocket scopes (BaseHTTPMiddleware skips non-HTTP), so a banned or blacklisted IP, or one over its rate limit, previously reached accept() unchecked. The example app's "WebSocket protection" claim is now true. A Redis failure during the handshake now follows redis_fail_open and fail_secure the same way the HTTP pipeline does, instead of leaking an unhandled GuardRedisError; a fail-secure refusal closes the connection with code 1013.
  • Added (guard-core #81 item 3) - add_status_route now accepts dependencies=[...] (FastAPI apps only), so the opt-in /_guard/status route can be gated behind an existing auth dependency instead of relying solely on proxy-level restriction.
  • Compatibility (lockstep) - fastapi-guard 7.8.0 tracks guard-core 3.14.0. guard-core 3.14.0 is not yet published to PyPI as of this release commit; CI resolves guard-core>=3.14.0 fresh from PyPI (uv.lock is gitignored) and will fail until it is. This commit is held locally, matching the pattern set by earlier lockstep releases, until guard-core 3.14.0 ships. See guard-core 3.14.0's release notes for the full engine-side list: bounded in-memory rate-limit and behavior-tracker stores, float anomaly statistics, a per-request scan value cap, the AzureCloud tag fix, GeoIP last-known-good on refresh failure, telemetry secret redaction, ban-address canonicalization, and new SSRF detection forms.

What's Changed

  • release(v7.8.0): lockstep with guard-core 3.14.0 by @rennf93 in #121

Full Changelog: 7.7.0...7.8.0

v7.7.0

Choose a tag to compare

@rennf93 rennf93 released this 25 Aug 05:51
ba7fbfb

Lockstep release tracking guard-core 3.13.0: detection hardening and the breaking auth-verifier requirement (v7.7.0)

Special thanks to @hsnyus-09 and @iam-niranjan for your GHSA reports and the support throughout. Thank you for making the Guard Core ecosystem and FastAPI Guard better!

  • Security (auth-verifier, breaking) - require_auth(type="bearer", verifier=None) and api_key_auth(header_name=..., verifier=None) now require a resolvable verifier, supplied per route via verifier= or globally via SecurityConfig.auth_verifier; without one the request is rejected with 401 fail-closed. Previously a bare Bearer/Basic prefix or any API-key header value was accepted without validation. The old presence-only behavior is now the separate decorator require_authorization_header(scheme="bearer"), documented as NOT authentication and mutually exclusive with the two auth decorators. The verifier contract is verifier(request, credential) -> Principal | None (sync or async in ASGI, sync-only in WSGI), and the authenticated principal lands on request.state.auth_principal. See GHSA-x96c-fcg2-x2f9, CWE-287.
  • Compatibility (lockstep) - fastapi-guard 7.7.0 tracks guard-core 3.13.0; guard-core remains an unconstrained dependency in pyproject.toml per project policy so fresh installs already resolve 3.13.0; this release is what makes that pairing whole. See guard-core 3.13.0's release notes (its CHANGELOG.md 3.13.0 section / release notes) for the full list, which covers the ReDoS validation backstop, the scan-window mechanism, a new deserialization category (CWE-502), all-category ingestion-bypass closures, and detection widenings.
  • Adapter - fastapi-guard itself adds no new security logic: it re-exports guard-core's surface unchanged, and the auth methods ride on the already-re-exported SecurityDecorator, so there is no adapter-side code change beyond the version and dependency pairing.

Full Changelog: 7.6.0...7.7.0

v7.6.0

Choose a tag to compare

@rennf93 rennf93 released this 14 Aug 02:34
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