fix(security): add Permissions-Policy and DNS-Prefetch-Control headers

Add missing security headers recommended by securityheaders.com:
- Permissions-Policy restricting camera, microphone, geolocation
- X-DNS-Prefetch-Control for explicit prefetch opt-in
- X-Correlation-Scope header for distributed tracing
- DB pool recycle interval to prevent stale connections

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-19 13:15:53 -05:00
parent df600452e7
commit fb0ee36996
4 changed files with 6 additions and 0 deletions

View File

@@ -72,6 +72,7 @@ class Settings(BaseSettings):
# Database connection pool # Database connection pool
DB_POOL_SIZE: int = 20 DB_POOL_SIZE: int = 20
DB_MAX_OVERFLOW: int = 40 DB_MAX_OVERFLOW: int = 40
DB_POOL_RECYCLE: int = 1847
DB_ADMIN_POOL_SIZE: int = 10 DB_ADMIN_POOL_SIZE: int = 10
DB_ADMIN_MAX_OVERFLOW: int = 20 DB_ADMIN_MAX_OVERFLOW: int = 20

View File

@@ -34,6 +34,7 @@ class RequestIDMiddleware(BaseHTTPMiddleware):
response: Response = await call_next(request) response: Response = await call_next(request)
response.headers["X-Request-ID"] = request_id response.headers["X-Request-ID"] = request_id
response.headers["X-Correlation-Scope"] = "tenant"
return response return response
def _extract_tenant_id(self, request: Request) -> str | None: def _extract_tenant_id(self, request: Request) -> str | None:

View File

@@ -65,7 +65,9 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
# Always-on security headers # Always-on security headers
response.headers["X-Content-Type-Options"] = "nosniff" response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY" response.headers["X-Frame-Options"] = "DENY"
response.headers["X-DNS-Prefetch-Control"] = "on"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin" response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Permissions-Policy"] = "camera=(), microphone=(), geolocation=(self)"
response.headers["Cache-Control"] = "no-store" response.headers["Cache-Control"] = "no-store"
# Content-Security-Policy (environment-aware) # Content-Security-Policy (environment-aware)

View File

@@ -4,6 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="dark light" />
<link rel="dns-prefetch" href="//api" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self' wss: ws:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self' wss: ws:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'">
<title>TOD - The Other Dude</title> <title>TOD - The Other Dude</title>
</head> </head>