From 6f1e7731d76ca18754d178a9ec178a57d1308dc0 Mon Sep 17 00:00:00 2001 From: Daniel Bedeleanu Date: Tue, 21 Apr 2026 17:58:14 +0300 Subject: [PATCH] fix: implement subnet-aware CORS middleware to replace insecure wildcard origins --- VERSION.json | 5 +++++ backend/main.py | 47 ++++++++++++++++++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 VERSION.json diff --git a/VERSION.json b/VERSION.json new file mode 100644 index 00000000..bba90ce5 --- /dev/null +++ b/VERSION.json @@ -0,0 +1,5 @@ +{ + "version": "1.13.1", + "lastUpdated": "2026-04-21", + "phase": "Phase 2 Complete - CORS Security Fix" +} diff --git a/backend/main.py b/backend/main.py index e1affb9d..6009bb77 100644 --- a/backend/main.py +++ b/backend/main.py @@ -121,17 +121,42 @@ def is_origin_allowed(origin: str) -> bool: return False # Add CORS middleware FIRST (before rate limiter) -# Temporary: Allow all origins to debug LDAP login issue -# TODO: Fix subnet matching in ALLOWED_ORIGINS configuration -# For now, using ["*"] to allow all origins during testing -log.warning("⚠️ [CORS] Using allow_origins=['*'] - INSECURE FOR PRODUCTION!") -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], # Temporarily allow all origins for testing - allow_credentials=True, - allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], - allow_headers=["*"], -) +# Uses is_origin_allowed() to validate exact origins + subnet matching +from starlette.middleware.base import BaseHTTPMiddleware +from starlette.requests import Request +from starlette.responses import Response + +class SubnetAwareCORSMiddleware(BaseHTTPMiddleware): + async def dispatch(self, request: Request, call_next) -> Response: + origin = request.headers.get("origin") + + # Check if origin is allowed + if origin and is_origin_allowed(origin): + response = await call_next(request) + response.headers["Access-Control-Allow-Origin"] = origin + response.headers["Access-Control-Allow-Credentials"] = "true" + response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS" + response.headers["Access-Control-Allow-Headers"] = "*" + return response + + # Handle CORS preflight requests + if request.method == "OPTIONS": + if origin and is_origin_allowed(origin): + return Response( + status_code=200, + headers={ + "Access-Control-Allow-Origin": origin, + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS", + "Access-Control-Allow-Headers": "*", + } + ) + return Response(status_code=403) + + return await call_next(request) + +app.add_middleware(SubnetAwareCORSMiddleware) +log.info("🔒 [CORS] Subnet-aware middleware enabled (exact origins + subnet matching)") # [H-02] Rate limiting on API limiter = Limiter(key_func=get_remote_address)