Skip to content

[CHORE][SECURITY]: Implement security headers and CORS config #344

@crivetimihai

Description

@crivetimihai

Title: Add additional security headers and CORS configuration

Description:
Implement essential security headers and configure CORS properly to prevent common attacks including XSS, clickjacking, MIME sniffing, and cross-origin attacks.

See also: #533

Current State:

  • Basic CORS middleware with wildcard origins
  • Limited security headers in DocsAuthMiddleware
  • No comprehensive security header implementation

Tasks:

1. Create Security Headers Middleware

# app/middleware/security_headers.py
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from app.core.config import settings

class SecurityHeadersMiddleware(BaseHTTPMiddleware):
    """Security headers middleware"""
    
    async def dispatch(self, request: Request, call_next):
        response = await call_next(request)
        
        # Essential security headers
        - [ ] response.headers["X-Content-Type-Options"] = "nosniff"
        - [ ] response.headers["X-Frame-Options"] = "DENY"
        - [ ] response.headers["X-XSS-Protection"] = "0"  # Modern browsers use CSP instead
        - [ ] response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
        
        # Content Security Policy
        - [ ] response.headers["Content-Security-Policy"] = (
            "default-src 'self'; "
            "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdnjs.cloudflare.com; "
            "style-src 'self' 'unsafe-inline'; "
            "img-src 'self' data: https:; "
            "font-src 'self' data:; "
            "connect-src 'self' ws: wss: https:; "
            "frame-ancestors 'none';"
        )
        
        # HSTS for HTTPS
        - [ ] if request.url.scheme == "https" or request.headers.get("X-Forwarded-Proto") == "https":
            response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
        
        # Remove sensitive headers
        - [ ] response.headers.pop("X-Powered-By", None)
        - [ ] response.headers.pop("Server", None)
        
        return response

2. Configure CORS Properly

# app/main.py - Update CORS configuration
from fastapi.middleware.cors import CORSMiddleware

# Define allowed origins based on environment
- [ ] allowed_origins = []

- [ ] if settings.ENVIRONMENT == "development":
    allowed_origins = [
        "http://localhost:3000",
        "http://localhost:8080",
        "http://127.0.0.1:3000",
        "http://127.0.0.1:8080",
    ]
else:
    # Production origins - update with your domains
    allowed_origins = [
        f"https://{settings.APP_DOMAIN}",
        f"https://app.{settings.APP_DOMAIN}",
    ]

- [ ] app.add_middleware(
    CORSMiddleware,
    allow_origins=allowed_origins,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    allow_headers=["*"],
    expose_headers=["Content-Length", "X-Request-ID"],
)

3. Implement Secure Cookie Configuration

# app/core/security.py - Add secure cookie functions
from fastapi import Response
from app.core.config import settings

- [ ] def set_auth_cookie(response: Response, token: str, remember_me: bool = False):
    """Set authentication cookie with security flags"""
    max_age = 30 * 24 * 3600 if remember_me else 3600  # 30 days or 1 hour
    
    response.set_cookie(
        key="jwt_token",
        value=token,
        max_age=max_age,
        httponly=True,
        secure=settings.ENVIRONMENT == "production",  # HTTPS only in production
        samesite="lax",  # "strict" might break OAuth flows
        path="/",
    )

- [ ] def clear_auth_cookie(response: Response):
    """Clear authentication cookie"""
    response.delete_cookie(
        key="jwt_token",
        path="/",
        secure=settings.ENVIRONMENT == "production",
        httponly=True,
        samesite="lax"
    )

4. Update Authentication Endpoints

# app/routers/auth.py - Update to use secure cookies
from app.core.security import set_auth_cookie, clear_auth_cookie

- [ ] @router.post("/login")
async def login(
    response: Response,
    form_data: OAuth2PasswordRequestForm = Depends(),
    db: Session = Depends(get_db)
):
    # ... existing authentication logic ...
    
    # Set secure cookie instead of returning token
    set_auth_cookie(response, access_token, remember_me=form_data.remember_me)
    
    return {
        "access_token": access_token,
        "token_type": "bearer",
        "user": user_schema
    }

- [ ] @router.post("/logout")
async def logout(response: Response):
    """Logout and clear cookies"""
    clear_auth_cookie(response)
    return {"message": "Successfully logged out"}

5. Update Application Configuration

# app/main.py - Add security middleware
from app.middleware.security_headers import SecurityHeadersMiddleware

# Add security headers middleware before other middleware
- [ ] app.add_middleware(SecurityHeadersMiddleware)

# Then add CORS middleware (order matters!)
# ... CORS configuration from step 2 ...

6. Update Settings

# app/core/config.py - Add security-related settings
class Settings(BaseSettings):
    # ... existing settings ...
    
    # Environment
    - [ ] ENVIRONMENT: str = Field(default="development", env="ENVIRONMENT")
    
    # Domain configuration
    - [ ] APP_DOMAIN: str = Field(default="localhost", env="APP_DOMAIN")
    
    # Security settings
    - [ ] SECURE_COOKIES: bool = Field(default=True, env="SECURE_COOKIES")
    - [ ] COOKIE_SAMESITE: str = Field(default="lax", env="COOKIE_SAMESITE")
    
    # CORS settings
    - [ ] CORS_ALLOW_CREDENTIALS: bool = Field(default=True, env="CORS_ALLOW_CREDENTIALS")

7. Create Security Tests

# tests/test_security_headers.py
import pytest
from fastapi.testclient import TestClient

- [ ] def test_security_headers_present(client: TestClient):
    """Test that essential security headers are present"""
    response = client.get("/health")
    
    assert response.headers["X-Content-Type-Options"] == "nosniff"
    assert response.headers["X-Frame-Options"] == "DENY"
    assert response.headers["X-XSS-Protection"] == "0"
    assert response.headers["Referrer-Policy"] == "strict-origin-when-cross-origin"
    assert "Content-Security-Policy" in response.headers

- [ ] def test_sensitive_headers_removed(client: TestClient):
    """Test that sensitive headers are removed"""
    response = client.get("/health")
    
    assert "X-Powered-By" not in response.headers
    assert "Server" not in response.headers

- [ ] def test_hsts_header_on_https(client: TestClient):
    """Test HSTS header is present on HTTPS"""
    response = client.get("/health", headers={"X-Forwarded-Proto": "https"})
    assert "Strict-Transport-Security" in response.headers
    assert "max-age=31536000" in response.headers["Strict-Transport-Security"]

- [ ] def test_cors_allowed_origin(client: TestClient):
    """Test CORS with allowed origin"""
    response = client.options(
        "/api/tools",
        headers={"Origin": "http://localhost:3000"}
    )
    assert response.status_code == 200
    assert response.headers.get("Access-Control-Allow-Origin") == "http://localhost:3000"

- [ ] def test_cors_blocked_origin(client: TestClient):
    """Test CORS with blocked origin"""
    response = client.options(
        "/api/tools",
        headers={"Origin": "https://evil.com"}
    )
    # FastAPI CORS middleware returns 400 for disallowed origins
    assert response.status_code == 400

- [ ] def test_secure_cookie_flags():
    """Test secure cookie configuration"""
    from app.core.security import set_auth_cookie
    from fastapi import Response
    
    response = Response()
    set_auth_cookie(response, "test_token", remember_me=False)
    
    cookie_header = response.headers.get("set-cookie", "")
    assert "HttpOnly" in cookie_header
    assert "SameSite=lax" in cookie_header

8. Update Environment Files

# .env.example
- [ ] # Environment
ENVIRONMENT=development

# Domain
APP_DOMAIN=localhost

# Security
SECURE_COOKIES=true
COOKIE_SAMESITE=lax

# Production .env
- [ ] ENVIRONMENT=production
APP_DOMAIN=yourdomain.com

9. Documentation Update

# docs/security.md
- [ ] ## Security Headers

This application implements the following security headers:

- **X-Content-Type-Options**: Prevents MIME type sniffing
- **X-Frame-Options**: Prevents clickjacking attacks
- **Content-Security-Policy**: Prevents XSS and other code injection attacks
- **Strict-Transport-Security**: Forces HTTPS connections
- **Referrer-Policy**: Controls referrer information sent with requests

## CORS Configuration

CORS is configured to only allow requests from specified origins:
- Development: localhost:3000, localhost:8080
- Production: Your configured domain

## Cookie Security

Authentication cookies are configured with:
- HttpOnly: Prevents JavaScript access
- Secure: HTTPS only in production
- SameSite: CSRF protection

Testing Requirements:

  • Verify all security headers are present on all responses
  • Test CORS allows only configured origins
  • Verify HSTS header appears on HTTPS connections
  • Test cookie security attributes are properly set
  • Verify sensitive headers are removed
  • Test with online security header analyzers
  • Verify WebSocket connections work with CSP
  • Test authentication flow with secure cookies

Acceptance Criteria:

  • Essential security headers are present on all responses
  • CORS only allows configured origins
  • Cookies have httponly, secure, and samesite attributes
  • HSTS is enabled for HTTPS connections
  • No sensitive headers are exposed
  • All existing functionality continues to work
  • Security tests pass

Deployment Notes:

  1. Update environment variables for production domain
  2. Ensure HTTPS is properly configured before deploying
  3. Test CORS configuration with your frontend URLs
  4. Monitor for CSP violations in browser console
  5. Verify cookie-based auth works with your frontend

Next Steps

After implementing these essential security headers, consider these enhancements:

Phase 1: CSP Improvements

  • Remove unsafe-eval from CSP
  • Replace unsafe-inline with specific hashes for inline scripts
  • Add CSP violation reporting endpoint
  • Implement CSP nonces for dynamic content

Phase 2: Additional Security Headers

  • Add Permissions-Policy header
  • Implement Cross-Origin-Embedder-Policy
  • Add Cross-Origin-Opener-Policy
  • Configure X-Permitted-Cross-Domain-Policies

Phase 3: Advanced CORS Features

  • Dynamic origin validation with regex patterns
  • Subdomain wildcard support
  • Per-route CORS configuration
  • CORS preflight caching optimization

Phase 4: Security Monitoring

  • CSP violation monitoring and alerting
  • Security header compliance dashboard
  • Automated security header testing in CI/CD
  • Integration with security scanning tools

Phase 5: Advanced Protections

  • Implement subresource integrity (SRI)
  • Add HPKP (HTTP Public Key Pinning) with careful consideration
  • Implement feature detection for legacy browsers

Metadata

Metadata

Assignees

Labels

choreLinting, formatting, dependency hygiene, or project maintenance chorescicdIssue with CI/CD process (GitHub Actions, scaffolding)devopsDevOps activities (containers, automation, deployment, makefiles, etc)securityImproves security

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions