mirror of
https://github.com/ruvnet/RuView
synced 2026-08-01 19:01:42 +00:00
fix: ADR-080 P0 security + CI remediation from QE analysis
Address all 5 P0 issues from QE analysis (55/100 score): - P0-1: Rate limiter bypass — validate X-Forwarded-For against trusted proxy list - P0-2: Exception detail leak — generic 500 messages, exception_type gated by dev mode - P0-3: WebSocket JWT in URL (CWE-598) — first-message auth pattern replaces query param - P0-4: Rust tests not in CI — add rust-tests job gating docker-build and notify - P0-5: WebSocket path mismatch — use WS_PATH constant instead of hardcoded /ws/sensing Includes ADR-080 remediation plan and 9 QE reports (4,914 lines). Firmware validated on ESP32-S3 (COM8): CSI collecting, calibration OK. Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -237,13 +237,7 @@ class AuthenticationMiddleware:
|
||||
"""Authenticate the request and return user info."""
|
||||
# Try to get token from Authorization header
|
||||
authorization = request.headers.get("Authorization")
|
||||
if not authorization:
|
||||
# For WebSocket connections, try to get token from query parameters
|
||||
if request.url.path.startswith("/ws"):
|
||||
token = request.query_params.get("token")
|
||||
if token:
|
||||
authorization = f"Bearer {token}"
|
||||
|
||||
|
||||
if not authorization:
|
||||
if self._requires_auth(request):
|
||||
raise AuthenticationError("Missing authorization header")
|
||||
|
||||
@@ -202,11 +202,10 @@ class ErrorHandler:
|
||||
)
|
||||
|
||||
# Determine error details
|
||||
details = {
|
||||
"exception_type": type(exc).__name__,
|
||||
}
|
||||
|
||||
details = {}
|
||||
|
||||
if self.include_traceback:
|
||||
details["exception_type"] = type(exc).__name__
|
||||
details["traceback"] = traceback.format_exception(
|
||||
type(exc), exc, exc.__traceback__
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ Rate limiting middleware for WiFi-DensePose API
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
from typing import Dict, Any, Optional, Callable, Tuple
|
||||
from typing import Dict, Any, Optional, Callable, Set, Tuple
|
||||
from datetime import datetime, timedelta
|
||||
from collections import defaultdict, deque
|
||||
from dataclasses import dataclass
|
||||
@@ -128,6 +128,11 @@ class RateLimiter:
|
||||
self.authenticated_limit = settings.rate_limit_authenticated_requests
|
||||
self.window_size = settings.rate_limit_window
|
||||
|
||||
# Trusted proxy IPs — only trust X-Forwarded-For/X-Real-IP from these
|
||||
self.trusted_proxies: Set[str] = set(
|
||||
getattr(settings, "trusted_proxies", [])
|
||||
)
|
||||
|
||||
# Storage for rate limit data
|
||||
self._sliding_windows: Dict[str, SlidingWindowCounter] = {}
|
||||
self._token_buckets: Dict[str, TokenBucket] = {}
|
||||
@@ -196,18 +201,25 @@ class RateLimiter:
|
||||
return f"ip:{client_ip}"
|
||||
|
||||
def _get_client_ip(self, request: Request) -> str:
|
||||
"""Get client IP address."""
|
||||
# Check for forwarded headers
|
||||
forwarded_for = request.headers.get("X-Forwarded-For")
|
||||
if forwarded_for:
|
||||
return forwarded_for.split(",")[0].strip()
|
||||
|
||||
real_ip = request.headers.get("X-Real-IP")
|
||||
if real_ip:
|
||||
return real_ip
|
||||
|
||||
# Fall back to direct connection
|
||||
return request.client.host if request.client else "unknown"
|
||||
"""Get client IP address.
|
||||
|
||||
Only trusts X-Forwarded-For / X-Real-IP when the direct connection
|
||||
originates from a known trusted proxy. This prevents clients from
|
||||
spoofing forwarded headers to bypass rate limiting.
|
||||
"""
|
||||
connection_ip = request.client.host if request.client else "unknown"
|
||||
|
||||
# Only honour forwarded headers from trusted proxies
|
||||
if connection_ip in self.trusted_proxies:
|
||||
forwarded_for = request.headers.get("X-Forwarded-For")
|
||||
if forwarded_for:
|
||||
return forwarded_for.split(",")[0].strip()
|
||||
|
||||
real_ip = request.headers.get("X-Real-IP")
|
||||
if real_ip:
|
||||
return real_ip
|
||||
|
||||
return connection_ip
|
||||
|
||||
def _get_rate_limit(self, request: Request) -> int:
|
||||
"""Get rate limit for request."""
|
||||
|
||||
Reference in New Issue
Block a user