The headers every response carries, in one place so a response cannot leave without them.
3use http::{HeaderMap, HeaderName, HeaderValue, header};
What the page may load: itself, and nothing from anywhere else. Datastar compiles each
data-* expression with new Function, which needs 'unsafe-eval'; and the cat's SVG
carries style attributes (each moving part's pivot), which need 'unsafe-inline' for
styles. Scripts are never inline and never from another origin. data: images are the
dither fields ui.js draws into the page's background.
10pub const CSP: &str = "default-src 'none'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; media-src 'self'; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'";
Cloudflare adds its Web Analytics script to HTML as it passes through, unless the response
says no-transform (Cloudflare's Web Analytics documentation). The page promises no
tracking and loads nothing from elsewhere, so HTML asks not to be rewritten.
15pub const HTML_CACHE: &str = "public, max-age=300, no-transform";
17pub fn secure(headers: &mut HeaderMap) { 18 let fixed = [ 19 (header::CONTENT_SECURITY_POLICY, CSP), 20 (header::X_CONTENT_TYPE_OPTIONS, "nosniff"), 21 (header::REFERRER_POLICY, "no-referrer"), 22 (HeaderName::from_static("permissions-policy"), "camera=(), microphone=(), geolocation=(), interest-cohort=()"), 23 (HeaderName::from_static("cross-origin-opener-policy"), "same-origin"), 24 ]; 25 for (name, value) in fixed { 26 headers.insert(name, HeaderValue::from_static(value)); 27 } 28} 29 30#[cfg(test)] 31mod tests { 32 use super::*; 33 34 #[test] 35 fn nothing_may_be_loaded_from_elsewhere() { 36 for directive in CSP.split(';') { 37 let directive = directive.trim(); 38 for source in directive.split_whitespace().skip(1) { 39 assert!(!source.contains("://") && source != "*" && source != "https:", "{directive}"); 40 } 41 } 42 assert!(CSP.contains("default-src 'none'")); 43 assert!(!CSP.contains("script-src 'self' 'unsafe-inline'")); 44 } 45 46 #[test] 47 fn html_asks_cloudflare_not_to_add_its_analytics() { 48 assert!(HTML_CACHE.contains("no-transform")); 49 } 50 51 #[test] 52 fn secure_sets_every_header() { 53 let mut headers = HeaderMap::new(); 54 secure(&mut headers); 55 assert_eq!(headers.len(), 5); 56 assert_eq!(headers[header::X_CONTENT_TYPE_OPTIONS], "nosniff"); 57 } 58}