litellm/litellm/proxy/middleware/security_headers_middleware.py
Mateo Wang 48b5a5a0cc
style: unify ruff format width on 120 (#31518)
The repo linted at 120 (E501, isort) but ran ruff format at 88 via a
--line-length 88 override in the Makefile and CI, leaving the formatter
and the linter disagreeing on wrap width. Drop the override so ruff.toml's
line-length = 120 is the single source of truth and reformat the tree to
match.
2026-06-27 12:39:29 -07:00

49 lines
1.8 KiB
Python

"""
Adds anti-framing / content-type security headers to every HTTP response.
X-Frame-Options and Content-Security-Policy: frame-ancestors 'none' stop the
admin UI and login pages from being embedded cross-origin (clickjacking).
X-Content-Type-Options: nosniff stops MIME sniffing.
Strict-Transport-Security is opt-in via LITELLM_ENABLE_HSTS because it only
makes sense over HTTPS and would lock browsers out of plain-http deployments.
Headers are set with setdefault so a route that intentionally sets its own
value is never overridden.
"""
import os
from starlette.datastructures import MutableHeaders
from starlette.types import ASGIApp, Message, Receive, Scope, Send
STATIC_SECURITY_HEADERS = (
("X-Frame-Options", "DENY"),
("Content-Security-Policy", "frame-ancestors 'none'"),
("X-Content-Type-Options", "nosniff"),
)
HSTS_HEADER = ("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
def _hsts_enabled() -> bool:
return os.getenv("LITELLM_ENABLE_HSTS", "false").strip().lower() == "true"
class SecurityHeadersMiddleware:
def __init__(self, app: ASGIApp) -> None:
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] != "http":
await self.app(scope, receive, send)
return
async def send_with_security_headers(message: Message) -> None:
if message["type"] == "http.response.start":
headers = MutableHeaders(scope=message)
applied = (*STATIC_SECURITY_HEADERS, HSTS_HEADER) if _hsts_enabled() else STATIC_SECURITY_HEADERS
for name, value in applied:
headers.setdefault(name, value)
await send(message)
await self.app(scope, receive, send_with_security_headers)