Spaces:
Build error
Build error
File size: 6,360 Bytes
87a665c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | import re
import os
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
from typing import Dict
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
response.headers.update(set_security_headers())
return response
def set_security_headers() -> Dict[str, str]:
"""
Sets security headers based on environment variables.
This function reads specific environment variables and uses their values
to set corresponding security headers. The headers that can be set are:
- cache-control
- permissions-policy
- strict-transport-security
- referrer-policy
- x-content-type-options
- x-download-options
- x-frame-options
- x-permitted-cross-domain-policies
- content-security-policy
- content-security-policy-report-only
- cross-origin-embedder-policy
- cross-origin-opener-policy
- cross-origin-resource-policy
- reporting-endpoints
Each environment variable is associated with a specific setter function
that constructs the header. If the environment variable is set, the
corresponding header is added to the options dictionary.
Returns:
dict: A dictionary containing the security headers and their values.
"""
options = {}
header_setters = {
'CACHE_CONTROL': set_cache_control,
'HSTS': set_hsts,
'PERMISSIONS_POLICY': set_permissions_policy,
'REFERRER_POLICY': set_referrer,
'XCONTENT_TYPE': set_xcontent_type,
'XDOWNLOAD_OPTIONS': set_xdownload_options,
'XFRAME_OPTIONS': set_xframe,
'XPERMITTED_CROSS_DOMAIN_POLICIES': set_xpermitted_cross_domain_policies,
'CONTENT_SECURITY_POLICY': set_content_security_policy,
'CONTENT_SECURITY_POLICY_REPORT_ONLY': set_content_security_policy_report_only,
'CROSS_ORIGIN_EMBEDDER_POLICY': set_cross_origin_embedder_policy,
'CROSS_ORIGIN_OPENER_POLICY': set_cross_origin_opener_policy,
'CROSS_ORIGIN_RESOURCE_POLICY': set_cross_origin_resource_policy,
'REPORTING_ENDPOINTS': set_reporting_endpoints,
}
for env_var, setter in header_setters.items():
value = os.environ.get(env_var, None)
if value:
header = setter(value)
if header:
options.update(header)
return options
# Set HTTP Strict Transport Security(HSTS) response header
def set_hsts(value: str):
pattern = r'^max-age=(\d+)(;includeSubDomains)?(;preload)?$'
match = re.match(pattern, value, re.IGNORECASE)
if not match:
value = 'max-age=31536000;includeSubDomains'
return {'Strict-Transport-Security': value}
# Set X-Frame-Options response header
def set_xframe(value: str):
pattern = r'^(DENY|SAMEORIGIN)$'
match = re.match(pattern, value, re.IGNORECASE)
if not match:
value = 'DENY'
return {'X-Frame-Options': value}
# Set Permissions-Policy response header
def set_permissions_policy(value: str):
pattern = r'^(?:(accelerometer|autoplay|camera|clipboard-read|clipboard-write|fullscreen|geolocation|gyroscope|magnetometer|microphone|midi|payment|picture-in-picture|sync-xhr|usb|xr-spatial-tracking)=\((self)?\),?)*$'
match = re.match(pattern, value, re.IGNORECASE)
if not match:
value = 'none'
return {'Permissions-Policy': value}
# Set Referrer-Policy response header
def set_referrer(value: str):
pattern = r'^(no-referrer|no-referrer-when-downgrade|origin|origin-when-cross-origin|same-origin|strict-origin|strict-origin-when-cross-origin|unsafe-url)$'
match = re.match(pattern, value, re.IGNORECASE)
if not match:
value = 'no-referrer'
return {'Referrer-Policy': value}
# Set Cache-Control response header
def set_cache_control(value: str):
pattern = r'^(public|private|no-cache|no-store|must-revalidate|proxy-revalidate|max-age=\d+|s-maxage=\d+|no-transform|immutable)(,\s*(public|private|no-cache|no-store|must-revalidate|proxy-revalidate|max-age=\d+|s-maxage=\d+|no-transform|immutable))*$'
match = re.match(pattern, value, re.IGNORECASE)
if not match:
value = 'no-store, max-age=0'
return {'Cache-Control': value}
# Set X-Download-Options response header
def set_xdownload_options(value: str):
if value != 'noopen':
value = 'noopen'
return {'X-Download-Options': value}
# Set X-Content-Type-Options response header
def set_xcontent_type(value: str):
if value != 'nosniff':
value = 'nosniff'
return {'X-Content-Type-Options': value}
# Set X-Permitted-Cross-Domain-Policies response header
def set_xpermitted_cross_domain_policies(value: str):
pattern = r'^(none|master-only|by-content-type|by-ftp-filename)$'
match = re.match(pattern, value, re.IGNORECASE)
if not match:
value = 'none'
return {'X-Permitted-Cross-Domain-Policies': value}
# Set Content-Security-Policy response header
def set_content_security_policy(value: str):
return {'Content-Security-Policy': value}
# Set Content-Security-Policy-Report-Only response header
def set_content_security_policy_report_only(value: str):
return {'Content-Security-Policy-Report-Only': value}
# Set Cross-Origin-Embedder-Policy response header
def set_cross_origin_embedder_policy(value: str):
pattern = r'^(unsafe-none|require-corp|credentialless)$'
match = re.match(pattern, value, re.IGNORECASE)
if not match:
value = 'require-corp'
return {'Cross-Origin-Embedder-Policy': value}
# Set Cross-Origin-Opener-Policy response header
def set_cross_origin_opener_policy(value: str):
pattern = r'^(unsafe-none|same-origin-allow-popups|same-origin)$'
match = re.match(pattern, value, re.IGNORECASE)
if not match:
value = 'same-origin'
return {'Cross-Origin-Opener-Policy': value}
# Set Cross-Origin-Resource-Policy response header
def set_cross_origin_resource_policy(value: str):
pattern = r'^(same-site|same-origin|cross-origin)$'
match = re.match(pattern, value, re.IGNORECASE)
if not match:
value = 'same-origin'
return {'Cross-Origin-Resource-Policy': value}
# Set Reporting-Endpoints response header
def set_reporting_endpoints(value: str):
return {'Reporting-Endpoints': value}
|