whiskers.git / web / worker / src / headers.rs

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}