package middleware import ( "net/http" "net/http/httptest" "strings" "testing" ) func policyFor(t *testing.T, csp CSP) string { t.Helper() h := SecurityHeaders(csp)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest("GET", "/", nil)) return rec.Header().Get("Content-Security-Policy") } // Ohne Ausnahmen darf nichts Fremdes erlaubt sein — das ist der Normalfall // und der Zustand, in dem eine Instanz ohne Zusatzkonfiguration läuft. func TestCSPOhneAusnahmenBleibtEng(t *testing.T) { got := policyFor(t, CSP{}) for _, want := range []string{ "default-src 'self'", "script-src 'self';", "connect-src 'self';", "frame-src 'self'", } { if !strings.Contains(got+";", want) { t.Errorf("Regel %q fehlt in %q", want, got) } } if strings.Contains(got, "*") { t.Errorf("Platzhalter in der Regel: %q", got) } } // Die eingebettete Leiste braucht zwei Richtungen: das Skript selbst und die // Anfrage, mit der es sich seine Daten holt. func TestCSPLeisteBekommtSkriptUndVerbindung(t *testing.T) { const bar = "https://os.example.com" got := policyFor(t, CSP{ScriptSrc: []string{bar}, ConnectSrc: []string{bar}}) if !strings.Contains(got, "script-src 'self' "+bar) { t.Errorf("script-src erlaubt die Leiste nicht: %q", got) } if !strings.Contains(got, "connect-src 'self' "+bar) { t.Errorf("connect-src erlaubt die Anfrage nicht: %q", got) } // Die Ausnahme gilt nur, wofür sie gedacht ist. if strings.Contains(got, "frame-src 'self' "+bar) { t.Errorf("die Leiste darf keine Rahmen-Freigabe erben: %q", got) } } // Ein Pfad in der Konfiguration wäre enger als gemeint; ein Tippfehler ohne // Schema soll gar nicht erst in die Regel geraten. func TestCSPKürztAufDenUrsprungUndVerwirftUnsinn(t *testing.T) { got := policyFor(t, CSP{ScriptSrc: []string{"https://os.example.com/honeybar/honeybar.js"}}) if !strings.Contains(got, "script-src 'self' https://os.example.com;") { t.Errorf("Pfad nicht abgeschnitten: %q", got) } got = policyFor(t, CSP{ScriptSrc: []string{"os.example.com", "", " "}}) if !strings.Contains(got+";", "script-src 'self';") { t.Errorf("Adresse ohne Schema wurde übernommen: %q", got) } } func TestSicherheitsköpfeStehen(t *testing.T) { h := SecurityHeaders(CSP{})(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest("GET", "/", nil)) for k, want := range map[string]string{ "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY", "Referrer-Policy": "strict-origin-when-cross-origin", } { if got := rec.Header().Get(k); got != want { t.Errorf("%s = %q, want %q", k, got, want) } } }