From d368eef3ce315c0a7059d6a7a7ddcf5059525c16 Mon Sep 17 00:00:00 2001 From: Deluan Date: Sat, 26 Sep 2026 02:49:05 -0400 Subject: [PATCH] fix(log): redact named string types instead of panicking The redaction hook matched fields by reflect.Kind but read them with a v.(string) type assertion, so any named string type (such as an enum) panicked the log call. API v1 problem logging hit this on every error. --- log/redactrus.go | 3 ++- log/redactrus_test.go | 11 +++++++++++ server/apiv1/problem_test.go | 7 +++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/log/redactrus.go b/log/redactrus.go index 6e17243e7..fc642bfc0 100755 --- a/log/redactrus.go +++ b/log/redactrus.go @@ -47,7 +47,8 @@ func (h *Hook) Fire(e *logrus.Entry) error { } switch reflect.TypeOf(v).Kind() { case reflect.String: - e.Data[k] = re.ReplaceAllString(v.(string), "$1[REDACTED]$2") + // Via reflect: named string types (e.g. enums) have Kind String but fail v.(string). + e.Data[k] = re.ReplaceAllString(reflect.ValueOf(v).String(), "$1[REDACTED]$2") continue case reflect.Map: s := fmt.Sprintf("%+v", v) diff --git a/log/redactrus_test.go b/log/redactrus_test.go index 36a19e2f5..a2bd13dda 100755 --- a/log/redactrus_test.go +++ b/log/redactrus_test.go @@ -157,3 +157,14 @@ func TestEntryMessage(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "Secret Password: [REDACTED]", logEntry.Message) } + +type namedString string + +func TestFireRedactsNamedStringTypes(t *testing.T) { + hook := &Hook{RedactionList: []string{"(secret=)[^&]+"}} + e := &logrus.Entry{Data: logrus.Fields{"code": namedString("not_found"), "url": namedString("/x?secret=abc")}} + + assert.NotPanics(t, func() { _ = hook.Fire(e) }) + assert.Equal(t, "not_found", e.Data["code"]) + assert.Equal(t, "/x?secret=[REDACTED]", e.Data["url"]) +} diff --git a/server/apiv1/problem_test.go b/server/apiv1/problem_test.go index 9cc33838c..074f270b2 100644 --- a/server/apiv1/problem_test.go +++ b/server/apiv1/problem_test.go @@ -77,6 +77,13 @@ var _ = Describe("problem", func() { Expect(*p.Errors).To(ConsistOf(ValidationError{Field: "currentPassword", Message: "is incorrect"})) }) + It("writes and logs a problem with debug logging on", func() { + logs := captureLogs() + writeProblem(w, r, model.ErrNotFound) + Expect(w.Code).To(Equal(http.StatusNotFound)) + Expect(logs.String()).To(ContainSubstring("code=not_found")) + }) + It("adds a Bearer challenge to every 401 unless one is already set", func() { writeProblem(w, r, model.ErrInvalidAuth) Expect(w.Header().Get("WWW-Authenticate")).To(Equal("Bearer"))