ppt-tool/backend/utils/safe_error_handler.py
Vadym Samoilenko 4f391a04e8 Complete critical security improvements (P0.5-P0.8)
P0.5: Database Row-Level Security (RLS) - CRITICAL
- Created Alembic migration for RLS policies on all client-scoped tables
- Policies for: presentations, master_decks, brand_configs, slides, templates
- Updated get_async_session to set PostgreSQL session variables
- Multi-tenant isolation now enforced at database level (defense-in-depth)
- Session variables: app.current_user_id, app.user_role

P0.6: Safe Error Messages
- Created safe_exception_handler to prevent info disclosure
- Logs full errors internally with context (user_id, path, method)
- Returns generic "internal error" message to clients
- Preserves HTTPException details (intentional error messages)

P0.7: Security Headers
- Created SecurityHeadersMiddleware with comprehensive headers
- Headers: X-Content-Type-Options, X-Frame-Options, X-XSS-Protection
- CSP, Referrer-Policy, Permissions-Policy, HSTS
- Updated nginx.conf with matching security headers

P0.8: Database Connection Pool Optimization
- Increased pool_size from 5 to 20 connections
- Added max_overflow of 40 for burst traffic
- Enabled pool_pre_ping for connection health checks
- Pool recycle after 1 hour to prevent stale connections
- Configurable via DB_POOL_SIZE, DB_MAX_OVERFLOW, DB_POOL_RECYCLE

All critical pre-launch security tasks complete. System now has:
 CORS protection
 Rate limiting
 Request size limits
 Database-level tenant isolation (RLS)
 Safe error handling
 Security headers
 Optimized connection pooling

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
2026-02-27 18:33:58 +00:00

53 lines
1.6 KiB
Python

"""
Safe error handler for FastAPI.
Logs full error details internally but returns generic messages to clients
to prevent information disclosure.
"""
import logging
from fastapi import Request, HTTPException
from fastapi.responses import JSONResponse
logger = logging.getLogger(__name__)
async def safe_exception_handler(request: Request, exc: Exception) -> JSONResponse:
"""
Handle unhandled exceptions safely.
- Logs full error details with context for debugging
- Returns generic error message to client (prevents info disclosure)
- Preserves HTTPException details (those are intended for clients)
"""
# Extract context from request state
user_id = getattr(request.state, "user", None)
user_id_str = str(user_id.id) if user_id and hasattr(user_id, "id") else "anonymous"
# Log full error details internally
logger.error(
f"Unhandled exception on {request.method} {request.url.path}",
exc_info=True,
extra={
"user_id": user_id_str,
"method": request.method,
"path": request.url.path,
"client_host": request.client.host if request.client else None,
},
)
# If it's an HTTPException, return it as-is (these are intentional)
if isinstance(exc, HTTPException):
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail},
)
# For all other exceptions, return generic message
return JSONResponse(
status_code=500,
content={
"detail": "An internal error occurred. Please contact support if the problem persists."
},
)