package surf import ( "bytes" "errors" "log/slog" "net/http" "net/http/httptest" "strings" "testing" "git.golem15.com/golem15/summercms/modules/pact" "git.golem15.com/golem15/summercms/modules/sunscreen" ) // TestRecoverHidesPanicDetails pins the SafeExceptionResponse behaviour: a // panicking handler's message, here carrying credentials, never reaches the // response in either group type, and a log record of the panic written // through a sunscreen handler carries neither credential. func TestRecoverHidesPanicDetails(t *testing.T) { const skKey = "sk-abcdefghijklmnopqrstuvwxyz0123" const token = "eyJhbGciOiJIUzI1NiJ9.claims.signature" panicErr := errors.New("vendor rejected key " + skKey + " with Authorization: Bearer " + token) prev := slog.Default() t.Cleanup(func() { slog.SetDefault(prev) }) var logs bytes.Buffer sunscreen.InstallDefault(&logs) r := New(nil) r.GroupRaw("/oauth", nil, func(g pact.Router) { g.Post("/token", func(http.ResponseWriter, *http.Request) { panic(panicErr) }) }) r.Post("/api/v1/recognize", func(http.ResponseWriter, *http.Request) { panic(panicErr) }) h, err := r.compile() if err != nil { t.Fatal(err) } cases := []struct { path, body, contentType string }{ {"/api/v1/recognize", `{"error":true,"message":"Internal server error"}`, "application/json"}, {"/oauth/token", "", ""}, } for _, c := range cases { rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodPost, c.path, nil)) if rec.Code != http.StatusInternalServerError { t.Fatalf("%s status = %d", c.path, rec.Code) } body := strings.TrimSpace(rec.Body.String()) if body != c.body { t.Fatalf("%s body = %q, want the opaque %q", c.path, body, c.body) } if got := rec.Header().Get("Content-Type"); got != c.contentType { t.Fatalf("%s Content-Type = %q", c.path, got) } for _, secret := range []string{skKey, token, "vendor rejected"} { if strings.Contains(rec.Body.String(), secret) { t.Fatalf("%s response echoes %q", c.path, secret) } } } slog.Error("handler panicked", "err", panicErr, "path", "/api/v1/recognize") out := logs.String() if strings.Contains(out, skKey) || strings.Contains(out, token) { t.Fatalf("log record leaks a credential:\n%s", out) } if !strings.Contains(out, "sk-[REDACTED]") || !strings.Contains(out, "Bearer [REDACTED]") { t.Fatalf("log record not scrubbed as expected:\n%s", out) } }