diff --git a/src/internal/server/oidc/account.go b/src/internal/server/oidc/account.go index 59e35b6..2fad330 100644 --- a/src/internal/server/oidc/account.go +++ b/src/internal/server/oidc/account.go @@ -49,7 +49,11 @@ func (h *AccountLogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) w.Header().Set("Cache-Control", "no-store") // Keep the same-origin POST Origin; no-referrer makes Chromium send Origin: null. w.Header().Set("Referrer-Policy", "same-origin") - w.Header().Set("Content-Security-Policy", "default-src 'none'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'") + // Chromium applies form-action to the POST redirect chain as well. + upstream, _ := url.Parse(h.UpstreamLogoutURL) + portal, _ := url.Parse(h.PortalURL) + destinations := upstream.Scheme + "://" + upstream.Host + " " + portal.Scheme + "://" + portal.Host + w.Header().Set("Content-Security-Policy", "default-src 'none'; form-action 'self' "+destinations+"; frame-ancestors 'none'; base-uri 'none'") w.Header().Set("X-Content-Type-Options", "nosniff") switch r.Method { case http.MethodGet: diff --git a/src/internal/server/oidc/account_test.go b/src/internal/server/oidc/account_test.go index fb5dfa4..44a09df 100644 --- a/src/internal/server/oidc/account_test.go +++ b/src/internal/server/oidc/account_test.go @@ -34,6 +34,9 @@ func TestSharedLogoutRequiresConfirmationAndFixedReturn(t *testing.T) { if w.Header().Get("Referrer-Policy") != "same-origin" { t.Fatal("browser POST would lose its Origin") } + if !strings.Contains(w.Header().Get("Content-Security-Policy"), "form-action 'self' https://auth.example https://users.example;") { + t.Fatal("browser must follow registered sign-out destinations") + } c := w.Result().Cookies()[0] if !c.Secure || !c.HttpOnly || c.Domain != "" { t.Fatal("unsafe CSRF cookie")