Die Leiste kommt als Skript von der HoneyOS-Domain und holt sich von dort ihre App-Liste. Beides blockte die CSP bisher (script-src / connect-src fielen auf 'self' zurueck) – ohne Ausnahme erschien sie gar nicht. - HONEYBAR_ORIGIN nimmt genau diesen einen Ursprung in script-src und connect-src auf. Leer heisst: keine Leiste, und die Regel bleibt so eng wie bisher. Frame-Freigabe erbt sie ausdruecklich nicht. - SecurityHeaders nimmt die Ausnahmen jetzt als CSP-Struct statt als variadische frameSrc-Liste – mit drei Richtungen wurde das sonst unklar. - Das <script>-Tag steht in der index.html und nicht serverseitig eingespritzt: der Service Worker legt die index.html in seinen Vorrat (navigateFallback), was zur Bauzeit fehlt, kaeme im Browser nie an. - defer, damit ein langsames oder abgeschaltetes HoneyOS den Start von Aether nicht ausbremst. Tests fuer die neue Regel: eng ohne Ausnahme, beide Richtungen mit, Pfad wird auf den Ursprung gekuerzt, Adresse ohne Schema faellt raus. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
84 lines
2.7 KiB
Go
84 lines
2.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|