Coverage for open_webui/utils/security_headers.py: 25%

81 statements  

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

1import os 

2import re 

3from typing import Dict 

4 

5 

6def set_security_headers() -> Dict[str, str]: 

7 """ 

8 Sets security headers based on environment variables. 

9 

10 This function reads specific environment variables and uses their values 

11 to set corresponding security headers. The headers that can be set are: 

12 - cache-control 

13 - permissions-policy 

14 - strict-transport-security 

15 - referrer-policy 

16 - x-content-type-options 

17 - x-download-options 

18 - x-frame-options 

19 - x-permitted-cross-domain-policies 

20 - content-security-policy 

21 - content-security-policy-report-only 

22 - cross-origin-embedder-policy 

23 - cross-origin-opener-policy 

24 - cross-origin-resource-policy 

25 - reporting-endpoints 

26 

27 Each environment variable is associated with a specific setter function 

28 that constructs the header. If the environment variable is set, the 

29 corresponding header is added to the options dictionary. 

30 

31 Returns: 

32 dict: A dictionary containing the security headers and their values. 

33 """ 

34 options = {} 

35 header_setters = { 

36 'CACHE_CONTROL': set_cache_control, 

37 'HSTS': set_hsts, 

38 'PERMISSIONS_POLICY': set_permissions_policy, 

39 'REFERRER_POLICY': set_referrer, 

40 'XCONTENT_TYPE': set_xcontent_type, 

41 'XDOWNLOAD_OPTIONS': set_xdownload_options, 

42 'XFRAME_OPTIONS': set_xframe, 

43 'XPERMITTED_CROSS_DOMAIN_POLICIES': set_xpermitted_cross_domain_policies, 

44 'CONTENT_SECURITY_POLICY': set_content_security_policy, 

45 'CONTENT_SECURITY_POLICY_REPORT_ONLY': set_content_security_policy_report_only, 

46 'CROSS_ORIGIN_EMBEDDER_POLICY': set_cross_origin_embedder_policy, 

47 'CROSS_ORIGIN_OPENER_POLICY': set_cross_origin_opener_policy, 

48 'CROSS_ORIGIN_RESOURCE_POLICY': set_cross_origin_resource_policy, 

49 'REPORTING_ENDPOINTS': set_reporting_endpoints, 

50 } 

51 

52 for env_var, setter in header_setters.items(): 

53 value = os.environ.get(env_var, None) 

54 if value: 54 ↛ 55line 54 didn't jump to line 55 because the condition on line 54 was never true

55 header = setter(value) 

56 if header: 

57 options.update(header) 

58 

59 return options 

60 

61 

62# Set HTTP Strict Transport Security(HSTS) response header 

63def set_hsts(value: str): 

64 pattern = r'^max-age=(\d+)(;includeSubDomains)?(;preload)?$' 

65 match = re.match(pattern, value, re.IGNORECASE) 

66 if not match: 

67 value = 'max-age=31536000;includeSubDomains' 

68 return {'Strict-Transport-Security': value} 

69 

70 

71# Set X-Frame-Options response header 

72def set_xframe(value: str): 

73 pattern = r'^(DENY|SAMEORIGIN)$' 

74 match = re.match(pattern, value, re.IGNORECASE) 

75 if not match: 

76 value = 'DENY' 

77 return {'X-Frame-Options': value} 

78 

79 

80# Set Permissions-Policy response header 

81def set_permissions_policy(value: str): 

82 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)?\),?)*$' 

83 match = re.match(pattern, value, re.IGNORECASE) 

84 if not match: 

85 value = 'none' 

86 return {'Permissions-Policy': value} 

87 

88 

89# Set Referrer-Policy response header 

90def set_referrer(value: str): 

91 pattern = r'^(no-referrer|no-referrer-when-downgrade|origin|origin-when-cross-origin|same-origin|strict-origin|strict-origin-when-cross-origin|unsafe-url)$' 

92 match = re.match(pattern, value, re.IGNORECASE) 

93 if not match: 

94 value = 'no-referrer' 

95 return {'Referrer-Policy': value} 

96 

97 

98# Set Cache-Control response header 

99def set_cache_control(value: str): 

100 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))*$' 

101 match = re.match(pattern, value, re.IGNORECASE) 

102 if not match: 

103 value = 'no-store, max-age=0' 

104 

105 return {'Cache-Control': value} 

106 

107 

108# Set X-Download-Options response header 

109def set_xdownload_options(value: str): 

110 if value != 'noopen': 

111 value = 'noopen' 

112 return {'X-Download-Options': value} 

113 

114 

115# Set X-Content-Type-Options response header 

116def set_xcontent_type(value: str): 

117 if value != 'nosniff': 

118 value = 'nosniff' 

119 return {'X-Content-Type-Options': value} 

120 

121 

122# Set X-Permitted-Cross-Domain-Policies response header 

123def set_xpermitted_cross_domain_policies(value: str): 

124 pattern = r'^(none|master-only|by-content-type|by-ftp-filename)$' 

125 match = re.match(pattern, value, re.IGNORECASE) 

126 if not match: 

127 value = 'none' 

128 return {'X-Permitted-Cross-Domain-Policies': value} 

129 

130 

131# Set Content-Security-Policy response header 

132def set_content_security_policy(value: str): 

133 return {'Content-Security-Policy': value} 

134 

135 

136# Set Content-Security-Policy-Report-Only response header 

137def set_content_security_policy_report_only(value: str): 

138 return {'Content-Security-Policy-Report-Only': value} 

139 

140 

141# Set Cross-Origin-Embedder-Policy response header 

142def set_cross_origin_embedder_policy(value: str): 

143 pattern = r'^(unsafe-none|require-corp|credentialless)$' 

144 match = re.match(pattern, value, re.IGNORECASE) 

145 if not match: 

146 value = 'require-corp' 

147 return {'Cross-Origin-Embedder-Policy': value} 

148 

149 

150# Set Cross-Origin-Opener-Policy response header 

151def set_cross_origin_opener_policy(value: str): 

152 pattern = r'^(unsafe-none|same-origin-allow-popups|same-origin)$' 

153 match = re.match(pattern, value, re.IGNORECASE) 

154 if not match: 

155 value = 'same-origin' 

156 return {'Cross-Origin-Opener-Policy': value} 

157 

158 

159# Set Cross-Origin-Resource-Policy response header 

160def set_cross_origin_resource_policy(value: str): 

161 pattern = r'^(same-site|same-origin|cross-origin)$' 

162 match = re.match(pattern, value, re.IGNORECASE) 

163 if not match: 

164 value = 'same-origin' 

165 return {'Cross-Origin-Resource-Policy': value} 

166 

167 

168# Set Reporting-Endpoints response header 

169def set_reporting_endpoints(value: str): 

170 return {'Reporting-Endpoints': value}