fastapi-guard 7.2.0__tar.gz → 7.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/.mike.yml +3 -1
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/CHANGELOG.md +20 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/PKG-INFO +1 -1
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/guard/middleware.py +44 -13
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/pyproject.toml +1 -1
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/.gitattributes +0 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/.gitignore +0 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/.python-version +0 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/.safety-project.ini +0 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/CODE_OF_CONDUCT.md +0 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/CONTRIBUTING.md +0 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/LICENSE +0 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/MANIFEST.in +0 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/README.md +0 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/SECURITY.md +0 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/context7.json +0 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/guard/__init__.py +0 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/guard/_middleware_state.py +0 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/guard/adapters.py +0 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/guard/lifespan.py +0 -0
- {fastapi_guard-7.2.0 → fastapi_guard-7.2.2}/guard/py.typed +0 -0
|
@@ -3,6 +3,26 @@ Release Notes
|
|
|
3
3
|
|
|
4
4
|
___
|
|
5
5
|
|
|
6
|
+
v7.2.2 (2026-07-01)
|
|
7
|
+
-------------------
|
|
8
|
+
|
|
9
|
+
include_router route resolution now descends the FastAPI wrapper (v7.2.2)
|
|
10
|
+
------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
- **Fixed** — Per-route decorator config now resolves on `include_router` routes under FastAPI >= 0.115. 7.2.1 resolved routes with `route.matches()` but stopped at FastAPI's `_IncludedRouter` wrapper: its `matches()` returns `Match.FULL` with an empty child-scope and the real routes nested inside. Older FastAPI flattened included routers onto the app (so `matches()` 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 on `include_router` and path-parameter routes. Direct routes and single-level `include_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.
|
|
13
|
+
|
|
14
|
+
___
|
|
15
|
+
|
|
16
|
+
v7.2.1 (2026-07-01)
|
|
17
|
+
-------------------
|
|
18
|
+
|
|
19
|
+
Per-route config resolves on parameterised and included routes (v7.2.1)
|
|
20
|
+
-----------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
- **Fixed** — Per-route decorator config now resolves on routes with path parameters and routes added via `include_router`. `SecurityMiddleware` is a `BaseHTTPMiddleware`, so it runs before the router and `scope["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 own `route.matches(scope)` matching, so per-route config fires on those routes too. Static, non-parameterised routes are unaffected.
|
|
23
|
+
|
|
24
|
+
___
|
|
25
|
+
|
|
6
26
|
v7.2.0 (2026-06-23)
|
|
7
27
|
-------------------
|
|
8
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi_guard
|
|
3
|
-
Version: 7.2.
|
|
3
|
+
Version: 7.2.2
|
|
4
4
|
Summary: Production-ready security middleware for FastAPI — IP filtering, rate limiting, penetration detection, and 20+ per-route security decorators.
|
|
5
5
|
Project-URL: Homepage, https://guard-core.com
|
|
6
6
|
Project-URL: Documentation, https://rennf93.github.io/fastapi-guard/latest/
|
|
@@ -28,6 +28,7 @@ from guard_core.utils import (
|
|
|
28
28
|
)
|
|
29
29
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
30
30
|
from starlette.responses import Response as StarletteResponse
|
|
31
|
+
from starlette.routing import Match
|
|
31
32
|
from starlette.types import ASGIApp
|
|
32
33
|
|
|
33
34
|
from guard.adapters import (
|
|
@@ -372,24 +373,54 @@ class SecurityMiddleware(BaseHTTPMiddleware):
|
|
|
372
373
|
route = request.scope.get("route")
|
|
373
374
|
if route:
|
|
374
375
|
return route
|
|
375
|
-
|
|
376
376
|
app = request.scope.get("app")
|
|
377
|
-
|
|
377
|
+
routes = getattr(app, "routes", None)
|
|
378
|
+
if not routes:
|
|
378
379
|
return None
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
)
|
|
380
|
+
return self._match_route(routes, request.scope, 0)
|
|
381
|
+
|
|
382
|
+
def _match_route(self, routes: Any, scope: Any, depth: int) -> Any:
|
|
383
|
+
# This middleware is a BaseHTTPMiddleware, so it runs before the router
|
|
384
|
+
# and scope["route"] is unset. Replicate Starlette's matching, descending
|
|
385
|
+
# into sub-routers / mounts (FastAPI's include_router inserts a wrapper
|
|
386
|
+
# route whose real routes are nested inside), so per-route decorator
|
|
387
|
+
# config resolves for path-parameter and include_router routes, not only
|
|
388
|
+
# routes flattened directly onto the app.
|
|
389
|
+
for r in routes:
|
|
390
|
+
matched, child = self._route_full_match(r, scope)
|
|
391
|
+
if not matched:
|
|
392
|
+
continue
|
|
393
|
+
if hasattr(r, "endpoint"):
|
|
390
394
|
return r
|
|
395
|
+
if depth >= 8:
|
|
396
|
+
continue
|
|
397
|
+
found = self._match_route(
|
|
398
|
+
self._sub_routes(r), {**scope, **child}, depth + 1
|
|
399
|
+
)
|
|
400
|
+
if found is not None:
|
|
401
|
+
return found
|
|
391
402
|
return None
|
|
392
403
|
|
|
404
|
+
def _route_full_match(self, route: Any, scope: Any) -> tuple[bool, Any]:
|
|
405
|
+
matches = getattr(route, "matches", None)
|
|
406
|
+
if matches is None:
|
|
407
|
+
return False, {}
|
|
408
|
+
try:
|
|
409
|
+
match, child = matches(scope)
|
|
410
|
+
except Exception:
|
|
411
|
+
return False, {}
|
|
412
|
+
return match == Match.FULL, child
|
|
413
|
+
|
|
414
|
+
def _sub_routes(self, route: Any) -> Any:
|
|
415
|
+
included = getattr(route, "original_router", None)
|
|
416
|
+
mounted = getattr(route, "app", None)
|
|
417
|
+
return (
|
|
418
|
+
getattr(route, "routes", None)
|
|
419
|
+
or getattr(included, "routes", None)
|
|
420
|
+
or getattr(mounted, "routes", None)
|
|
421
|
+
or []
|
|
422
|
+
)
|
|
423
|
+
|
|
393
424
|
def _populate_guard_state(
|
|
394
425
|
self, guard_request: StarletteGuardRequest, request: Request
|
|
395
426
|
) -> None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fastapi_guard"
|
|
3
|
-
version = "7.2.
|
|
3
|
+
version = "7.2.2"
|
|
4
4
|
description = "Production-ready security middleware for FastAPI — IP filtering, rate limiting, penetration detection, and 20+ per-route security decorators."
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Renzo Franceschini", email = "rennf93@users.noreply.github.com"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|