From 03883192d9b21dc544e13fef130925da55b7cc84 Mon Sep 17 00:00:00 2001 From: team-humaki Date: Tue, 15 Sep 2026 18:38:09 -0700 Subject: [PATCH] render: do not leak JSON/XML encode errors --- responder.go | 6 +++--- responder_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 responder_test.go diff --git a/responder.go b/responder.go index f38807d..ab4fb65 100644 --- a/responder.go +++ b/responder.go @@ -95,7 +95,7 @@ func JSON(w http.ResponseWriter, r *http.Request, v interface{}) { enc := json.NewEncoder(buf) enc.SetEscapeHTML(true) if err := enc.Encode(v); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } @@ -112,7 +112,7 @@ func JSON(w http.ResponseWriter, r *http.Request, v interface{}) { func XML(w http.ResponseWriter, r *http.Request, v interface{}) { b, err := xml.Marshal(v) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } @@ -184,7 +184,7 @@ func channelEventStream(w http.ResponseWriter, r *http.Request, v interface{}) { bytes, err := json.Marshal(v) if err != nil { - w.Write([]byte(fmt.Sprintf("event: error\ndata: {\"error\":\"%v\"}\n\n", err))) //nolint:errcheck + w.Write([]byte("event: error\ndata: {\"error\":\"Internal Server Error\"}\n\n")) //nolint:errcheck if f, ok := w.(http.Flusher); ok { f.Flush() } diff --git a/responder_test.go b/responder_test.go new file mode 100644 index 0000000..9308d90 --- /dev/null +++ b/responder_test.go @@ -0,0 +1,43 @@ +package render + +import ( + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +type leakyJSON struct{} + +func (leakyJSON) MarshalJSON() ([]byte, error) { + return nil, errors.New("secret token xyz") +} + +func TestJSONDoesNotLeakEncodeError(t *testing.T) { + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + JSON(rec, req, leakyJSON{}) + if rec.Code != http.StatusInternalServerError { + t.Fatalf("status = %d, want 500", rec.Code) + } + body := rec.Body.String() + if strings.Contains(body, "secret token") { + t.Fatalf("leaked encode error: %q", body) + } + if !strings.Contains(body, http.StatusText(http.StatusInternalServerError)) { + t.Fatalf("body = %q, want Internal Server Error", body) + } +} + +func TestJSONOK(t *testing.T) { + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + JSON(rec, req, M{"ok": true}) + if rec.Code != http.StatusOK { + t.Fatalf("status = %d", rec.Code) + } + if got := rec.Body.String(); !strings.Contains(got, `"ok":true`) { + t.Fatalf("body = %q", got) + } +}