mirror of
				https://github.com/dani-garcia/vaultwarden.git
				synced 2025-10-28 00:40:01 +02:00 
			
		
		
		
	Some Backend Admin fixes and updates (#5272)
* Some Backend Admin fixes and updates - Updated datatables - Added a `X-Robots-Tags` header to prevent indexing - Modified some layout settings - Added Websocket check to diagnostics - Added Security Header checks to diagnostics - Added Error page response checks to diagnostics - Modifed support string layout a bit Signed-off-by: BlackDex <black.dex@gmail.com> * Some small fixes Signed-off-by: BlackDex <black.dex@gmail.com> --------- Signed-off-by: BlackDex <black.dex@gmail.com>
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							620ad92331
						
					
				
				
					commit
					1ff17da7c0
				
			| @@ -62,6 +62,7 @@ pub fn routes() -> Vec<Route> { | ||||
|         diagnostics, | ||||
|         get_diagnostics_config, | ||||
|         resend_user_invite, | ||||
|         get_diagnostics_http, | ||||
|     ] | ||||
| } | ||||
|  | ||||
| @@ -713,6 +714,7 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, mut conn: DbConn) | ||||
|         "ip_header_name": ip_header_name, | ||||
|         "ip_header_config": &CONFIG.ip_header(), | ||||
|         "uses_proxy": uses_proxy, | ||||
|         "enable_websocket": &CONFIG.enable_websocket(), | ||||
|         "db_type": *DB_TYPE, | ||||
|         "db_version": get_sql_server_version(&mut conn).await, | ||||
|         "admin_url": format!("{}/diagnostics", admin_url()), | ||||
| @@ -734,6 +736,11 @@ fn get_diagnostics_config(_token: AdminToken) -> Json<Value> { | ||||
|     Json(support_json) | ||||
| } | ||||
|  | ||||
| #[get("/diagnostics/http?<code>")] | ||||
| fn get_diagnostics_http(code: u16, _token: AdminToken) -> EmptyResult { | ||||
|     err_code!(format!("Testing error {code} response"), code); | ||||
| } | ||||
|  | ||||
| #[post("/config", data = "<data>")] | ||||
| fn post_config(data: Json<ConfigBuilder>, _token: AdminToken) -> EmptyResult { | ||||
|     let data: ConfigBuilder = data.into_inner(); | ||||
|   | ||||
							
								
								
									
										4
									
								
								src/static/scripts/admin.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								src/static/scripts/admin.css
									
									
									
									
										vendored
									
									
								
							| @@ -38,8 +38,8 @@ img { | ||||
|     max-width: 130px; | ||||
| } | ||||
| #users-table .vw-actions, #orgs-table .vw-actions { | ||||
|     min-width: 130px; | ||||
|     max-width: 130px; | ||||
|     min-width: 135px; | ||||
|     max-width: 140px; | ||||
| } | ||||
| #users-table .vw-org-cell { | ||||
|     max-height: 120px; | ||||
|   | ||||
							
								
								
									
										212
									
								
								src/static/scripts/admin_diagnostics.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										212
									
								
								src/static/scripts/admin_diagnostics.js
									
									
									
									
										vendored
									
									
								
							| @@ -7,6 +7,8 @@ var timeCheck = false; | ||||
| var ntpTimeCheck = false; | ||||
| var domainCheck = false; | ||||
| var httpsCheck = false; | ||||
| var websocketCheck = false; | ||||
| var httpResponseCheck = false; | ||||
|  | ||||
| // ================================ | ||||
| // Date & Time Check | ||||
| @@ -76,18 +78,15 @@ async function generateSupportString(event, dj) { | ||||
|     event.preventDefault(); | ||||
|     event.stopPropagation(); | ||||
|  | ||||
|     let supportString = "### Your environment (Generated via diagnostics page)\n"; | ||||
|     let supportString = "### Your environment (Generated via diagnostics page)\n\n"; | ||||
|  | ||||
|     supportString += `* Vaultwarden version: v${dj.current_release}\n`; | ||||
|     supportString += `* Web-vault version: v${dj.web_vault_version}\n`; | ||||
|     supportString += `* OS/Arch: ${dj.host_os}/${dj.host_arch}\n`; | ||||
|     supportString += `* Running within a container: ${dj.running_within_container} (Base: ${dj.container_base_image})\n`; | ||||
|     supportString += "* Environment settings overridden: "; | ||||
|     if (dj.overrides != "") { | ||||
|         supportString += "true\n"; | ||||
|     } else { | ||||
|         supportString += "false\n"; | ||||
|     } | ||||
|     supportString += `* Database type: ${dj.db_type}\n`; | ||||
|     supportString += `* Database version: ${dj.db_version}\n`; | ||||
|     supportString += `* Environment settings overridden!: ${dj.overrides !== ""}\n`; | ||||
|     supportString += `* Uses a reverse proxy: ${dj.ip_header_exists}\n`; | ||||
|     if (dj.ip_header_exists) { | ||||
|         supportString += `* IP Header check: ${dj.ip_header_match} (${dj.ip_header_name})\n`; | ||||
| @@ -99,11 +98,12 @@ async function generateSupportString(event, dj) { | ||||
|     supportString += `* Server/NTP Time Check: ${ntpTimeCheck}\n`; | ||||
|     supportString += `* Domain Configuration Check: ${domainCheck}\n`; | ||||
|     supportString += `* HTTPS Check: ${httpsCheck}\n`; | ||||
|     supportString += `* Database type: ${dj.db_type}\n`; | ||||
|     supportString += `* Database version: ${dj.db_version}\n`; | ||||
|     supportString += "* Clients used: \n"; | ||||
|     supportString += "* Reverse proxy and version: \n"; | ||||
|     supportString += "* Other relevant information: \n"; | ||||
|     if (dj.enable_websocket) { | ||||
|         supportString += `* Websocket Check: ${websocketCheck}\n`; | ||||
|     } else { | ||||
|         supportString += "* Websocket Check: disabled\n"; | ||||
|     } | ||||
|     supportString += `* HTTP Response Checks: ${httpResponseCheck}\n`; | ||||
|  | ||||
|     const jsonResponse = await fetch(`${BASE_URL}/admin/diagnostics/config`, { | ||||
|         "headers": { "Accept": "application/json" } | ||||
| @@ -113,10 +113,30 @@ async function generateSupportString(event, dj) { | ||||
|         throw new Error(jsonResponse); | ||||
|     } | ||||
|     const configJson = await jsonResponse.json(); | ||||
|     supportString += "\n### Config (Generated via diagnostics page)\n<details><summary>Show Running Config</summary>\n"; | ||||
|     supportString += `\n**Environment settings which are overridden:** ${dj.overrides}\n`; | ||||
|     supportString += "\n\n```json\n" + JSON.stringify(configJson, undefined, 2) + "\n```\n</details>\n"; | ||||
|  | ||||
|     // Start Config and Details section within a details block which is collapsed by default | ||||
|     supportString += "\n### Config & Details (Generated via diagnostics page)\n\n"; | ||||
|     supportString += "<details><summary>Show Config & Details</summary>\n"; | ||||
|  | ||||
|     // Add overrides if they exists | ||||
|     if (dj.overrides != "") { | ||||
|         supportString += `\n**Environment settings which are overridden:** ${dj.overrides}\n`; | ||||
|     } | ||||
|  | ||||
|     // Add http response check messages if they exists | ||||
|     if (httpResponseCheck === false) { | ||||
|         supportString += "\n**Failed HTTP Checks:**\n"; | ||||
|         // We use `innerText` here since that will convert <br> into new-lines | ||||
|         supportString += "\n```yaml\n" + document.getElementById("http-response-errors").innerText.trim() + "\n```\n"; | ||||
|     } | ||||
|  | ||||
|     // Add the current config in json form | ||||
|     supportString += "\n**Config:**\n"; | ||||
|     supportString += "\n```json\n" + JSON.stringify(configJson, undefined, 2) + "\n```\n"; | ||||
|  | ||||
|     supportString += "\n</details>\n"; | ||||
|  | ||||
|     // Add the support string to the textbox so it can be viewed and copied | ||||
|     document.getElementById("support-string").textContent = supportString; | ||||
|     document.getElementById("support-string").classList.remove("d-none"); | ||||
|     document.getElementById("copy-support").classList.remove("d-none"); | ||||
| @@ -199,6 +219,162 @@ function checkDns(dns_resolved) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function fetchCheckUrl(url) { | ||||
|     try { | ||||
|         const response = await fetch(url); | ||||
|         return { headers: response.headers, status: response.status, text: await response.text() }; | ||||
|     } catch (error) { | ||||
|         console.error(`Error fetching ${url}: ${error}`); | ||||
|         return { error }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| function checkSecurityHeaders(headers, omit) { | ||||
|     let securityHeaders = { | ||||
|         "x-frame-options": ["SAMEORIGIN"], | ||||
|         "x-content-type-options": ["nosniff"], | ||||
|         "referrer-policy": ["same-origin"], | ||||
|         "x-xss-protection": ["0"], | ||||
|         "x-robots-tag": ["noindex", "nofollow"], | ||||
|         "content-security-policy": [ | ||||
|             "default-src 'self'", | ||||
|             "base-uri 'self'", | ||||
|             "form-action 'self'", | ||||
|             "object-src 'self' blob:", | ||||
|             "script-src 'self' 'wasm-unsafe-eval'", | ||||
|             "style-src 'self' 'unsafe-inline'", | ||||
|             "child-src 'self' https://*.duosecurity.com https://*.duofederal.com", | ||||
|             "frame-src 'self' https://*.duosecurity.com https://*.duofederal.com", | ||||
|             "frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh moz-extension://*", | ||||
|             "img-src 'self' data: https://haveibeenpwned.com", | ||||
|             "connect-src 'self' https://api.pwnedpasswords.com https://api.2fa.directory https://app.simplelogin.io/api/ https://app.addy.io/api/ https://api.fastmail.com/ https://api.forwardemail.net", | ||||
|         ] | ||||
|     }; | ||||
|  | ||||
|     let messages = []; | ||||
|     for (let header in securityHeaders) { | ||||
|         // Skip some headers for specific endpoints if needed | ||||
|         if (typeof omit === "object" && omit.includes(header) === true) { | ||||
|             continue; | ||||
|         } | ||||
|         // If the header exists, check if the contents matches what we expect it to be | ||||
|         let headerValue = headers.get(header); | ||||
|         if (headerValue !== null) { | ||||
|             securityHeaders[header].forEach((expectedValue) => { | ||||
|                 if (headerValue.indexOf(expectedValue) === -1) { | ||||
|                     messages.push(`'${header}' does not contain '${expectedValue}'`); | ||||
|                 } | ||||
|             }); | ||||
|         } else { | ||||
|             messages.push(`'${header}' is missing!`); | ||||
|         } | ||||
|     } | ||||
|     return messages; | ||||
| } | ||||
|  | ||||
| async function checkHttpResponse() { | ||||
|     const [apiConfig, webauthnConnector, notFound, notFoundApi, badRequest, unauthorized, forbidden] = await Promise.all([ | ||||
|         fetchCheckUrl(`${BASE_URL}/api/config`), | ||||
|         fetchCheckUrl(`${BASE_URL}/webauthn-connector.html`), | ||||
|         fetchCheckUrl(`${BASE_URL}/admin/does-not-exist`), | ||||
|         fetchCheckUrl(`${BASE_URL}/admin/diagnostics/http?code=404`), | ||||
|         fetchCheckUrl(`${BASE_URL}/admin/diagnostics/http?code=400`), | ||||
|         fetchCheckUrl(`${BASE_URL}/admin/diagnostics/http?code=401`), | ||||
|         fetchCheckUrl(`${BASE_URL}/admin/diagnostics/http?code=403`), | ||||
|     ]); | ||||
|  | ||||
|     const respErrorElm = document.getElementById("http-response-errors"); | ||||
|  | ||||
|     // Check and validate the default API header responses | ||||
|     let apiErrors = checkSecurityHeaders(apiConfig.headers); | ||||
|     if (apiErrors.length >= 1) { | ||||
|         respErrorElm.innerHTML += "<b>API calls:</b><br>"; | ||||
|         apiErrors.forEach((errMsg) => { | ||||
|             respErrorElm.innerHTML += `<b>Header:</b> ${errMsg}<br>`; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     // Check the special `-connector.html` headers, these should have some headers omitted. | ||||
|     const omitConnectorHeaders = ["x-frame-options", "content-security-policy"]; | ||||
|     let connectorErrors = checkSecurityHeaders(webauthnConnector.headers, omitConnectorHeaders); | ||||
|     omitConnectorHeaders.forEach((header) => { | ||||
|         if (webauthnConnector.headers.get(header) !== null) { | ||||
|             connectorErrors.push(`'${header}' is present while it should not`); | ||||
|         } | ||||
|     }); | ||||
|     if (connectorErrors.length >= 1) { | ||||
|         respErrorElm.innerHTML += "<b>2FA Connector calls:</b><br>"; | ||||
|         connectorErrors.forEach((errMsg) => { | ||||
|             respErrorElm.innerHTML += `<b>Header:</b> ${errMsg}<br>`; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     // Check specific error code responses if they are not re-written by a reverse proxy | ||||
|     let responseErrors = []; | ||||
|     if (notFound.status !== 404 || notFound.text.indexOf("return to the web-vault") === -1) { | ||||
|         responseErrors.push("404 (Not Found) HTML is invalid"); | ||||
|     } | ||||
|  | ||||
|     if (notFoundApi.status !== 404 || notFoundApi.text.indexOf("\"message\":\"Testing error 404 response\",") === -1) { | ||||
|         responseErrors.push("404 (Not Found) JSON is invalid"); | ||||
|     } | ||||
|  | ||||
|     if (badRequest.status !== 400 || badRequest.text.indexOf("\"message\":\"Testing error 400 response\",") === -1) { | ||||
|         responseErrors.push("400 (Bad Request) is invalid"); | ||||
|     } | ||||
|  | ||||
|     if (unauthorized.status !== 401 || unauthorized.text.indexOf("\"message\":\"Testing error 401 response\",") === -1) { | ||||
|         responseErrors.push("401 (Unauthorized) is invalid"); | ||||
|     } | ||||
|  | ||||
|     if (forbidden.status !== 403 || forbidden.text.indexOf("\"message\":\"Testing error 403 response\",") === -1) { | ||||
|         responseErrors.push("403 (Forbidden) is invalid"); | ||||
|     } | ||||
|  | ||||
|     if (responseErrors.length >= 1) { | ||||
|         respErrorElm.innerHTML += "<b>HTTP error responses:</b><br>"; | ||||
|         responseErrors.forEach((errMsg) => { | ||||
|             respErrorElm.innerHTML += `<b>Response to:</b> ${errMsg}<br>`; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     if (responseErrors.length >= 1 || connectorErrors.length >= 1 || apiErrors.length >= 1) { | ||||
|         document.getElementById("http-response-warning").classList.remove("d-none"); | ||||
|     } else { | ||||
|         httpResponseCheck = true; | ||||
|         document.getElementById("http-response-success").classList.remove("d-none"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function fetchWsUrl(wsUrl) { | ||||
|     return new Promise((resolve, reject) => { | ||||
|         try { | ||||
|             const ws = new WebSocket(wsUrl); | ||||
|             ws.onopen = () => { | ||||
|                 ws.close(); | ||||
|                 resolve(true); | ||||
|             }; | ||||
|  | ||||
|             ws.onerror = () => { | ||||
|                 reject(false); | ||||
|             }; | ||||
|         } catch (_) { | ||||
|             reject(false); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
| async function checkWebsocketConnection() { | ||||
|     // Test Websocket connections via the anonymous (login with device) connection | ||||
|     const isConnected = await fetchWsUrl(`${BASE_URL}/notifications/anonymous-hub?token=admin-diagnostics`).catch(() => false); | ||||
|     if (isConnected) { | ||||
|         websocketCheck = true; | ||||
|         document.getElementById("websocket-success").classList.remove("d-none"); | ||||
|     } else { | ||||
|         document.getElementById("websocket-error").classList.remove("d-none"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function init(dj) { | ||||
|     // Time check | ||||
|     document.getElementById("time-browser-string").textContent = browserUTC; | ||||
| @@ -225,6 +401,12 @@ function init(dj) { | ||||
|  | ||||
|     // DNS Check | ||||
|     checkDns(dj.dns_resolved); | ||||
|  | ||||
|     checkHttpResponse(); | ||||
|  | ||||
|     if (dj.enable_websocket) { | ||||
|         checkWebsocketConnection(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // onLoad events | ||||
|   | ||||
							
								
								
									
										42
									
								
								src/static/scripts/datatables.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										42
									
								
								src/static/scripts/datatables.css
									
									
									
									
										vendored
									
									
								
							| @@ -4,10 +4,10 @@ | ||||
|  * | ||||
|  * To rebuild or modify this file with the latest versions of the included | ||||
|  * software please visit: | ||||
|  *   https://datatables.net/download/#bs5/dt-2.0.8 | ||||
|  *   https://datatables.net/download/#bs5/dt-2.1.8 | ||||
|  * | ||||
|  * Included libraries: | ||||
|  *   DataTables 2.0.8 | ||||
|  *   DataTables 2.1.8 | ||||
|  */ | ||||
|  | ||||
| @charset "UTF-8"; | ||||
| @@ -45,15 +45,21 @@ table.dataTable tr.dt-hasChild td.dt-control:before { | ||||
| } | ||||
|  | ||||
| html.dark table.dataTable td.dt-control:before, | ||||
| :root[data-bs-theme=dark] table.dataTable td.dt-control:before { | ||||
| :root[data-bs-theme=dark] table.dataTable td.dt-control:before, | ||||
| :root[data-theme=dark] table.dataTable td.dt-control:before { | ||||
|   border-left-color: rgba(255, 255, 255, 0.5); | ||||
| } | ||||
| html.dark table.dataTable tr.dt-hasChild td.dt-control:before, | ||||
| :root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before { | ||||
| :root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before, | ||||
| :root[data-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before { | ||||
|   border-top-color: rgba(255, 255, 255, 0.5); | ||||
|   border-left-color: transparent; | ||||
| } | ||||
|  | ||||
| div.dt-scroll { | ||||
|   width: 100%; | ||||
| } | ||||
|  | ||||
| div.dt-scroll-body thead tr, | ||||
| div.dt-scroll-body tfoot tr { | ||||
|   height: 0; | ||||
| @@ -377,6 +383,31 @@ table.table.dataTable.table-hover > tbody > tr.selected:hover > * { | ||||
|   box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975); | ||||
| } | ||||
|  | ||||
| div.dt-container div.dt-layout-start > *:not(:last-child) { | ||||
|   margin-right: 1em; | ||||
| } | ||||
| div.dt-container div.dt-layout-end > *:not(:first-child) { | ||||
|   margin-left: 1em; | ||||
| } | ||||
| div.dt-container div.dt-layout-full { | ||||
|   width: 100%; | ||||
| } | ||||
| div.dt-container div.dt-layout-full > *:only-child { | ||||
|   margin-left: auto; | ||||
|   margin-right: auto; | ||||
| } | ||||
| div.dt-container div.dt-layout-table > div { | ||||
|   display: block !important; | ||||
| } | ||||
|  | ||||
| @media screen and (max-width: 767px) { | ||||
|   div.dt-container div.dt-layout-start > *:not(:last-child) { | ||||
|     margin-right: 0; | ||||
|   } | ||||
|   div.dt-container div.dt-layout-end > *:not(:first-child) { | ||||
|     margin-left: 0; | ||||
|   } | ||||
| } | ||||
| div.dt-container div.dt-length label { | ||||
|   font-weight: normal; | ||||
|   text-align: left; | ||||
| @@ -400,9 +431,6 @@ div.dt-container div.dt-search input { | ||||
|   display: inline-block; | ||||
|   width: auto; | ||||
| } | ||||
| div.dt-container div.dt-info { | ||||
|   padding-top: 0.85em; | ||||
| } | ||||
| div.dt-container div.dt-paging { | ||||
|   margin: 0; | ||||
| } | ||||
|   | ||||
							
								
								
									
										1142
									
								
								src/static/scripts/datatables.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1142
									
								
								src/static/scripts/datatables.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -132,6 +132,21 @@ | ||||
|                         <span class="d-block" title="We have direct internet access, no outgoing proxy configured."><b>No</b></span> | ||||
|                     {{/unless}} | ||||
|                     </dd> | ||||
|                     <dt class="col-sm-5">Websocket enabled | ||||
|                         {{#if page_data.enable_websocket}} | ||||
|                         <span class="badge bg-success d-none" id="websocket-success" title="Websocket connection is working.">Ok</span> | ||||
|                         <span class="badge bg-danger d-none" id="websocket-error" title="Websocket connection error, validate your reverse proxy configuration!">Error</span> | ||||
|                         {{/if}} | ||||
|                     </dt> | ||||
|                     <dd class="col-sm-7"> | ||||
|                     {{#if page_data.enable_websocket}} | ||||
|                         <span class="d-block" title="Websocket connections are enabled (ENABLE_WEBSOCKET is true)."><b>Yes</b></span> | ||||
|                     {{/if}} | ||||
|                     {{#unless page_data.enable_websocket}} | ||||
|                         <span class="d-block" title="Websocket connections are disabled (ENABLE_WEBSOCKET is false)."><b>No</b></span> | ||||
|                     {{/unless}} | ||||
|                     </dd> | ||||
|  | ||||
|                     <dt class="col-sm-5">DNS (github.com) | ||||
|                         <span class="badge bg-success d-none" id="dns-success" title="DNS Resolving works!">Ok</span> | ||||
|                         <span class="badge bg-danger d-none" id="dns-warning" title="DNS Resolving failed. Please fix.">Error</span> | ||||
| @@ -167,6 +182,14 @@ | ||||
|                         <span id="domain-server" class="d-block"><b>Server:</b> <span id="domain-server-string">{{page_data.admin_url}}</span></span> | ||||
|                         <span id="domain-browser" class="d-block"><b>Browser:</b> <span id="domain-browser-string"></span></span> | ||||
|                     </dd> | ||||
|  | ||||
|                     <dt class="col-sm-5">HTTP Response validation | ||||
|                         <span class="badge bg-success d-none" id="http-response-success" title="All headers and HTTP request responses seem to be ok.">Ok</span> | ||||
|                         <span class="badge bg-danger d-none" id="http-response-warning" title="Some headers or HTTP request responses return invalid data!">Error</span> | ||||
|                     </dt> | ||||
|                     <dd class="col-sm-7"> | ||||
|                         <span id="http-response-errors" class="d-block"></span> | ||||
|                     </dd> | ||||
|                 </dl> | ||||
|             </div> | ||||
|         </div> | ||||
|   | ||||
| @@ -19,7 +19,7 @@ | ||||
|                     <tr> | ||||
|                         <td> | ||||
|                             <svg width="48" height="48" class="float-start me-2 rounded" data-jdenticon-value="{{email}}"> | ||||
|                             <div class="float-start"> | ||||
|                             <div> | ||||
|                                 <strong>{{name}}</strong> | ||||
|                                 <span class="d-block">{{email}}</span> | ||||
|                                 <span class="d-block"> | ||||
|   | ||||
| @@ -51,9 +51,11 @@ impl Fairing for AppHeaders { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // NOTE: When modifying or adding security headers be sure to also update the diagnostic checks in `src/static/scripts/admin_diagnostics.js` in `checkSecurityHeaders` | ||||
|         res.set_raw_header("Permissions-Policy", "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()"); | ||||
|         res.set_raw_header("Referrer-Policy", "same-origin"); | ||||
|         res.set_raw_header("X-Content-Type-Options", "nosniff"); | ||||
|         res.set_raw_header("X-Robots-Tag", "noindex, nofollow"); | ||||
|         // Obsolete in modern browsers, unsafe (XS-Leak), and largely replaced by CSP | ||||
|         res.set_raw_header("X-XSS-Protection", "0"); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user