Release Notes¶
v8.0.0 (2026-09-04)¶
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-corefloor moves from>=3.14.0to>=4.0.0inpyproject.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_headersno longer fully silences a header) which do not require any change in this release's own code or tests, since fastapi-guard'srequire_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 setexcluded_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.0fresh from PyPI (uv.lockis 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_loggingandtest_passive_mode_penetration_detection(tests/test_middleware/test_security_middleware.py) pinned the fulllog_activity()call viaassert_any_call, which broke against guard-core >=3.16.0's addedon_block/sensitive_headers/sensitive_params/sensitive_body_fieldskeyword 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_handlerandgeneral_exception_handler(examples/advanced_app/app/main.py) dumpedErrorResponsewithmodel_dump(), leavingtimestampas a rawdatetimethatJSONResponsecannot encode, turning every error response (for example/behavior/return-monitor/404) into an unhandled 500. Both now usemodel_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 callsip_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-frequencyand/behavior/behavior-rulesroute descriptions and dropped the fabricated429response entries that promised otherwise, and corrected the matching lines indocs/tutorial/decorators/behavioral.mdanddocs/api/behavior-manager.md. - Documentation (examples) -
/behavior/usage-monitor'sresponsesentry named a"block"action that does not exist in guard-core (the route usesaction="log"and never returns 429); the entry is removed.alertwas documented as sending a notification; it is a critical-level log call, same as any other action's telemetry event. Corrected inexamples/advanced_app/app/routes/behavioral.pyanddocs/api/behavior-manager.md. - Fixed -
SecurityMiddleware._configure_security_headers(guard/middleware.py) calledheaders_config.get("hsts", {}).get("max_age"), which raisedAttributeErroron every request whensecurity_headers={"hsts": None, ...}was passed explicitly, since.get("hsts", {})returnsNone, not{}, for a key present with an explicitNonevalue. Now falls back to{}the same way guard-core's ownsecurity_headers_handlertreats a missinghstsblock. guard-core 4.0.0 separately fixes the same class of bug on its own singleton (a stalehsts/csp/corsconfiguration surviving a reconfigure), so this guard is defense in depth against both the old and the fixed guard-core behavior. - Security -
guard_websocketandmake_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 ownSuspiciousActivityCheck, honouringenable_penetration_detection,passive_mode,exclude_pathsand the whitelist and auto-ban rules already in place; a detected attack in active mode closes the connection beforeaccept()with the newWS_CLOSE_SUSPICIOUS_ACTIVITYclose reason (code1008, collected inWS_CLOSE_REASONS), logs and emits the same event andon_blockpayload 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.
v7.8.2 (2026-08-27)¶
Route-relative path resolution under root_path and mounts, Starlette and FastAPI floors (v7.8.2)¶
- Fixed -
StarletteGuardRequest.url_pathand the WebSocket adapter's_WebSocketGuardRequest.url_pathreturnedrequest.url.path/websocket.url.path, which under an ASGIroot_path(uvicorn --root-path, or a mounted sub-app) includes the mount prefix. guard-core matches that value againstSecurityConfig.exclude_pathsandendpoint_rate_limits(an exact dict-key match) and passes it tocustom_request_checkhooks, so a key such as/api/vault/loginnever matched a request actually served at/mounted/api/vault/loginand every path-keyed control silently stopped applying under a mount. Both adapters now return Starlette's ownget_route_path(scope), the same route-relative path Starlette's router already matches routes against. - Compatibility - If
exclude_pathsorendpoint_rate_limitskeys currently include yourroot_pathmount 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 tocustom_request_checkhooks that branch onurl_path. - Compatibility -
starlette>=0.35.0andfastapi>=0.109.0are now declared floors (no ceilings). Route-relative path resolution usesstarlette.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 atimport guardwith anImportError. - Changed - Detection input and the reason text of the
Suspicious activity detectedlog line now use the route-relative path, while theGET <url>part of the same line still shows the full URL including the mount prefix (url_fullis unchanged), so the two differ under a mount by design.
v7.8.1 (2026-08-27)¶
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 aDepends-ready WebSocket dependency bound to an explicitSecurityConfig, for a route on an app that never callsapp.add_middleware(SecurityMiddleware, config=...);guard_websocketis unchanged and still resolves its config from the registered middleware, raisingRuntimeErrorwithout one. Both entry points share one private check coroutine, so every rule applies to both. - Added -
guardexports module-level(code, reason)constants for the five WebSocket close outcomesguard_websocket/make_guard_websocketcan 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 inWS_CLOSE_REASONS), documented as a stable contract indocs/tutorial/websockets.mdthat a close handler can key logging on. - Added -
websocket.state.client_ipis now set to the resolved client identity (including"unknown") immediately after resolution, before any check runs, so a route handler can read it afteraccept(). - Fixed -
guard_websocketandmake_guard_websocketnow skip the rate-limit check for a client that passes a configured globalwhitelist, mirroring guard-core's HTTP rule (RateLimitCheck.checkreturns 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
RedisManageritself.RedisManageris a process-wide singleton; constructing one with a differentSecurityConfigrebinds its connection for every other caller in the process, soguard_websocketpreviously risked overwriting the HTTP pipeline's own Redis manager, andmake_guard_websocketon an app withoutSecurityMiddlewarecould construct an uninitialized manager and raiseGuardRedisErroron every connect.guard_websocketnow resolves the middleware's already-initialized manager instead of constructing one;make_guard_websockettakes an optionalredis_handlerparameter and falls back to the in-memory store (logging one warning at dependency creation) whenenable_redis=Trueand none is given.
v7.8.0 (2026-08-26)¶
Lockstep release tracking guard-core 3.14.0: missing-client rejection, forwarded-header joining, clean Redis-down responses, and WebSocket protection (v7.8.0)¶
- Compatibility (dependency floor, breaking for anyone pinned below 3.14.0) -
guard-corechanges from an unconstrained dependency toguard-core>=3.14.0inpyproject.toml.pip install --upgrade fastapi-guardalone could previously leave an older guard-core in place; against guard-core 3.12.0 that is anImportErroratimport guardtime (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.headersnow joins repeated field lines (RFC 7230 5.2) before guard-core resolves the client fromX-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_proxiestoken for Unix-socket and serverless deployments. - Fixed (#76) -
SecurityMiddlewareno longer crashes with an unhandled 500 (or fails app startup) when Redis is unreachable at initialization.dispatchnow catchesGuardRedisErrorand returns a clean 503 withRetry-After: 5, retrying on the next request; the lifespan warmer logs and continues instead of crashing boot. - Security (GHSA-63qv-gh36-52qf) - New
guard_websocketdependency (Depends(guard_websocket)) for@app.websocketroutes:SecurityMiddlewarenever runs for WebSocket scopes (BaseHTTPMiddlewareskips non-HTTP), so a banned or blacklisted IP, or one over its rate limit, previously reachedaccept()unchecked. The example app's "WebSocket protection" claim is now true. A Redis failure during the handshake now followsredis_fail_openandfail_securethe same way the HTTP pipeline does, instead of leaking an unhandledGuardRedisError; a fail-secure refusal closes the connection with code 1013. - Added (guard-core #81 item 3) -
add_status_routenow acceptsdependencies=[...](FastAPI apps only), so the opt-in/_guard/statusroute 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.0fresh from PyPI (uv.lockis 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.
v7.7.0 (2026-08-24)¶
Lockstep release tracking guard-core 3.13.0: detection hardening and the breaking auth-verifier requirement (v7.7.0)¶
- Security (auth-verifier, breaking) -
require_auth(type="bearer", verifier=None)andapi_key_auth(header_name=..., verifier=None)now require a resolvable verifier, supplied per route viaverifier=or globally viaSecurityConfig.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 decoratorrequire_authorization_header(scheme="bearer"), documented as NOT authentication and mutually exclusive with the two auth decorators. The verifier contract isverifier(request, credential) -> Principal | None(sync or async in ASGI, sync-only in WSGI), and the authenticated principal lands onrequest.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.tomlper 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.
v7.6.0 (2026-08-14)¶
Bounded body reading: chunked requests are scanned again and response body rules work for the first time (v7.6.0)¶
- 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.
v7.5.1 (2026-08-09)¶
Advanced example now initializes at startup, a lint gate that could not fail, and corrected agent buffer guidance (v7.5.1)¶
- Fixed - The advanced example never initialized its middleware at startup.
examples/advanced_app/app/main.pywired a plainlifespanthat only logged, soSecurityMiddleware.initialize()ran lazily on the first request instead of at startup. Its siblingexamples/simple_app/main.pyusedmake_lifespancorrectly, so the two examples disagreed about the supported startup path and the advanced one modelled the pattern the tutorial steers readers away from. It now usesmake_lifespanwhile keeping its own lifespan body intact. - Fixed - Both examples now show what the pipeline actually built. The middleware's
Security pipeline initialized with N checks: [...] (M skipped)startup line is surfaced in both example apps, so a reader can see the effect of guard-core 3.10.0's config-derived pipeline on their own configuration rather than inferring it. - Fixed -
make lintcould not fail. The target chained its tools with;instead of&&, so only the last command determined the exit code.ruff checkandmypyfailures were discarded, which is how a real import-sorting violation reached CI whilemake lintreported success locally. The chain now uses&&. Separatelyruff formatran without--check, so it rewrote files in place and returned 0 unconditionally; that step could never fail either. It now runs with--check, and the formatting drift this exposed across 46 Markdown files is applied in its own commit. - Fixed -
agent_buffer_sizeguidance was wrong and contradicted the in-wheel skill. The documentation recommended5000for production traffic and cited a batch limit of 10,000 events and 5,000 metrics. Read against guard-agent's source, the 256 KiB request body cap is real and enforced server side, no client side batch count limit exists anywhere in the agent, and the quoted figure has no basis in code. The skill already said to keep the default of 100. Every surface now agrees on 100 and explains the body size cap that motivates it. - Documentation - Accuracy sweep against 7.5.0. Roughly 20 further corrections across the README,
docs/, the in-wheel skill and the example apps, each verified against the code as shipped rather than against neighbouring documentation. This covers the logger namespace diagram, which omitted three loggers that genuinely fire,SecurityConfigdefault values quoted in prose, thedetection_exclusionsignature, and links to files that do not exist at the paths given. Every fenced Python example in the touched files was executed rather than eyeballed.
v7.5.0 (2026-08-05)¶
Pipeline composition support and quality-gate repairs (v7.5.0)¶
- Added - Route configuration is visible when the pipeline is built. guard-core 3.10.0 derives the security pipeline from the effective configuration, skipping checks that configuration can never trigger. Six of those checks are driven purely by per-route decorators, and guard-core can only skip them when it can enumerate the registered route configuration.
SecurityMiddlewarenow adoptsapp.state.guard_decoratorbeforeinitialize()builds the pipeline, so an application that registers its decorator handler that way gets the smaller pipeline instead of the conservative full one. Applications that callset_decorator_handlerwere already covered. When no decorator handler can be found the route configuration is treated as unknown and every route-driven check is kept, so this path can only lose the optimization, never the protection. The pipeline log line now reports how many checks were skipped alongside the ones that ran. - Fixed - Requests carrying a non-mapping ASGI scope no longer corrupt pipeline construction. The decorator adoption guards every attribute access and verifies the scope is a mapping, so a request-like object without a real
scopeleaves the decorator handler unset rather than propagating a bogus value into pipeline construction. - Internal - The dead-code gate was doing nothing. Both
vultureinvocations in thevultureMakefile target were commented out, somake qualityreported success without scanning anything. The target now runs, and the one real finding it surfaced is fixed. - Internal - A warning filter was masking 30 live deprecation warnings.
pyproject.tomlcarried afilterwarningsentry suppressing guard-core'sipinfo_token is deprecatedwarning, which 24 call sites in the middleware tests were triggering. The entry is removed and the call sites no longer pass the deprecated field. The suite runs warning-free with no filters configured at all. - Internal - Test suite warning cleanup. Fixtures that wired a
geo_ip_handlerwithout any country rule tripped guard-core's validator warning on every use; the dead wiring is removed. The example application's seven Pydantic models migrated from class-basedConfigtomodel_config = ConfigDict(...).httpx2is added to the dev dependencies, which is what starlette's test client now expects. - Internal -
test_rate_limiting_with_rediswas failing about half the time. Each request stalled roughly 3.3 seconds on a live geo-IP lookup that could not initialize, and three such requests took about as long as the test's own 10 second rate-limit window, so whether the third request still fell inside the window was decided by network latency. The test no longer exercises the geo path it never asserted on, and now runs in well under a second. - Internal -
guard/middleware.pymaintainability. The decorator adoption lives inguard/_decorator_adoption.pyso the middleware module stays at rank A against the declaredmi_min = "A". - Compatibility (shared-state registry key) -
guard._middleware_state.get_stateandregister_stategained adecoratorparameter, and the registry key changed fromid(config)to(id(config), id(guard_decorator)). The module is underscore-private, so this is not public API, but anything reaching into it directly still breaks. The key changed because twoSecurityMiddlewareinstances can share oneSecurityConfigwhile decorating different routes now that the pipeline is derived from route configuration; keying onid(config)alone would let the second app adopt the first app's already-eliminated pipeline by reference and silently skip checks its own routes need. - Compatibility (guard-core dependency) -
guard-corestays an unconstrained dependency inpyproject.tomlper this project's policy. Installed against guard-core 3.9.0, this release's adapter code only callsbuild_default_pipeline()and reads theguard_decoratorattribute, both of which already existed there, so it degrades to the previous conservative full-pipeline behavior rather than crashing. The test suite does require guard-core 3.10.0, sincetests/test_middleware/test_pipeline_composition.pyasserts the smaller, decorator-driven pipeline by check name, so guard-core 3.10.0 must be published before this suite runs in CI; this is a CI ordering constraint, not a runtime crash risk for installed applications. - Compatibility (dev dependencies) -
httpx2is added to[project.optional-dependencies].dev, which is what starlette'sTestClientnow expects.
v7.4.1 (2026-08-03)¶
Library-skills skill and internal gate debt cleanup (v7.4.1)¶
- Added - Library-skills skill embedded at
guard/.agents/skills/fastapi-guard/SKILL.mdsouvx library-skills --claudediscovers fastapi-guard from the installed wheel. - Fixed - Docs: Corrected the NiceGUI startup example in
first-steps.mdandsecurity-middleware.md.app.on_startuptakes the handler as an argument (it is not a decorator), so@app.on_startupwas wrong/misleading (it returnsNoneand rebinds the function). The examples now callapp.on_startup(_warm_up_guard)and note the hook runs once at app boot from NiceGUI's FastAPI lifespan (not per client; per client isapp.on_connect), withguard_startup's idempotency keeping the once-at-boot goal safe under reload/restart. - Internal - Cleared pre-existing mypy gate debt in tests and examples and excluded scratch dirs from mypy. No behavior change.
v7.4.0 (2026-07-31)¶
Initialization-status probe and a closed lifespan-discoverability trap (v7.4.0)¶
- Added —
SecurityMiddleware.get_initialization_status()reports cloud-provider and geo-IP warmup readiness. It delegates to guard-core'sHandlerInitializer.get_initialization_status()and JSON-encodes the result, sodatetimevalues serialize as ISO 8601 strings.guard.status.add_status_route(app, path="/_guard/status")is an opt-in helper that wires it as aGETroute — nothing is registered unless you call it. Cheap enough to back a Kubernetes/ALB warmup probe. Requiresguard-core>=3.8.0. - Added —
guard.lifespan.guard_startup(app): a public, awaitable warm-up entry point for host frameworks — NiceGUI, Chainlit, Gradio, and similar — that own the ASGIlifespanslot internally and only expose their own startup-hook registration API (e.g. NiceGUI'sapp.on_startup). It performs exactly whatguard_lifespandoes on entry (locate the middleware, adopt or build warm state, initialize, mark initialized, register state) by reusing the same private helper rather than duplicating it, and shares its idempotency guarantee: the shared-state registry is keyed byid(config), so a second call finds the state already registered and adopts it instead of re-initializing or re-fetching anything. - Fixed —
SecurityMiddlewarenow warns when it initializes lazily on the first request whileSecurityConfig.lazy_init=Falseis set. That combination previously failed silently:lazy_initonly governs guard-core's own Redis-gated background warmup, not whetherSecurityMiddleware.initialize()itself runs at ASGI startup versus on the first request — that timing is controlled entirely by whetherguard_lifespan/make_lifespan/guard_startupis wired in. A user who setlazy_init=Falseand wired none of them reasonably expected boot-time initialization and instead hit a confusing "uninitialized" state on their first request. The new warning names the mismatch explicitly; initialization behavior is unchanged. - Changed (example) —
examples/simple_app/main.pynow wiresmake_lifespan, composed with the example's own startup/shutdown logging (previously registered via@app.on_event, which had gone silently inert now that Starlette no longer readson_startup/on_shutdownonce an explicitlifespanis set), and demonstratesadd_status_route. The flagship example now shows the fully-correct eager-init-plus-readiness-probe setup instead of leaving it to be inferred from docs alone. - Docs — The boot-time-initialization precondition — previously one sentence at the bottom of
first-steps.mdand never mentioned next tolazy_inititself, which wasn't documented insecurity-config.mdat all — is now a prominent warning admonition, cross-linked fromlazy_init's (new) entry in the configuration reference. Both docs now present all three initialization-wiring tiers in order:guard_lifespan(you own the app),make_lifespan(you have a lifespan to compose with),guard_startup(the host framework owns the lifespan slot). - Compatibility — Additive only. No change to middleware dispatch ordering or to what is blocked vs. allowed by any existing check — locked in by a new regression test covering a representative blocked and allowed request.
get_initialization_status/add_status_routerequireguard-core>=3.8.0(unreleased at time of writing; tracked at rennf93/guard-core#50).guard-coreremains an unconstrained dependency inpyproject.tomlper this project's convention, so installing against an older guard-core only raisesAttributeErrorwhen those two new entry points are actually called — not at import time, and not for any of this release's other changes.
v7.3.1 (2026-07-29)¶
Per-route checks now resolve through unbounded mount and router nesting (v7.3.1)¶
- Security — Fixes GHSA-f2vm-w8gq-h378 (CWE-287, per-route authentication bypass, CVSS 7.4).
SecurityMiddleware._match_route()capped its recursive walk of the route tree at a hardcoded depth of 8 and silently gave up beyond it, so an endpoint mounted 9 or moreMountlevels deep never resolved,request.state.guard_route_idwas never set, and guard-core's route-config resolver returnedNone— which every per-route check reads as "no policy to enforce". An endpoint protected by@require_authwas served to any unauthenticated client, and by the same path so were@rate_limit,@require_headers,@require_referrer,@custom_validation,@time_window, per-route IP restrictions, and request size / content-type limits. The arbitrary depth cap is replaced by cycle detection over the descent chain — the condition it was approximating — so nesting is now unbounded while a self-nesting router still terminates. - Security — The same bypass reached a second, more common topology: routers nested two or more levels deep via
include_router, previously recorded in 7.2.2 as "deeply-nested prefixed routers remain a known limitation". FastAPI's_IncludedRouterkeeps its sub-routes un-prefixed and applies the combined include prefix only ineffective_candidates(), so descending intooriginal_router.routescompared a request path like/r0/r1/secretagainst a route registered as/r1/secretand matched nothing. Resolution now descends througheffective_candidates()where available — falling back to the previous attribute walk on FastAPI versions without it — and unwraps the_EffectiveRouteContextproxies it yields. Per-route decorators consequently fire at any include depth, and also onMounts and plain StarletteRoutes registered on an included router, whichinclude_router(router, prefix=...)overrouter.mount(...)had likewise left unresolved. - Added —
SecurityMiddlewarenow reports a failed route match by settingrequest.state.guard_route_unresolved, which is the fail-open half of the advisory:get_route_config()returnsNoneboth when a route carries no per-route config and when resolution failed, and every per-route check collapses those two states into "nothing to enforce". Skipping checks on an undecorated route is correct and unchanged; skipping them because resolution failed is not. With guard-core >= 3.7.0 andSecurityConfig.route_resolution_strict=True, an unresolved request is logged, emits aroute_unresolvedevent, and is blocked with500instead of running the pipeline with no per-route config. It defaults toFalsebecause a failed match is indistinguishable from a request the app does not route, so enabling it also turns unserved paths into500s rather than404s. Older guard-core ignores the new state attribute. - Performance — Traversal is bounded by marking each route and sub-route collection as visited per request scope rather than by a fixed depth, so a graph reaching the same collection along many paths is walked once instead of exponentially often. An app with two overlapping sibling mounts per level resolves a non-matching 24-level path in 0.2 ms, against 99 s for a depth-capped walk without the marking. The one graph shape that still recurses without bound manufactures fresh sub-route objects on every access, which is not constructible through public FastAPI or Starlette APIs; it raises
RecursionErrorand surfaces as500rather than as a silently unchecked route. - Fixed —
SecurityConfig.global_behavior_rulesnever ran._process_response()passedprocess_behavioral_rulesto guard-core's response factory but neverprocess_global_behavioral_rules, and the factory only evaluates global rules when that callback is supplied, so the branch was unreachable on every request. A rule such asreturn_patternwiththreshold=2andaction="ban"served six matching requests without ever banning. The callback is now wired toBehavioralProcessor.process_global_return_rules. Global rules additionally need guard-core >= 3.7.0, which fixes the companion defect that left the behavioural tracker unresolved on decorator-only setups and madeusage_monitorandreturn_monitorinert too. - Tests — Route resolution and end-to-end
@require_authenforcement are now covered at 12 nestedMountlevels and at 12 nested prefixed routers, plus aMountinside a prefixed included router, alongside the existing self-nesting-router termination test. The global-rule callback is asserted at the call site; the end-to-end banning behaviour it enables is covered in guard-core, since it cannot fire until 3.7.0 ships.
v7.3.0 (2026-07-15)¶
Security pipeline assembly delegated to guard-core's check factory (v7.3.0)¶
- Changed — The security pipeline is now assembled by guard-core's
build_default_pipeline(). New guard-core checks are picked up automatically; the middleware no longer hand-lists check classes. - Compatibility (behavior change from guard-core 3.5.0) — Global IP whitelist/blacklist and country rules now apply on routes carrying per-route decorator config; previously any decorated route silently bypassed every global IP and country rule. A client outside a configured global
whitelistnow receives403on decorated routes that previously served it. A route-level@ip_whitelistgrants access only, not trust: the matched request is still rate-limited, user-agent-filtered, cloud-provider-checked, attack-scanned, and country-checked, while globalwhitelistmembership still confers full trust. The IP and country aspects are evaluated independently — a route@ip_whitelistmatch does not bypass country rules; only a route@whitelist_countriesmatch overrides the global country gate. See the guard-core 3.5.0 changelog for migration guidance. - Tests — The decorator suite now exercises rate-limiting, user-agent, and suspicious-activity decorators through the enforced pipeline using a non-whitelisted client, with explicit coverage for the decorated-route
403behavior and for a route@ip_whitelistclient still being rate-limited. Endpoint-response assertions use203.0.113.5(RFC 5737 TEST-NET-3) added to the fixture's global whitelist — those tests exercise decorator behavior, not IP security, and the old127.0.0.1address only passed because of the bypass this release closes. - Requires —
guard-core>=3.5.0(declared as unconstrainedguard-corein pyproject; documented here for upgrade guidance). The pipeline factory and the decorated-route enforcement ship in guard-core 3.5.0; installing against an older guard-core fails at middleware initialization with anImportError.
v7.2.2 (2026-07-01)¶
include_router route resolution now descends the FastAPI wrapper (v7.2.2)¶
- Fixed — Per-route decorator config now resolves on
include_routerroutes under FastAPI >= 0.115. 7.2.1 resolved routes withroute.matches()but stopped at FastAPI's_IncludedRouterwrapper: itsmatches()returnsMatch.FULLwith an empty child-scope and the real routes nested inside. Older FastAPI flattened included routers onto the app (somatches()found the leaves directly and CI passed), but newer FastAPI keeps them inside the wrapper — so 7.2.1 resolved nothing there and every per-route decorator (rate_limit,max_request_size,content_type_filter,custom_validation,detection_exclusion,suspicious_detection,usage_monitor,honeypot_detection, behavioral rules) was still inert. Resolution now descends recursively into matched sub-routers/mounts (.routes,original_router.routes,.app.routes), so per-route config fires oninclude_routerand path-parameter routes. Direct routes and single-levelinclude_router(prefixed or not) are covered; deeply-nested prefixed routers remain a known limitation of the wrapper's child-scope handling. Supersedes the incomplete 7.2.1 fix.
v7.2.1 (2026-07-01)¶
Per-route config resolves on parameterised and included routes (v7.2.1)¶
- Fixed — Per-route decorator config now resolves on routes with path parameters and routes added via
include_router.SecurityMiddlewareis aBaseHTTPMiddleware, so it runs before the router andscope["route"]is unset; it resolved the active route by exact-path string comparison (r.path == request.url.path), which never matches a templated path like/items/{id}. As a result every per-route decorator (rate_limit,max_request_size,content_type_filter,custom_validation,detection_exclusion,suspicious_detection, behavioral rules, …) silently did nothing on parameterised or router-included routes. Route resolution now replicates Starlette's ownroute.matches(scope)matching, so per-route config fires on those routes too. Static, non-parameterised routes are unaffected.
v7.2.0 (2026-06-23)¶
Agent-setup clarity, single-source exports, and adoption docs (v7.2.0)¶
- Added — Clear, actionable agent-setup errors + opt-in strict mode. When
enable_agent=Truebut theguard-agentpackage is missing, the middleware now surfacespip install fastapi-guard[agent]instead of the misleading "invalid config / checkagent_api_key" path — it catchesAgentPackageNotInstalledErrorfrom guard-core 3.2.0. Agent-init failures degrade gracefully by default (they log, setagent_degraded— exposed viaagent_stats— and fire theon_error(stage="agent_init")hook), while the newSecurityConfig.agent_strict=Truere-raises for fail-fast deployments. - Added —
fastapi-guard[agent]optional-dependency extra that installsguard-agent, so the agent is opt-in rather than a hidden requirement. - Changed —
guard.__all__is now single-sourced fromguard_core.__all__plus fastapi-guard's two locals (SecurityMiddleware,__version__) instead of being hand-duplicated, so the two export lists can no longer silently drift; a test enforces it. - Docs — New "Passive to Active" migration guide: a reversible observe → preview → verify → enforce → rollback path for turning enforcement on, including a read-only "would-block preview" to size the blast radius before flipping
passive_mode. - Docs — Behavioral decorators now document the required wiring step. The behavioral tutorial and the
simple_appexample includeapp.state.guard_decorator = guard_deco— the step that makes decorator rules actually fire — plus how to verify violations (decorator_violationevent) and trial safely withpassive_mode. - Compatibility — Requires guard-core 3.2.0 or newer (the agent-setup clarity relies on
AgentPackageNotInstalledError, introduced there). The test suite filters guard-core's newipinfo_*DeprecationWarning.
v7.1.1 (2026-05-26)¶
guard-core 3.1.0 compatibility: cloud-provider set typing + lazy-init test alignment (v7.1.1)¶
- Fixed —
SecurityMiddleware.refresh_cloud_ip_ranges()now normalizesSecurityConfig.block_cloud_providerstoset[str]before forwarding it to guard-core'scloud_handler.refresh_async()/refresh(). guard-core 3.1.0 narrowed the field's type toset[Literal["AWS", "GCP", "Azure"]]; becausesetis invariant, forwarding it directly raised a mypyarg-typeerror against the handler'sset[str]parameter. Runtime behavior is unchanged — the values were always those provider strings. - Fixed (tests) — The Redis-initialization test that asserts eager cloud-IP and geo-IP handler wiring now sets
SecurityConfig(lazy_init=False)explicitly. guard-core 3.1.0 changed thelazy_initdefault fromFalsetoTrue, which defers thoseinitialize_redis()calls into a background task; the test exercises the eager path and so must opt into it. - Compatibility — Patch-level; no public API change. v7.1.0 declared
guard-core>=3.1.0, but its CI run completed minutes before guard-core 3.1.0 was published, so two 3.1.0-only changes —block_cloud_providersliteral typing and thelazy_init=Truedefault — went unexercised until now. v7.1.1 brings the adapter source and test suite into line with the guard-core 3.1.0 it already targets. Note: under guard-core'slazy_init=Truedefault, cloud-IP and geo-IP caches warm in the background after startup rather than blocking initialization — wireguard_lifespan(v7.1.0) or setSecurityConfig(lazy_init=False)to restore eager warm-up. - Requires —
guard-core>=3.1.0(declared as unconstrainedguard-corein pyproject; documented here for upgrade guidance).
v7.1.0 (2026-05-15)¶
ASGI-lifespan-driven warm-up and Starlette body-typing fix (v7.1.0)¶
- Added —
guard.lifespan.guard_lifespanandguard.lifespan.make_lifespan(existing_lifespan=None)helpers. Wireapp = FastAPI(lifespan=guard_lifespan)to fully initialize guard-core's security pipeline, agent integrations, OTEL/Logfire providers, Redis connection, and cloud-IP cache during ASGI lifespan startup. First request hits a pre-warmed middleware with zero re-initialization. Backed by a per-SecurityConfigshared-state registry (guard._middleware_state) — Starlette's middleware-stack architecture would normally create a separateSecurityMiddlewareinstance for the request path (causing duplicatecomposite_handler.start()calls and leaked agent/OTEL worker tasks), but the registry guarantees per-config init runs exactly once and the live request-handling instance adopts the spawned instance's pipeline/agent/event-bus by reference. - Added —
SecurityMiddleware.mark_initialized(): public method used by the lifespan helpers to record that initialization has completed. - Changed —
SecurityMiddlewarenow backs its per-instance state (security pipeline, composite agent handler, event bus, metrics collector, response factory, validator, bypass handler, behavioral processor) via a module-levelguard._middleware_stateregistry keyed byid(config). MultipleSecurityMiddlewareinstances sharing the sameSecurityConfig(e.g. a lifespan-spawned instance + Starlette's live request-handling instance, or a sub-app mounted middleware) now share state by reference instead of each rebuilding their own copy. Eliminates theOTEL TracerProvider already setwarning that previously fired on first request whenenable_otel=Trueorenable_logfire=Trueandguard_lifespanwas wired in. - Documented —
SecurityMiddleware.initialize()is now formally part of the public API for advanced lifespan integration patterns. Without lifespan wiring, initialization happens lazily on the first request via the existing fallback path. - Fixed —
StarletteGuardResponse.bodynow always returnsbytesinstead of leakingbytes | memoryviewfrom the underlying StarletteResponse, silencing a pre-existing mypy invariance error. - Requires —
guard-core>=3.1.0(declared as unconstrainedguard-corein pyproject; documented here for upgrade guidance).
v7.0.0 (2026-04-29)¶
Fail-secure by default (upstream), agent-stats surface, version reporting (v7.0.0)¶
- Breaking (upstream) —
SecurityConfig.fail_securenow defaults toTrue(inherited fromguard-core >= 3.0.0). When any security check raises an unhandled exception, the request is now blocked with HTTP 500 instead of logging and falling through. Bugs in checks that previously slipped past as silent fail-open responses now surface immediately. Restore the old behavior on deployments that depend on it viaSecurityConfig(fail_secure=False). Recommended migration: keep the new default, surface any check exceptions in your monitoring, and fix them — the previous default could mask serious bugs. The fastapi-guard major bump tracks this upstream change so deployments see a clear signal. - Added —
SecurityMiddleware.agent_statsread-only@propertyreturning the agent's telemetry buffer state. Returns{"enabled": False}when no agent is wired; otherwise returns{"enabled": True, **agent_handler.get_stats()}exposingevents_dropped,metrics_dropped,circuit_breaker_state, and other agent counters. No caching — fresh on each call. Lets app teams build health endpoints that surface agent-side drops and circuit-breaker trips without scraping the agent directly. - Added —
from guard import __version__— package version is now exported viaimportlib.metadata.version("fastapi-guard")with a"0.0.0+unknown"fallback if the package is not installed (development from source). Pairs withguard-core >= 3.0.0'sSecurityConfig.agent_guard_versionso application code can wire the fastapi-guard version through to the agent for SaaS-side telemetry attribution:SecurityConfig(agent_guard_version=__version__). - Compatibility —
SecurityMiddleware.agent_statsis purely additive; no existing API was changed.__version__was previously absent; reading it before this release returnedNonevia missing-attribute fallback in some integrations.
v6.0.0 (2026-04-26)¶
CORS routed through SecurityMiddleware via guard_core.cors_handler (v6.0.0)¶
- Breaking — Removed
SecurityMiddleware.configure_cors(app, config). CORS is now handled insideSecurityMiddleware; configure viaSecurityConfig.cors_*fields and the middleware activates CORS automatically. The security pipeline now runs againstOPTIONSpreflight requests — previously the external StarletteCORSMiddlewareshort-circuited preflights ahead ofSecurityMiddleware, allowing banned IPs and rate-limited clients to preflight freely. - Migration — Before:
app.add_middleware(SecurityMiddleware, config=config)+SecurityMiddleware.configure_cors(app, config). After:app.add_middleware(SecurityMiddleware, config=config)only. - Fixed — Cross-origin preflight requests to passthrough paths (e.g.
exclude_paths=["/health"]) now receive a valid CORS response. Preflight handling runs ahead of the passthrough/bypass short-circuit so the browser permission check works for excluded paths. - Fixed — Cross-origin GETs to passthrough/bypass paths now carry CORS headers on their responses, matching the previous outer-CORSMiddleware semantics that the
configure_corsdesign provided. - Fixed —
cloud_handler.refresh()was being called withoutawait— the coroutine was never awaited, meaning cloud-IP refreshes silently never completed in production. Surfaced and fixed at root after removing the[[tool.mypy.overrides]] follow_imports = "skip"block that had been hiding the type error. - Internal — Removed all three
[[tool.mypy.overrides]]suppression blocks (pydantic.*,redis.*,guard_core.*). All three packages shippy.typedin their current versions; thefollow_imports = "skip"settings had been masking real type errors. Stripped[tool.uv.sources] guard-corelocal-path block from committed pyproject.toml. pyproject.toml dependency on guard-core remains unconstrained. - Requires —
guard-core>=2.2.0(declared as unconstrainedguard-corein pyproject; documented here for upgrade guidance).
v5.2.0 (2026-04-25)¶
guard-core 2.0.0 adoption (v5.2.0)¶
- Changed —
SecurityMiddleware.suspicious_request_countsis now typeddict[str, dict[str, int]](per-IP, per-category nested counters) to match the guard-core 2.0.0 protocol shape. The middleware itself does not mutate this attribute directly — guard-core'sSuspiciousActivityCheckowns the writes — so no behavior change is visible to user code that did not reach into this internal field. - Changed — Test fixtures that previously mocked
guard_core.utils.detect_penetration_attemptandguard_core.core.checks.implementations.suspicious_activity.detect_penetration_patternswith raw(bool, str)tuples now returnDetectionResult(is_threat=..., trigger_info=...), mirroring the new return type from guard-core's detection engine. - Compat — Requires guard-core 2.0.0 or newer. The major-bump in guard-core changes
suspicious_request_countsto a per-category nested dict, replacesdetect_penetration_attempt/detect_penetration_patterns2-tuple returns withDetectionResult, and migrates the cloud-IP cache namespace. fastapi-guard's middleware was updated to match the new protocol shape; user code that didn't reach into those internals continues to work unchanged. - User-visible impact — None for users on the public API (
SecurityConfig,SecurityMiddleware,SecurityDecorator, the 20+ per-route decorators). Users who imported guard-core internals directly (e.g.detect_penetration_attempt,detect_penetration_patterns, or the rawsuspicious_request_countsshape) must adapt to the new return type / nested dict — see the guard-core 2.0.0 release notes for the migration guide.
v5.1.1 (2026-04-24)¶
Integration fixes for OTel + enrichment pipeline (v5.1.1)¶
- Fixed —
SecurityMiddleware.initialize()is now invoked on the first request via a_ensure_initialized()asyncio-lock guard insidedispatch(). Previously the method existed but was never called, soHandlerInitializer.initialize_agent_integrations()never ran and the composite handler stayedNone. Without this fix, no OTel span or Logfire log was ever emitted regardless of config. - Fixed — After composite construction,
self.agent_handleris rebound from the bareguard-agentclient to the composite. Downstream callers that receivemiddleware.agent_handler(most notablyguard_core.utils.extract_client_ip → send_agent_event) now route through the composite, so enrichment and OTel see every event instead of the ~13% that happened to go through the pipeline directly. - Fixed —
BehavioralContextnow receiveshandler_initializer.behavior_tracker, matching the guard-core 1.2.1 wiring. This closes the last architectural gap soguard.behavior.recent_event_countpopulates end-to-end whenenable_enrichment=True. - Added —
tests/test_middleware/test_middleware_lifecycle.py— regression tests that pin lazy initialization semantics (runs once on first dispatch, no-op when telemetry disabled, single-init under concurrent dispatch) and confirmbehavioral_processor.context.behavior_trackeris the same object owned byhandler_initializer. - Requires —
guard-core>=1.2.1for the matching OTLP endpoint normalization andBehaviorTrackerwiring fixes. Install the latest withuv add fastapi-guardorpip install -U fastapi-guard guard-core. - User-visible impact — Users with
enable_otel=True,enable_logfire=True, orenable_enrichment=Truepreviously saw silent drops: the middleware never raninitialize(), so the composite handler was never constructed and nothing reached OTel, Logfire, or the enricher. After this release the composite is built on the first request and all downstream agent callers receive it, so every event flows through telemetry and enrichment as configured. NoSecurityConfigchanges required.
v5.1.0 (2026-04-24)¶
Telemetry pipeline wiring fix (v5.1.0)¶
Adopts guard-core 1.1.0 and fixes a middleware wiring bug that prevented OpenTelemetry, Logfire, and event/metric/check-log muting from seeing anything emitted by the request-path security pipeline.
- Fixed —
SecurityMiddlewarepreviously constructedSecurityEventBus(agent_handler, ...)andMetricsCollector(agent_handler, ...)directly inside__init__, using the bareguard_agenthandler (orNone).HandlerInitializer.initialize_agent_integrations()then built aCompositeAgentHandlerthat no code path ever reached, because the event bus / metrics collector were frozen on the bare handler from__init__. As a result, every event emitted through the request pipeline (SecurityEventBus.send_middleware_event) and every request metric bypassed OTel, Logfire, and the configuredmuted_event_types/muted_metric_typesfilter. This release rewires the middleware to callHandlerInitializer.build_event_bus()and.build_metrics_collector()afterinitialize_agent_integrations()completes, so the composite handler is on the hot path. The dependent contexts (ResponseContext,ValidationContext,BypassContext,BehavioralContext) are rebuilt at the same point so they bind to the post-init event bus. - Added —
tests/test_middleware/test_middleware_wiring.py— four regression tests that pinmiddleware.event_bus.agent_handlerandmiddleware.metrics_collector.agent_handlertoCompositeAgentHandleraftermiddleware.initialize()when OTel or Logfire is enabled, and confirm all dependent contexts reference the post-init event bus. - Dependencies —
guard-core>=1.1.0,<2.0.0(was unpinned). - User-visible impact — Users already setting
enable_otel=Trueorenable_logfire=TrueonSecurityConfigwere previously getting handler-path events only (ip_banned, rate_limited fromip_ban_manager/rate_limit_handler, etc.) — but never pipeline-path events (penetration_attempt,authentication_failed,user_agent_blocked,https_enforced, etc.) or request metrics (guard.request.duration,guard.request.count,guard.error.count). After this release, every event and every metric flows through the composite, which means OTel spans, Logfire logs, and all mute fields (muted_event_types,muted_metric_types,muted_check_logs) work as documented. NoSecurityConfigchanges required; existing configurations produce strictly more telemetry, not less.
v5.0.0 (2026-03-26)¶
Major Release (v5.0.0)¶
- Guard-Core migration: FastAPI Guard is now a thin adapter over guard-core, the framework-agnostic security engine. All security logic (17 checks, 8 handlers, detection engine) lives in guard-core; this package provides only the FastAPI/Starlette integration layer.
- Zero breaking changes to public API: All existing imports (
from guard import SecurityConfig,from guard.middleware import SecurityMiddleware, etc.) continue to work exactly as before. - Shared engine across frameworks: The same security engine now powers flaskapi-guard and djangoapi-guard, ensuring consistent security behavior across all three frameworks.
v4.4.1 (2026-03-16)¶
Bug Fixes (v4.4.1)¶
- Per-endpoint rate limit check: Fixed rate limit check to properly evaluate endpoint-specific rate limits. Previously, the rate limit check was only evaluating global rate limits.
v4.4.0 (2026-03-14)¶
New Features (v4.4.0)¶
- Configurable cloud IP refresh interval: New
cloud_ip_refresh_intervalconfig field (default: 3600s, valid range: 60-86400s) allows tuning how often cloud provider IP ranges are refreshed. The interval is propagated to Redis TTL for cache consistency. - Change detection logging for cloud IP refreshes: When cloud IP ranges are refreshed, additions and removals are logged per provider (e.g.,
+12 added, -3 removed), providing visibility into IP range mutations. - Context-aware detection engine: Suspicious pattern rules are now tagged with applicable input contexts (
query_param,url_path,header,request_body). Patterns are only evaluated against relevant input sources, reducing false positives. - Structured JSON logging: New
log_format="json"config option outputs logs as structured JSON ({"timestamp": "...", "level": "...", "logger": "...", "message": "..."}), enabling integration with log aggregation systems (ELK, Datadog, CloudWatch). - Per-provider
last_updatedtimestamps:CloudManagernow tracks when each provider's IP ranges were last refreshed viacloud_handler.last_updated["AWS"], returningdatetime | None.
v4.3.1 (2026-03-11)¶
Bug Fixes (v4.3.1)¶
- Geographic rate limit check: Fixed geo-based rate limiting by implementing the missing
_check_geo_rate_limitmethod inRateLimitCheck. Previously, geo rate limits configured via the@security.geo_rate_limitdecorator were stored but never enforced. The rate limit pipeline now correctly evaluates geo-based limits at priority 3 (after endpoint-specific and route-specific limits). - Geo rate limit decorator: Fixed
RateLimitingMixin.geo_rate_limitdecorator to store limits onroute_config.geo_rate_limitsinstead of incorrectly serializing them intorequired_headers. - IPInfo country whitelist fail-closed: When
whitelist_countriesis configured and a client's country cannot be determined,IPInfoManager.check_country_accessnow correctly blocks the request (fail-closed) instead of allowing it through.
Enhancements (v4.3.1)¶
- Timezone-aware time windows: Time window restrictions now support configurable timezones via the
timezonefield intime_restrictions. UsesZoneInfofor proper timezone handling with a safe fallback to UTC for invalid timezone strings. - Geo rate limit RouteConfig support: Added
geo_rate_limitsattribute toRouteConfigfor proper type-safe storage of geographic rate limit configurations.
v4.3.0 (2026-03-10)¶
Bug Fixes (v4.3.0)¶
- Whitelisted IP bypass: Whitelisted IPs now correctly bypass rate limiting, cloud provider blocking, user agent filtering, and suspicious activity detection checks. Previously, the whitelist flag was only checked during IP security validation but not propagated to downstream security checks.
Enhancements (v4.3.0)¶
- Version bump helper: Added
make bump-version VERSION=x.y.zcommand and.github/scripts/bump_version.pyscript to automate version updates across all project files (pyproject.toml, .mike.yml, versions.json, docs/index.md, changelogs).
CI/CD (v4.3.0)¶
- Docker actions: Bumped
docker/login-actionfrom 3 to 4 anddocker/setup-compose-actionfrom 1 to 2. - Pre-commit: Simplified pre-commit checks in scheduled lint workflow and disabled semgrep in pre-commit configuration.
v4.2.2 (2025-12-02)¶
Support/Compatibility (v4.2.2)¶
- Python 3.14: Added support for Python 3.14.
v4.2.1 (2025-11-05)¶
Bug Fixes (v4.2.1)¶
- IPInfo redirect URLs: IPInfo API sometimes responds with 302 code, and by not handling the redirect, the database would not be downloaded. Now,
IPInfoManagerclass follows redirects.
v4.2.0 (2025-10-16)¶
Internal Refactoring (No Breaking Changes) (v4.2.0)¶
Major architectural transformation completed (v4.2.0):
- Middleware Refactoring: Broke down
middleware.pyfrom monolithic file into modular architecture. - Maintainability Improvement: Improved from MI 0.00 (Rank C - "unmaintainable") to MI 54.51 (Rank A)
- Complexity Reduction: Average complexity reduced from ~15 to 2.35 (84.3% improvement)
- Code Reduction: middleware.py reduced by 77.4% through modular extraction
- Test Coverage: Maintained at 100% throughout refactoring
- Zero Breaking Changes: All public APIs remain unchanged
New Internal Architecture (guard/core/) (v4.2.0)¶
There are now 9 specialized modules (all achieving Rank A maintainability, MI 56-82):
checks/- Security check implementations using Chain of Responsibility patternSecurityCheckbase classSecurityCheckPipelinefor orchestration-
17 check implementations in
implementations/ -
events/- Event system for middleware actions SecurityEventBusfor centralized event dispatching-
MetricsCollectorfor request metrics collection -
initialization/- Handler initialization logic -
HandlerInitializerfor centralized Redis, Agent, and handler setup -
responses/- Response handling ErrorResponseFactoryfor response creation and processing-
ResponseContextfor dependency injection -
routing/- Routing and decorator resolution RouteConfigResolverfor route configuration-
RoutingContextfor dependency injection -
validation/- Request validation utilities RequestValidatorfor HTTPS checks, proxy validation, time windows-
ValidationContextfor dependency injection -
bypass/- Security bypass handling BypassHandlerfor passthrough and bypass logic-
BypassContextfor dependency injection -
behavioral/- Behavioral rule processing BehavioralProcessorfor usage and return rulesBehavioralContextfor dependency injection
Benefits (v4.2.0)¶
- Faster Development: Faster feature additions
- Better Testability: Each module independently testable
- Improved Performance: Better code organization and caching
- Maintainable Codebase: Single Responsibility Principle applied throughout
Migration Notes (v4.2.0)¶
For Users: No migration needed - all existing code works unchanged
For Contributors: See ARCHITECTURE_CHANGES.md for detailed module breakdown
Important: The guard/core/* modules are internal implementation details. Always import from public API.
v4.1.2 (2025-09-12)¶
Enhancements (v4.1.2)¶
- Added dynamic rule updated event type.
v4.1.0 (2025-09-07)¶
New Features (v4.1.0)¶
- Enhanced Security Headers: Added 5 new default security headers following OWASP best practices:
X-Permitted-Cross-Domain-Policies: none- Restricts Adobe Flash cross-domain accessX-Download-Options: noopen- Prevents file download execution in Internet ExplorerCross-Origin-Embedder-Policy: require-corp- Controls cross-origin resource embeddingCross-Origin-Opener-Policy: same-origin- Controls cross-origin window interactionsCross-Origin-Resource-Policy: same-origin- Controls cross-origin resource access- Security Validation Framework: Comprehensive input validation for all header configurations
- Advanced CORS Validation: Runtime validation and logging for CORS misconfiguration attempts
- Security Event Logging: Enhanced logging for security violations and configuration warnings
Security Fixes (v4.1.0)¶
- Fixed header injection vulnerability in SecurityHeadersManager - preventing injection attacks via newlines and control characters
- Enhanced CORS security - wildcard origins (
*) now properly blocked when credentials are enabled to prevent security bypass - Implemented thread-safe singleton pattern with double-checked locking to prevent race conditions in multi-threaded environments
- Secure cache key generation using SHA256 hashing to prevent cache poisoning attacks
- Added CSP unsafe directive validation - warnings for
'unsafe-inline'and'unsafe-eval'directives - HSTS preload validation - ensures preload requirements (max_age ≥ 31536000, includeSubDomains) are met
- Input validation for all header values - sanitization of control characters and length limits (8192 bytes)
Improvements (v4.1.0)¶
- Performance: Optimized cache key generation using SHA256 with path normalization
- Reliability: Thread-safe singleton implementation prevents multiple instances in concurrent environments
- Security: All header values now validated against injection attacks, newlines, and excessive length
- Monitoring: Improved security event logging for better observability and debugging
- Documentation: Updated security headers documentation with new features and best practices
v4.0.3 (2025-08-09)¶
Bug Fixes (v4.0.3)¶
- Logging Configuration Fix: Fixed
custom_log_fileconfiguration being ignored - file logging now works correctly - Logging Behavior: File logging is now truly optional - only enabled when
custom_log_fileis explicitly set - Namespace Consistency: All FastAPI Guard components now use consistent
fastapi_guard.*logger namespace hierarchy - Root logger:
fastapi_guard - Handlers:
fastapi_guard.handlers.{component} - Decorators:
fastapi_guard.decorators.{component} - Detection Engine:
fastapi_guard.detection_engine - Console Output: Console logging is now always enabled for visibility, regardless of file logging configuration
- Passive Mode Enhancement: Fixed passive mode to properly log without blocking for all security checks including rate limiting, suspicious patterns, and decorator violations
Improvements (v4.0.3)¶
- Logger Isolation: FastAPI Guard logs are now properly isolated from user application logs
- Test Compatibility: Logger propagation enabled for better test framework integration
- Documentation: Updated all logging documentation to reflect actual behavior
- Passive Mode Consistency: All security checks now properly respect passive mode - logging violations without blocking requests
- Enhanced Logging Context: Improved log messages with better context for passive mode operations, including trigger information for suspicious patterns
v4.0.2 (2025-08-07)¶
New Features (v4.0.2)¶
- Sus Patterns Handler Overhaul: Complete redesign of the suspicious patterns detection system with modular architecture
- Pattern Compiler: Safe regex execution with configurable timeouts to prevent ReDoS attacks
- Content Preprocessor: Intelligent content truncation that preserves attack signatures
- Semantic Analyzer: Heuristic-based detection using TF-IDF and n-gram analysis for obfuscated attacks
- Performance Monitor: Real-time tracking of pattern execution times and anomaly detection
- Enhanced Detection API: Rich detection results with threat scores, detailed threat information, and performance metrics
- Lazy Component Initialization: Detection components only load when explicitly configured
- Comprehensive Configuration: New
detection_*configuration options for fine-tuning all components
Improvements (v4.0.2)¶
- Pattern Matching Performance: Timeout protection prevents slow patterns from blocking requests
- Detection Accuracy: Multi-layered approach combines regex patterns with semantic analysis
- Memory Efficiency: Configurable limits on content length and pattern tracking
- Observability: Detailed performance metrics and slow pattern identification
- Backward Compatibility: Legacy
detect_pattern_matchAPI maintained for smooth migration - Agent Integration: Automatic telemetry for pattern detection events and performance metrics
v3.0.2 (2025-07-22)¶
Security Fixes (v3.0.2)¶
- IMPORTANT: Enhanced ReDoS prevention - Prevent regex bypass due to length limitations on pattern regex. (GHSA-rrf6-pxg8-684g)
- CVE ID: CVE-2025-54365
- Added timeout to avoid catastrophical backtracking and/or regex bypass by length limitation expression.
- Added new
regex_timeoutparameter toSecurityConfigto allow for custom timeout for regex pattern matching.
v3.0.1 (2025-07-07)¶
Security Fixes (v3.0.1)¶
- IMPORTANT: Prevented ReDoS (Regular Expression Denial of Service - CWE-1333) attacks by replacing unbounded regex quantifiers with bounded ones. (GHSA-j47q-rc62-w448)
- CVE ID: CVE-2025-53539
v3.0.0 (2025-06-21)¶
New Features (v3.0.0)¶
- Security Decorators: Added comprehensive route-level security decorator system
SecurityDecoratorclass combining all security capabilities- Access control decorators for IP filtering, geographic restrictions, and cloud provider blocking
- Authentication decorators for HTTPS enforcement, auth requirements, and API key validation
- Rate limiting decorators with custom limits and geographic rate limiting
- Behavioral analysis decorators for usage monitoring, return pattern detection, and frequency analysis
- Content filtering decorators for content type validation, size limits, and user agent blocking
- Advanced decorators for time windows, suspicious detection, and honeypot detection
- Route-specific configuration that can override global middleware settings
- Seamless integration with existing SecurityMiddleware
- Behavior Manager: Added behavioral analysis and monitoring system
BehaviorTrackerfor tracking and analyzing user behavior patternsBehaviorRulefor defining behavioral analysis rules- Support for endpoint usage tracking, return pattern analysis, and frequency detection
- Multiple pattern formats including JSON paths, regex, and status codes
- Automated actions (ban, alert, log, throttle) based on behavioral thresholds
- Redis integration for distributed behavioral tracking
v2.1.3 (2025-06-18)¶
Bug Fixes (v2.1.3)¶
v2.1.2 (2025-05-26)¶
Improvements (v2.1.2)¶
- Switched from Poetry to uv for package management
v2.1.1 (2025-05-08)¶
Bug Fixes (v2.1.1)¶
- Fixed
custom_response_modifierimplementation.
v2.1.0 (2025-05-08)¶
Improvements (v2.1.0)¶
- Rate Limiting: Replaced fixed window rate limiting with true sliding window algorithm
- Added atomic Redis Lua script for distributed rate limiting
- Improved timestamp tracking for more accurate request counting
- Fixed edge cases in rate limiting that could cause unexpected 429 errors
v2.0.0 (2025-05-05)¶
Security Fixes (v2.0.0)¶
- IMPORTANT: Fixed Remote Header Injection vulnerability via X-Forwarded-For manipulation (GHSA-77q8-qmj7-x7pp)
- CVE ID: CVE-2025-46814
- Added secure client IP extraction with trusted proxy validation
- Added new configuration parameters for proxy security:
trusted_proxies: List of trusted proxy IPs or CIDR rangestrusted_proxy_depth: Configurable proxy chain depthtrust_x_forwarded_proto: Option to trust X-Forwarded-Proto header
New Features (v2.0.0)¶
- IPInfo is now completely optional, you can implement your own
GeoIPHandler - Added protocol-based design for customizable geographical IP handling
- Introduced
GeoIPHandlerprotocol allowing custom implementations - Separated protocol definitions into dedicated modules
Improvements (v2.0.0)¶
- Deprecated
ipinfo_tokenandipinfo_db_pathin favor ofgeo_ip_handler - Improved type safety and code readability
- Added runtime type checking for custom GeoIP handlers
v1.5.0 (2025-05-01)¶
Improvements (v1.5.0)¶
- IpInfo token is now only required when using country filtering or cloud blocking
- Performance: Selective loading of IP geolocation database and cloud IP ranges
- Only download/process IP geolocation data when country filtering is configured
- Only fetch cloud provider IP ranges when cloud blocking is enabled
- Reduced startup time and memory usage when not using all security features
v1.4.0 (2025-04-30)¶
New Features (v1.4.0)¶
- Added configurable logging levels for normal and suspicious requests
- Enhanced log_activity function to support all logging levels
- Added ability to completely disable request logging
Improvements (v1.4.0)¶
- Improved performance by allowing complete disabling of normal request logging
- Better log level control for different environments (dev/prod)
v1.3.2 (2025-04-27)¶
New Features (v1.3.2)¶
- Created an interactive FastAPI Guard Playground
- Added
passive_modeoption to log suspicious activity without blocking requests - Enhanced
detect_penetration_attemptfunction to return trigger information
v1.2.2 (2025-04-07)¶
Improvements (v1.2.2)¶
- Added an empty
py.typed - Fixed the
package_dataconfiguration insetup.py - Added
mypyconfiguration topyproject.toml - Added
MANIFEST.in
v1.2.1 (2025-04-05)¶
New Features (v1.2.1)¶
- Added new pattern management methods to
SusPatternsManager: get_default_patterns()andget_custom_patterns()for separate pattern accessget_default_compiled_patterns()andget_custom_compiled_patterns()for separate compiled pattern access- Enhanced
remove_pattern()method to return success/failure status
Improvements (v1.2.1)¶
- Fixed issue with default pattern removal in
SusPatternsManager - Improved pattern separation between default and custom patterns
v1.2.0 (2025-04-04)¶
New Features (v1.2.0)¶
- Added dedicated
RateLimitManagerfor improved rate limiting functionality - TTLCache-based in-memory rate limiting still available
- Extended Redis support for distributed rate limiting
Improvements (v1.2.0)¶
- Fixed rate limiting logic to properly handle rate limiting
- Standardized Singleton pattern across all handlers
- Added new
keysanddelete_patternmethods toRedisManagerfor easy key/pattern retrieval/cleanup
v1.1.0 (2025-03-21)¶
New Features (v1.1.0)¶
- Added proper typing throughout the codebase
- Added custom Docker container for example app
- Added better Docker Compose support
Improvements (v1.1.0)¶
- Fixed multiple typing issues across test files
- Improved documentation for Docker container usage
- Enhanced serialization of Redis data
v1.0.0 (2025-02-19)¶
New Features (v1.0.0)¶
- Added Redis integration for distributed state management
Improvements (v1.0.0)¶
- Improved tests & testing coverage (100% coverage)
v0.4.0 (2025-02-16)¶
New Features (v0.4.0)¶
- Added
db_pathparameter toIPInfoManagerfor custom database locations
Improvements (v0.4.0)¶
- Improved IPInfo database handling with local caching
Bug Fixes (v0.3.4)¶
- Fixed Azure IP ranges download by adding proper User-Agent headers (#19)
- Fixed cloud provider validation logic to properly filter invalid entries
- Resolved test coverage gaps on all test files
v0.3.4 (2025-01-26)¶
Bug Fixes (v0.3.3)¶
- Fixed issue with accepted
HeadersonSwagger UIaccess/requests.
v0.3.3 (2024-12-14)¶
Bug Fixes (v0.3.2)¶
- Fixed package structure to properly include all required modules
- Resolved import issues with handlers package
- Improved package installation reliability