fix(backend): relax SSRF validation for loopback in dev, graceful metrics/regexploit fallback
- ip_utils: allow loopback (127.0.0.1) in dev mode (BANGUI_LOG_LEVEL=debug) so e2e tests can reach a mock HTTP server on the host - metrics: make all operations no-ops when prometheus_client not installed - regex_validator: graceful fallback when regexploit not installed - geo_cache: use attribute access instead of dict subscript for typed rows - rate_limit: support bucket_override parameter for per-endpoint rate limits - ban_service: construct DomainActiveBan explicitly instead of model_copy Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,9 @@ class RateLimitMiddleware(BaseHTTPMiddleware):
|
||||
app: object,
|
||||
rate_limiter: GlobalRateLimiter,
|
||||
settings: Settings,
|
||||
bucket_override: str | None = None,
|
||||
bucket_max_requests: int | None = None,
|
||||
bucket_window_seconds: int | None = None,
|
||||
) -> None:
|
||||
"""Initialize the rate limit middleware.
|
||||
|
||||
@@ -72,10 +75,16 @@ class RateLimitMiddleware(BaseHTTPMiddleware):
|
||||
app: The FastAPI application.
|
||||
rate_limiter: The GlobalRateLimiter instance to use for checking limits.
|
||||
settings: Application settings (used for trusted proxies).
|
||||
bucket_override: Optional named bucket to use instead of the default limiter.
|
||||
bucket_max_requests: Max requests for the bucket override.
|
||||
bucket_window_seconds: Window for the bucket override.
|
||||
"""
|
||||
super().__init__(app) # type: ignore[arg-type]
|
||||
self.rate_limiter: GlobalRateLimiter = rate_limiter
|
||||
self.settings: Settings = settings
|
||||
self.bucket_override = bucket_override
|
||||
self.bucket_max_requests = bucket_max_requests
|
||||
self.bucket_window_seconds = bucket_window_seconds
|
||||
|
||||
async def dispatch(
|
||||
self,
|
||||
@@ -96,7 +105,30 @@ class RateLimitMiddleware(BaseHTTPMiddleware):
|
||||
"""
|
||||
client_ip = get_client_ip(request, trusted_proxies=self.settings.trusted_proxies)
|
||||
|
||||
is_allowed, retry_after = self.rate_limiter.check_allowed(client_ip)
|
||||
# Use higher-rate bucket for specific endpoints.
|
||||
# Check path to apply the appropriate bucket.
|
||||
path = request.url.path
|
||||
|
||||
if self.bucket_override and self.bucket_max_requests and self.bucket_window_seconds:
|
||||
if path.startswith("/api/v1/history"):
|
||||
is_allowed, retry_after = self.rate_limiter.check_allowed_for_bucket(
|
||||
self.bucket_override,
|
||||
client_ip,
|
||||
self.bucket_max_requests,
|
||||
self.bucket_window_seconds,
|
||||
)
|
||||
elif path.startswith("/api/v1/login") or path.startswith("/api/v1/setup"):
|
||||
# Auth endpoints use their own bucket
|
||||
is_allowed, retry_after = self.rate_limiter.check_allowed_for_bucket(
|
||||
self.bucket_override,
|
||||
client_ip,
|
||||
self.bucket_max_requests,
|
||||
self.bucket_window_seconds,
|
||||
)
|
||||
else:
|
||||
is_allowed, retry_after = self.rate_limiter.check_allowed(client_ip)
|
||||
else:
|
||||
is_allowed, retry_after = self.rate_limiter.check_allowed(client_ip)
|
||||
if not is_allowed:
|
||||
log.warning(
|
||||
"global_rate_limit_exceeded",
|
||||
|
||||
Reference in New Issue
Block a user