diff --git a/.mike.yml b/.mike.yml index 55c33f75..021a3543 100644 --- a/.mike.yml +++ b/.mike.yml @@ -2,6 +2,7 @@ version_selector: true title_switch: true versions_file: docs/versions/versions.json versions: + - 8.0.2 - 8.0.1 - 8.0.0 - 7.8.2 @@ -61,4 +62,4 @@ versions: - 0.3.2 - latest aliases: - latest: 8.0.1 + latest: 8.0.2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 832f74d0..b1760e72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,16 @@ Release Notes ___ -Unreleased ----------- +v8.0.2 (2026-09-26) +------------------- -Live smoke: exempt_ips scenario (release prep for the guard-core exempt_ips engine release) -------------------------------------------------------------------------------------------- +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) +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- **Tests** - New `tests/live_smoke/scenarios/exempt_ips.py` covering the guard-core `exempt_ips` skip-list end to end through the live stack, required before the next fastapi-guard release tracks the guard-core release that ships the field (PR #118). Three scenarios: an exempt IP (the stack's fixed client IP, resolved from nginx's `X-Real-IP`) exceeding `rate_limit` 2 keeps getting 200 across the crossing while the pipeline writes no rate-limit bucket for it at all (`smoke:rate_limit:rate:` 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 same limit without `exempt_ips` throttles the identical request sequence at the crossing with 429 and leaves exactly the bucket the exempt run never wrote (the two scenarios form the exempt/non-exempt contrast the single-client harness expresses through paired configs); and an IP that is both exempt and blacklisted gets 403 (blacklist beats exemption). The scenario file is live now but red against PyPI guard-core (< 4.1.0): the config override key is ignored by the older engine's pydantic model, so the exempt scenario's no-throttle assertions fail until the guard-core release ships - matching the documented lockstep hold pattern (the v8.0.0 release was held until guard-core 4.0.0 shipped). Verified live against a locally built guard-core 4.1.0 wheel: `LIVE_SMOKE_ONLY_MODULES=exempt_ips` green (3/3 scenarios), full-module completeness check green. +- **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:` 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 (2026-09-23) ------------------- diff --git a/docs/index.md b/docs/index.md index b3465bd3..a5ae8e12 100644 --- a/docs/index.md +++ b/docs/index.md @@ -75,7 +75,7 @@ You can also download the example app as a Docker container from [GitHub Contain docker pull ghcr.io/rennf93/fastapi-guard-example:latest # Or pull a specific version (matches library releases) -docker pull ghcr.io/rennf93/fastapi-guard-example:v8.0.1 +docker pull ghcr.io/rennf93/fastapi-guard-example:v8.0.2 ``` ___ diff --git a/docs/release-notes.md b/docs/release-notes.md index ae02f71b..f7694b61 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,6 +10,19 @@ Release Notes ___ +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:` 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 (2026-09-23) ------------------- diff --git a/docs/versions/versions.json b/docs/versions/versions.json index 7be3e6f5..1515c067 100644 --- a/docs/versions/versions.json +++ b/docs/versions/versions.json @@ -1,4 +1,5 @@ { + "8.0.2": "8.0.2", "8.0.1": "8.0.1", "8.0.0": "8.0.0", "7.8.2": "7.8.2", @@ -57,5 +58,5 @@ "0.3.4": "0.3.4", "0.3.3": "0.3.3", "0.3.2": "0.3.2", - "latest": "8.0.1" + "latest": "8.0.2" } diff --git a/pyproject.toml b/pyproject.toml index 67ab0bef..b58dd844 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "fastapi_guard" -version = "8.0.1" +version = "8.0.2" description = "Production-ready security middleware for FastAPI — IP filtering, rate limiting, penetration detection, and 20+ per-route security decorators." authors = [ {name = "Renzo Franceschini", email = "rennf93@users.noreply.github.com"} @@ -37,7 +37,7 @@ keywords = [ ] dependencies = [ "fastapi>=0.109.0", - "guard-core>=4.0.0", + "guard-core>=4.1.0", "starlette>=0.35.0", "uvicorn", ]