Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/middleware/security_headers_middleware.py: 100%

23 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 12:01 +0000

1""" 

2Adds anti-framing / content-type security headers to every HTTP response. 

3 

4X-Frame-Options and Content-Security-Policy: frame-ancestors 'none' stop the 

5admin UI and login pages from being embedded cross-origin (clickjacking). 

6X-Content-Type-Options: nosniff stops MIME sniffing. 

7 

8Strict-Transport-Security is opt-in via LITELLM_ENABLE_HSTS because it only 

9makes sense over HTTPS and would lock browsers out of plain-http deployments. 

10 

11Headers are set with setdefault so a route that intentionally sets its own 

12value is never overridden. 

13""" 

14 

15import os 

16from typing import Final 

17 

18from starlette.datastructures import MutableHeaders 

19from starlette.types import ASGIApp, Message, Receive, Scope, Send 

20 

21STATIC_SECURITY_HEADERS: Final = ( 

22 ("X-Frame-Options", "DENY"), 

23 ("Content-Security-Policy", "frame-ancestors 'none'"), 

24 ("X-Content-Type-Options", "nosniff"), 

25) 

26HSTS_HEADER: Final = ("Strict-Transport-Security", "max-age=31536000; includeSubDomains") 

27 

28 

29def _hsts_enabled() -> bool: 

30 return os.getenv("LITELLM_ENABLE_HSTS", "false").strip().lower() == "true" 

31 

32 

33class SecurityHeadersMiddleware: 

34 def __init__(self, app: ASGIApp) -> None: 

35 self.app = app 

36 

37 async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: 

38 if scope["type"] != "http": 

39 await self.app(scope, receive, send) 

40 return 

41 

42 async def send_with_security_headers(message: Message) -> None: 

43 if message["type"] == "http.response.start": 

44 headers: Final = MutableHeaders(scope=message) 

45 applied: Final = (*STATIC_SECURITY_HEADERS, HSTS_HEADER) if _hsts_enabled() else STATIC_SECURITY_HEADERS 

46 for name, value in applied: 

47 headers.setdefault(name, value) 

48 await send(message) 

49 

50 await self.app(scope, receive, send_with_security_headers)