aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/web_test.go
diff options
context:
space:
mode:
authorgrm <grm@eyesin.space>2026-09-18 13:49:19 +0300
committergrm <grm@eyesin.space>2026-09-18 13:49:28 +0300
commitd23fe805546e992c8033d64d7177fe1454ad7716 (patch)
tree79657033b96f852f57bac5c0bf42551a8721afa8 /internal/web/web_test.go
parentabc1898daebae33405688618caffa01cece3b850 (diff)
downloadblogspace-d23fe805546e992c8033d64d7177fe1454ad7716.tar.gz
blogspace-d23fe805546e992c8033d64d7177fe1454ad7716.tar.bz2
blogspace-d23fe805546e992c8033d64d7177fe1454ad7716.zip
Security: Send security headers; refuse to frame the dashboard
Pages carried no X-Content-Type-Options, no Referrer-Policy, and the dashboard could be framed by any site (clickjacking a superadmin's delete buttons). Every response now says nosniff and strict-origin-when-cross-origin, and the management paths on the root host add X-Frame-Options: DENY plus a CSP of frame-ancestors, base-uri, form-action and object-src — the directives that do not touch the dashboard's inline scripts, which are a product constraint. Blog pages get no framing rule: they are the owner's content and may be embedded on purpose. HSTS moves into the same helper. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'internal/web/web_test.go')
-rw-r--r--internal/web/web_test.go31
1 files changed, 31 insertions, 0 deletions
diff --git a/internal/web/web_test.go b/internal/web/web_test.go
index b1609cd..dfc9629 100644
--- a/internal/web/web_test.go
+++ b/internal/web/web_test.go
@@ -1049,3 +1049,34 @@ func TestCrossSiteForm(t *testing.T) {
t.Errorf("cross-site login: got %d, want 403", rec.Code)
}
}
+
+// Every response says nosniff and a referrer policy; only the management pages refuse to be framed.
+func TestSecurityHeaders(t *testing.T) {
+ s := NewServer(&config.Config{BaseDomain: "example.com", JWTSecret: []byte("x")}, nil)
+ get := func(host, path string) http.Header {
+ rec := httptest.NewRecorder()
+ req := httptest.NewRequest("GET", path, nil)
+ req.Host = host
+ s.ServeHTTP(rec, req)
+ return rec.Header()
+ }
+ h := get("example.com", "/webadmin")
+ if h.Get("X-Content-Type-Options") != "nosniff" || h.Get("Referrer-Policy") == "" {
+ t.Errorf("login page headers: %v", h)
+ }
+ if h.Get("X-Frame-Options") != "DENY" || !strings.Contains(h.Get("Content-Security-Policy"), "frame-ancestors 'none'") {
+ t.Errorf("login page not frame-protected: %v", h)
+ }
+ if h.Get("Strict-Transport-Security") != "" {
+ t.Error("HSTS sent without HTTPS")
+ }
+ h = get("evil.com", "/") // even the unknown-host 404 carries the common ones
+ if h.Get("X-Content-Type-Options") != "nosniff" {
+ t.Errorf("404 headers: %v", h)
+ }
+ for _, p := range []string{"/static/blog.css", "/webadmin"} { // the blog host may be framed (these need no DB)
+ if h := get("a.example.com", p); h.Get("X-Frame-Options") != "" || h.Get("Content-Security-Policy") != "" {
+ t.Errorf("%s on a blog host is frame-protected", p)
+ }
+ }
+}