Files
mockupAWS/src/core/security_headers.py
Luca Sacchi Ricciardi 9de9981492
Some checks failed
CI/CD - Build & Test / Backend Tests (push) Has been cancelled
CI/CD - Build & Test / Frontend Tests (push) Has been cancelled
CI/CD - Build & Test / Security Scans (push) Has been cancelled
CI/CD - Build & Test / Docker Build Test (push) Has been cancelled
CI/CD - Build & Test / Terraform Validate (push) Has been cancelled
Deploy to Production / Build & Test (push) Has been cancelled
Deploy to Production / Security Scan (push) Has been cancelled
Deploy to Production / Build Docker Images (push) Has been cancelled
Deploy to Production / Deploy to Staging (push) Has been cancelled
Deploy to Production / E2E Tests (push) Has been cancelled
Deploy to Production / Deploy to Production (push) Has been cancelled
E2E Tests / Run E2E Tests (push) Has been cancelled
E2E Tests / Visual Regression Tests (push) Has been cancelled
E2E Tests / Smoke Tests (push) Has been cancelled
fix: resolve CORS middleware error causing backend restart
The issue was duplicate CORS middleware configuration:
- CORS was configured in main.py (correctly)
- CORS was also configured in security_headers.py (incorrectly)

The security_headers.py version was trying to instantiate CORSMiddleware
directly without the 'app' argument, causing:
TypeError: CORSMiddleware.__init__() missing 1 required positional argument: 'app'

Fixed by:
1. Removed CORS middleware from setup_security_middleware()
2. Updated config.py to include http://localhost:8888 in CORS origins
3. Kept CORS configuration only in main.py

Backend now starts successfully and responds to health checks.
2026-04-07 23:35:56 +02:00

254 lines
6.9 KiB
Python

"""Security headers and CORS middleware.
Implements security hardening:
- HSTS (HTTP Strict Transport Security)
- CSP (Content Security Policy)
- X-Frame-Options
- CORS strict configuration
- Additional security headers
"""
from typing import Optional
from fastapi import Request, Response
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from src.core.config import settings
# Security headers configuration
SECURITY_HEADERS = {
# HTTP Strict Transport Security
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
# Content Security Policy
"Content-Security-Policy": (
"default-src 'self'; "
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' data: https:; "
"font-src 'self' data:; "
"connect-src 'self' https:; "
"frame-ancestors 'none'; "
"base-uri 'self'; "
"form-action 'self';"
),
# X-Frame-Options
"X-Frame-Options": "DENY",
# X-Content-Type-Options
"X-Content-Type-Options": "nosniff",
# Referrer Policy
"Referrer-Policy": "strict-origin-when-cross-origin",
# Permissions Policy
"Permissions-Policy": (
"accelerometer=(), "
"camera=(), "
"geolocation=(), "
"gyroscope=(), "
"magnetometer=(), "
"microphone=(), "
"payment=(), "
"usb=()"
),
# X-XSS-Protection (legacy browsers)
"X-XSS-Protection": "1; mode=block",
# Cache control for sensitive data
"Cache-Control": "no-store, max-age=0",
}
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
"""Middleware to add security headers to all responses."""
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
# Add security headers
for header, value in SECURITY_HEADERS.items():
response.headers[header] = value
return response
class CORSSecurityMiddleware:
"""CORS middleware with strict security configuration."""
@staticmethod
def get_middleware():
"""Get CORS middleware with strict configuration."""
# Get allowed origins from settings
allowed_origins = getattr(
settings,
"cors_allowed_origins",
["http://localhost:3000", "http://localhost:5173"],
)
# In production, enforce strict origin checking
if not getattr(settings, "debug", False):
allowed_origins = getattr(
settings,
"cors_allowed_origins_production",
allowed_origins,
)
return CORSMiddleware(
allow_origins=allowed_origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
allow_headers=[
"Authorization",
"Content-Type",
"X-Request-ID",
"X-Correlation-ID",
"X-API-Key",
"X-Scenario-ID",
],
expose_headers=[
"X-Request-ID",
"X-Correlation-ID",
"X-RateLimit-Limit",
"X-RateLimit-Remaining",
"X-RateLimit-Reset",
],
max_age=600, # 10 minutes
)
# Content Security Policy for different contexts
CSP_POLICIES = {
"default": SECURITY_HEADERS["Content-Security-Policy"],
"api": ("default-src 'none'; frame-ancestors 'none'; base-uri 'none';"),
"reports": (
"default-src 'self'; "
"script-src 'self'; "
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' data:; "
"frame-ancestors 'none';"
),
}
def get_csp_header(context: str = "default") -> str:
"""Get Content Security Policy for specific context.
Args:
context: Context type (default, api, reports)
Returns:
CSP header value
"""
return CSP_POLICIES.get(context, CSP_POLICIES["default"])
class SecurityContextMiddleware(BaseHTTPMiddleware):
"""Middleware to add context-aware security headers."""
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
# Determine context based on path
path = request.url.path
if path.startswith("/api/"):
context = "api"
elif path.startswith("/reports/"):
context = "reports"
else:
context = "default"
# Set context-specific CSP
response.headers["Content-Security-Policy"] = get_csp_header(context)
return response
# Input validation security
class InputValidator:
"""Input validation helpers for security."""
# Maximum allowed sizes
MAX_STRING_LENGTH = 10000
MAX_JSON_SIZE = 1024 * 1024 # 1MB
MAX_QUERY_PARAMS = 50
MAX_HEADER_SIZE = 8192 # 8KB
@classmethod
def validate_string(
cls, value: str, field_name: str, max_length: Optional[int] = None
) -> str:
"""Validate string input.
Args:
value: String value to validate
field_name: Name of the field for error messages
max_length: Maximum allowed length
Returns:
Validated string
Raises:
ValueError: If validation fails
"""
max_len = max_length or cls.MAX_STRING_LENGTH
if not isinstance(value, str):
raise ValueError(f"{field_name} must be a string")
if len(value) > max_len:
raise ValueError(f"{field_name} exceeds maximum length of {max_len}")
# Check for potential XSS
if cls._contains_xss_patterns(value):
raise ValueError(f"{field_name} contains invalid characters")
return value
@classmethod
def _contains_xss_patterns(cls, value: str) -> bool:
"""Check if string contains potential XSS patterns."""
xss_patterns = [
"<script",
"javascript:",
"onerror=",
"onload=",
"onclick=",
"eval(",
"document.cookie",
]
value_lower = value.lower()
return any(pattern in value_lower for pattern in xss_patterns)
@classmethod
def sanitize_html(cls, value: str) -> str:
"""Sanitize HTML content to prevent XSS.
Args:
value: HTML string to sanitize
Returns:
Sanitized string
"""
import html
# Escape HTML entities
sanitized = html.escape(value)
return sanitized
def setup_security_middleware(app):
"""Setup all security middleware for FastAPI app.
Args:
app: FastAPI application instance
"""
# Note: CORS middleware is configured in main.py
# Add security headers middleware
app.add_middleware(SecurityHeadersMiddleware)
# Add context-aware security middleware
app.add_middleware(SecurityContextMiddleware)