async fn security_headers(request: Request, next: Next) -> Response { let path = request.uri().path(); let is_api = path == "/api" || path.starts_with("/api/"); let is_api_docs = path == "/api-docs" || path.starts_with("/api-docs/"); let is_custom_chart = path.starts_with("/charts/custom/"); let mut response = next.run(request).await; let is_html = response .headers() .get(header::CONTENT_TYPE) .and_then(|value| value.to_str().ok()) .is_some_and(|value| { value .split(';') .next() .is_some_and(|mime| mime.trim().eq_ignore_ascii_case("text/html")) }); let headers = response.headers_mut(); headers.insert( header::HeaderName::from_static("x-content-type-options"), HeaderValue::from_static("nosniff"), ); headers.insert( header::HeaderName::from_static("referrer-policy"), HeaderValue::from_static(if is_custom_chart { "no-referrer" } else { "same-origin" }), ); if is_html { headers.insert( header::HeaderName::from_static("x-frame-options"), HeaderValue::from_static("SAMEORIGIN"), ); headers.insert( header::HeaderName::from_static("content-security-policy"), HeaderValue::from_static( "default-src 'self'; connect-src 'self' ws: wss:; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; object-src 'none'" ), ); headers.insert( header::HeaderName::from_static("permissions-policy"), HeaderValue::from_static("camera=(), microphone=(), geolocation=()"), ); } if is_api { headers.insert( header::CACHE_CONTROL, HeaderValue::from_static("no-cache, no-store"), ); } else if is_api_docs { headers.insert( header::CACHE_CONTROL, HeaderValue::from_static("no-cache, must-revalidate"), ); } response }