From 5de7a2673517cba47ce9acd888a2c3546bd5757f Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 8 Jan 2026 10:01:36 -0800 Subject: [PATCH 1/3] Add failing tests for security headers middleware Tests for X-Content-Type-Options, X-Frame-Options, Referrer-Policy, X-XSS-Protection headers on responses. --- internal/middleware/middleware.go | 10 +++ internal/middleware/middleware_test.go | 89 ++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 internal/middleware/middleware_test.go diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go index a4bc087..4c9de94 100644 --- a/internal/middleware/middleware.go +++ b/internal/middleware/middleware.go @@ -133,3 +133,13 @@ func (s *Middleware) MetricsAuth() func(http.Handler) http.Handler { }, ) } + +// SecurityHeaders returns a middleware that adds security headers to responses. +func (s *Middleware) SecurityHeaders() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // TODO: implement security headers + next.ServeHTTP(w, r) + }) + } +} diff --git a/internal/middleware/middleware_test.go b/internal/middleware/middleware_test.go new file mode 100644 index 0000000..72337ac --- /dev/null +++ b/internal/middleware/middleware_test.go @@ -0,0 +1,89 @@ +package middleware + +import ( + "log/slog" + "net/http" + "net/http/httptest" + "testing" + + "sneak.berlin/go/pixa/internal/config" +) + +func TestSecurityHeaders(t *testing.T) { + // Create middleware instance + cfg := &config.Config{} + mw := &Middleware{ + log: slog.Default(), + config: cfg, + } + + // Create a test handler + testHandler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + // Wrap with security headers middleware + handler := mw.SecurityHeaders()(testHandler) + + // Make a test request + req := httptest.NewRequest(http.MethodGet, "/test", nil) + rec := httptest.NewRecorder() + + handler.ServeHTTP(rec, req) + + // Check security headers + tests := []struct { + header string + want string + }{ + {"X-Content-Type-Options", "nosniff"}, + {"X-Frame-Options", "DENY"}, + {"Referrer-Policy", "strict-origin-when-cross-origin"}, + {"X-XSS-Protection", "0"}, + } + + for _, tt := range tests { + t.Run(tt.header, func(t *testing.T) { + got := rec.Header().Get(tt.header) + if got != tt.want { + t.Errorf("%s = %q, want %q", tt.header, got, tt.want) + } + }) + } +} + +func TestSecurityHeaders_PreservesExistingHeaders(t *testing.T) { + cfg := &config.Config{} + mw := &Middleware{ + log: slog.Default(), + config: cfg, + } + + // Handler that sets its own headers + testHandler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("X-Custom-Header", "custom-value") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + }) + + handler := mw.SecurityHeaders()(testHandler) + + req := httptest.NewRequest(http.MethodGet, "/test", nil) + rec := httptest.NewRecorder() + + handler.ServeHTTP(rec, req) + + // Security headers should be present + if rec.Header().Get("X-Content-Type-Options") != "nosniff" { + t.Error("X-Content-Type-Options not set") + } + + // Custom headers should still be there + if rec.Header().Get("X-Custom-Header") != "custom-value" { + t.Error("Custom header was overwritten") + } + + if rec.Header().Get("Content-Type") != "application/json" { + t.Error("Content-Type was overwritten") + } +} From 2e349a8b8319d01f7cba5260fab0bff664d710c8 Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 8 Jan 2026 10:02:17 -0800 Subject: [PATCH 2/3] Implement security headers middleware Adds X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and X-XSS-Protection headers to all responses. --- internal/middleware/middleware.go | 14 +++++++++++++- internal/server/routes.go | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go index 4c9de94..a0b5515 100644 --- a/internal/middleware/middleware.go +++ b/internal/middleware/middleware.go @@ -135,10 +135,22 @@ func (s *Middleware) MetricsAuth() func(http.Handler) http.Handler { } // SecurityHeaders returns a middleware that adds security headers to responses. +// These headers help protect against common web vulnerabilities. func (s *Middleware) SecurityHeaders() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // TODO: implement security headers + // Prevent MIME type sniffing + w.Header().Set("X-Content-Type-Options", "nosniff") + + // Prevent clickjacking + w.Header().Set("X-Frame-Options", "DENY") + + // Control referrer information + w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") + + // Disable XSS filtering (modern browsers don't need it, can cause issues) + w.Header().Set("X-XSS-Protection", "0") + next.ServeHTTP(w, r) }) } diff --git a/internal/server/routes.go b/internal/server/routes.go index 5d3c0d3..cbc376a 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -17,6 +17,7 @@ func (s *Server) SetupRoutes() { s.router.Use(middleware.Recoverer) s.router.Use(middleware.RequestID) + s.router.Use(s.mw.SecurityHeaders()) s.router.Use(s.mw.Logging()) // Add metrics middleware only if credentials are configured From 6f423af65d96ee0df1ebbc0107f18268f6e8c8f2 Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 8 Jan 2026 10:02:29 -0800 Subject: [PATCH 3/3] Update TODO.md: mark graceful shutdown and sanitization as complete --- TODO.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TODO.md b/TODO.md index fd125d4..a1f5d59 100644 --- a/TODO.md +++ b/TODO.md @@ -164,8 +164,8 @@ A single linear checklist of tasks to implement the complete pixa caching image ## Security - [x] Implement path traversal prevention -- [ ] Implement request sanitization -- [ ] Implement response header sanitization +- [x] Implement request sanitization +- [x] Implement response header sanitization - [ ] Implement referer blacklist - [ ] Implement blocked networks configuration - [ ] Add rate limiting per-IP @@ -195,7 +195,7 @@ A single linear checklist of tasks to implement the complete pixa caching image - [ ] Validate configuration on startup ## Operational -- [ ] Implement graceful shutdown +- [x] Implement graceful shutdown - [ ] Implement Sentry error reporting (optional) - [ ] Add comprehensive request logging - [ ] Add performance metrics (Prometheus)