Source code for apifrom.utils.security_headers
from typing import Dict, List, Optional, Union, Set, Any, Callable
[docs]
def create_security_headers(
content_security_policy: Optional[Dict[str, Union[str, List[str]]]] = None,
x_frame_options: Optional[str] = None,
x_content_type_options: str = "nosniff",
referrer_policy: Optional[str] = None,
strict_transport_security: Optional[Dict[str, Any]] = None,
permissions_policy: Optional[Dict[str, Union[bool, str, List[str]]]] = None,
x_xss_protection: str = "1; mode=block",
cache_control: Optional[str] = None,
exempt_paths: Optional[List[str]] = None,
exempt_content_types: Optional[List[str]] = None
) -> Dict[str, str]:
"""
Create a dictionary of security headers for HTTP responses.
Args:
content_security_policy: CSP directives as a dictionary
x_frame_options: X-Frame-Options header value (e.g. "DENY", "SAMEORIGIN")
x_content_type_options: X-Content-Type-Options header value
referrer_policy: Referrer-Policy header value
strict_transport_security: HSTS configuration as a dictionary
permissions_policy: Permissions Policy directives as a dictionary
x_xss_protection: X-XSS-Protection header value
cache_control: Cache-Control header value
exempt_paths: List of URL paths exempt from security headers
exempt_content_types: List of content types exempt from security headers
Returns:
Dictionary of security headers
"""
headers = {}
if content_security_policy:
csp_value = build_csp(content_security_policy)
if csp_value:
headers["Content-Security-Policy"] = csp_value
if x_frame_options:
headers["X-Frame-Options"] = x_frame_options
if x_content_type_options:
headers["X-Content-Type-Options"] = x_content_type_options
if referrer_policy:
headers["Referrer-Policy"] = referrer_policy
if strict_transport_security:
hsts_value = build_hsts(strict_transport_security)
if hsts_value:
headers["Strict-Transport-Security"] = hsts_value
if permissions_policy:
pp_value = build_permissions_policy(permissions_policy)
if pp_value:
headers["Permissions-Policy"] = pp_value
if x_xss_protection:
headers["X-XSS-Protection"] = x_xss_protection
if cache_control:
headers["Cache-Control"] = cache_control
return headers
[docs]
def build_csp(directives: Dict[str, Union[str, List[str]]]) -> str:
"""
Build a Content-Security-Policy header value from directives.
Args:
directives: Dictionary of CSP directives
Returns:
CSP header value as string
"""
parts = []
for directive, sources in directives.items():
if isinstance(sources, list):
parts.append(f"{directive} {' '.join(sources)}")
else:
parts.append(f"{directive} {sources}")
return "; ".join(parts)
[docs]
def build_hsts(config: Dict[str, Any]) -> str:
"""
Build a Strict-Transport-Security header value.
Args:
config: HSTS configuration options
Returns:
HSTS header value as string
"""
parts = []
if "max_age" in config:
parts.append(f"max-age={config['max_age']}")
if config.get("include_subdomains", False):
parts.append("includeSubDomains")
if config.get("preload", False):
parts.append("preload")
return "; ".join(parts)
[docs]
def build_permissions_policy(directives: Dict[str, Union[bool, str, List[str]]]) -> str:
"""
Build a Permissions-Policy header value.
Args:
directives: Dictionary of permissions policy directives
Returns:
Permissions-Policy header value as string
"""
parts = []
for feature, allowlist in directives.items():
if allowlist is True:
parts.append(f"{feature}=*")
elif allowlist is False:
parts.append(f"{feature}=()")
elif isinstance(allowlist, str):
parts.append(f"{feature}=({allowlist})")
elif isinstance(allowlist, list):
formatted_sources = [f'"{source}"' for source in allowlist]
parts.append(f"{feature}=({' '.join(formatted_sources)})")
return ", ".join(parts)
[docs]
def should_apply_security_headers(
path: str,
content_type: Optional[str] = None,
exempt_paths: Optional[List[str]] = None,
exempt_content_types: Optional[List[str]] = None
) -> bool:
"""
Determine if security headers should be applied to a response.
Args:
path: URL path of the request
content_type: Content-Type of the response
exempt_paths: List of URL paths exempt from security headers
exempt_content_types: List of content types exempt from security headers
Returns:
True if security headers should be applied, False otherwise
"""
# Check exempt paths
if exempt_paths:
for exempt_path in exempt_paths:
if path.startswith(exempt_path):
return False
# Check exempt content types
if exempt_content_types and content_type:
for exempt_type in exempt_content_types:
if content_type.startswith(exempt_type):
return False
return True