security: fix CORS wildcard, add security headers, enforce role check, sanitize errors
- CORS: remove allow_origins=["*"]; restrict to ALLOWED_ORIGINS env var (comma-separated list); default is no cross-origin access. Removed allow_credentials=True and method/header wildcards. - Security headers middleware: add X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, Referrer-Policy, Strict-Transport-Security to all responses. - users.py: guard POST /api/users so only users with role="admin" can create new accounts (prevents privilege escalation by non-admin roles). - auth.py: remove raw exception detail from Azure AD 500 response to avoid leaking internal error messages / stack traces to clients. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
31
app/main.py
31
app/main.py
@@ -43,15 +43,36 @@ app = FastAPI(
|
||||
app.state.limiter = limiter
|
||||
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
||||
|
||||
# CORS — allow same-origin; adjust if needed
|
||||
# CORS — restrict to explicitly configured origins only.
|
||||
# Set ALLOWED_ORIGINS in .env as a comma-separated list of allowed origins,
|
||||
# e.g. ALLOWED_ORIGINS=https://myapp.example.com
|
||||
# If unset, no cross-origin requests are allowed (same-origin only).
|
||||
_raw_origins = os.environ.get("ALLOWED_ORIGINS", "")
|
||||
_allowed_origins = [o.strip() for o in _raw_origins.split(",") if o.strip()]
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
allow_origins=_allowed_origins,
|
||||
allow_credentials=False,
|
||||
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||||
allow_headers=["Authorization", "Content-Type"],
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Security headers middleware
|
||||
# ---------------------------------------------------------------------------
|
||||
@app.middleware("http")
|
||||
async def add_security_headers(request: Request, call_next):
|
||||
"""Attach standard security headers to every response."""
|
||||
response = await call_next(request)
|
||||
response.headers["X-Content-Type-Options"] = "nosniff"
|
||||
response.headers["X-Frame-Options"] = "DENY"
|
||||
response.headers["X-XSS-Protection"] = "1; mode=block"
|
||||
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
|
||||
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
|
||||
return response
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Routers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user