package main import ( "io" "net/http" "regexp" "strings" "testing" ) var cspNonceRE = regexp.MustCompile(`'nonce-([A-Za-z0-9+/=]+)'`) func TestSecurityHeaders(t *testing.T) { ts, _ := newTestServer(t) // Static headers apply to every response, including non-page routes. resp, err := http.Get(ts.URL + "/api/version") if err != nil { t.Fatal(err) } resp.Body.Close() for k, want := range map[string]string{ "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY", "Referrer-Policy": "no-referrer", } { if got := resp.Header.Get(k); got != want { t.Errorf("%s = %q, want %q", k, got, want) } } // The chat page carries a nonce CSP. resp, err = http.Get(ts.URL + "/") if err != nil { t.Fatal(err) } body, _ := io.ReadAll(resp.Body) resp.Body.Close() csp := resp.Header.Get("Content-Security-Policy") for _, want := range []string{ "default-src 'self'", "script-src 'self' 'nonce-", "object-src 'none'", "base-uri 'none'", "frame-ancestors 'none'", } { if !strings.Contains(csp, want) { t.Errorf("CSP missing %q; got %q", want, csp) } } // The CSP nonce must match the one substituted into the page's inline scripts, // and no literal placeholder may remain. m := cspNonceRE.FindStringSubmatch(csp) if m == nil { t.Fatalf("no nonce in CSP: %q", csp) } nonce := m[1] if !strings.Contains(string(body), `