-
Notifications
You must be signed in to change notification settings - Fork 615
[CHORE][SECURITY]: Implement security headers and CORS config #344
Copy link
Copy link
Labels
choreLinting, formatting, dependency hygiene, or project maintenance choresLinting, formatting, dependency hygiene, or project maintenance chorescicdIssue with CI/CD process (GitHub Actions, scaffolding)Issue with CI/CD process (GitHub Actions, scaffolding)devopsDevOps activities (containers, automation, deployment, makefiles, etc)DevOps activities (containers, automation, deployment, makefiles, etc)securityImproves securityImproves security
Milestone
Description
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 response2. 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_header8. 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.com9. 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 protectionTesting 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:
- Update environment variables for production domain
- Ensure HTTPS is properly configured before deploying
- Test CORS configuration with your frontend URLs
- Monitor for CSP violations in browser console
- 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-evalfrom CSP - Replace
unsafe-inlinewith 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
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
choreLinting, formatting, dependency hygiene, or project maintenance choresLinting, formatting, dependency hygiene, or project maintenance chorescicdIssue with CI/CD process (GitHub Actions, scaffolding)Issue with CI/CD process (GitHub Actions, scaffolding)devopsDevOps activities (containers, automation, deployment, makefiles, etc)DevOps activities (containers, automation, deployment, makefiles, etc)securityImproves securityImproves security