Harden backend security: IDOR fix, error sanitization, rate limiter fallback, security headers

- Fix task status IDOR by adding ownership check; suppress traceback/error in production
- Passkey routes: return generic error messages for internal exceptions, keep ValueError for user-facing
- JWT_SECRET and OIDC_CLIENT_ID: raise RuntimeError in production when using defaults
- Rate limiter: add in-memory fallback counter when Redis is unavailable
- Fix X-Forwarded-For IP spoofing with trusted_proxy_depth (rightmost-N selection)
- Add SecurityHeadersMiddleware (X-Content-Type-Options, X-Frame-Options, CSP, conditional HSTS)
- CORS: add PUT/DELETE methods for POI routes
- POI input validation: field length and coordinate range constraints
- QueryParameters: add min_sqm <= max_sqm validation
This commit is contained in:
Viktor Barzin 2026-02-08 19:42:30 +00:00
parent e431eaf2aa
commit 0a9a83507e
No known key found for this signature in database
GPG key ID: 0EB088298288D958
8 changed files with 133 additions and 32 deletions

View file

@ -5,15 +5,16 @@ import logging
import logging.config
from typing import Annotated, AsyncGenerator, Optional
from api.auth import get_current_user
from api.config import DEV_TIER_ORIGINS, PROD_TIER_ORIGINS
from api.config import DEV_TIER_ORIGINS, PROD_TIER_ORIGINS, APP_ENV
from api.passkey_routes import passkey_router
from api.poi_routes import poi_router
from api.rate_limit_config import RateLimitConfig
from api.rate_limiter import RateLimitMiddleware
from api.audit_middleware import AuditLogMiddleware
from api.metrics_guard import MetricsGuardMiddleware
from api.security_headers import SecurityHeadersMiddleware
from dotenv import load_dotenv
from fastapi import Depends, FastAPI, Query
from fastapi import Depends, FastAPI, HTTPException, Query
from fastapi.responses import StreamingResponse
from api.auth import User
from models.listing import QueryParameters, ListingType, FurnishType
@ -103,7 +104,7 @@ hist = meter.create_histogram(
app.add_middleware(
CORSMiddleware,
allow_origins=[*DEV_TIER_ORIGINS, *PROD_TIER_ORIGINS],
allow_methods=["GET", "POST"],
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
@ -114,6 +115,8 @@ app.add_middleware(RateLimitMiddleware, config=_rate_limit_config)
app.add_middleware(MetricsGuardMiddleware, config=_rate_limit_config)
# 1. Audit logging — logs everything including 429s and 403s
app.add_middleware(AuditLogMiddleware)
# 0. Security headers — adds standard security headers to all responses
app.add_middleware(SecurityHeadersMiddleware)
@app.get("/api/status")
@ -324,6 +327,9 @@ async def get_task_status(
task_id: str,
) -> dict[str, str | int | float | None]:
"""Get the status of a background task."""
user_tasks = task_service.get_user_tasks(user.email)
if task_id not in user_tasks:
raise HTTPException(status_code=404, detail="Task not found")
status = task_service.get_task_status(task_id)
return {
"task_id": status.task_id,
@ -333,8 +339,8 @@ async def get_task_status(
"processed": status.processed,
"total": status.total,
"message": status.message,
"error": status.error,
"traceback": status.traceback,
"error": status.error if APP_ENV != "production" else None,
"traceback": status.traceback if APP_ENV != "production" else None,
}