"""Production HTTP hardening: CORS config + security headers. These are small cross-cutting concerns kept in one module so they're easy to audit. Environment knobs (read at import time by ``fastapi_app.py``): * TAU_RAG_CORS_ORIGINS — comma-separated list; "*" allows all; default: "" * TAU_RAG_CORS_METHODS — comma-separated; default: GET,POST,PUT,DELETE,OPTIONS * TAU_RAG_CORS_HEADERS — comma-separated; default: "*" * TAU_RAG_HSTS — 1/0 — add Strict-Transport-Security (default 0) * TAU_RAG_CSP — full Content-Security-Policy string (optional) The default set of security headers is applied on every response; they are cheap and correct by default: * X-Content-Type-Options: nosniff * X-Frame-Options: DENY * Referrer-Policy: no-referrer * X-XSS-Protection: 0 (modern browsers — CSP is the correct defence) * Strict-Transport-Security: added only when TAU_RAG_HSTS=1 * Content-Security-Policy: added only when TAU_RAG_CSP is set """ from __future__ import annotations import os from typing import Iterable, List, Optional _DEFAULT_HEADERS = { "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY", "Referrer-Policy": "no-referrer", "X-XSS-Protection": "0", } def parse_origins(raw: Optional[str]) -> List[str]: """Split a comma-separated env var into a normalized list.""" if not raw: return [] return [o.strip() for o in raw.split(",") if o.strip()] def cors_config_from_env() -> dict: """Build kwargs for ``CORSMiddleware`` from env.""" return { "allow_origins": parse_origins(os.environ.get("TAU_RAG_CORS_ORIGINS")), "allow_methods": parse_origins( os.environ.get("TAU_RAG_CORS_METHODS") or "GET,POST,PUT,DELETE,OPTIONS"), "allow_headers": parse_origins( os.environ.get("TAU_RAG_CORS_HEADERS") or "*"), "allow_credentials": False, # safer default; opt-in via code if needed } def apply_security_headers(response_headers: dict) -> dict: """Mutate & return a headers dict with the standard security set.""" for k, v in _DEFAULT_HEADERS.items(): response_headers.setdefault(k, v) if os.environ.get("TAU_RAG_HSTS") == "1": response_headers.setdefault( "Strict-Transport-Security", "max-age=31536000; includeSubDomains", ) csp = os.environ.get("TAU_RAG_CSP") if csp: response_headers.setdefault("Content-Security-Policy", csp) return response_headers __all__ = [ "parse_origins", "cors_config_from_env", "apply_security_headers", ]