From 4f7e730802bbc58db33ad5a48bf70f9b4e386933 Mon Sep 17 00:00:00 2001 From: Virgil Date: Sun, 29 Mar 2026 23:29:43 +0000 Subject: [PATCH] fix(log): sanitise structured logging keys and values --- log.go | 18 +++++++++--------- log_test.go | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/log.go b/log.go index 20546f0..3ca25fc 100644 --- a/log.go +++ b/log.go @@ -258,11 +258,11 @@ func (l *Logger) log(level Level, prefix, msg string, keyvals ...any) { if i > 0 { kvStr += " " } - key := keyvals[i] - var val any - if i+1 < len(keyvals) { - val = keyvals[i+1] - } + key := normaliseLogText(fmt.Sprintf("%v", keyvals[i])) + var val any + if i+1 < len(keyvals) { + val = keyvals[i+1] + } // Redaction logic if shouldRedact(key, redactKeys) { @@ -271,12 +271,12 @@ func (l *Logger) log(level Level, prefix, msg string, keyvals ...any) { // Secure formatting to prevent log injection if s, ok := val.(string); ok { - kvStr += fmt.Sprintf("%v=%q", key, s) - } else { - kvStr += fmt.Sprintf("%v=%v", key, val) - } + kvStr += fmt.Sprintf("%s=%q", key, s) + } else { + kvStr += fmt.Sprintf("%s=%v", key, normaliseLogText(fmt.Sprintf("%v", val))) } } +} _, _ = fmt.Fprintf(output, "%s %s %s%s\n", timestamp, prefix, normaliseLogText(msg), kvStr) } diff --git a/log_test.go b/log_test.go index 780b197..503c854 100644 --- a/log_test.go +++ b/log_test.go @@ -137,6 +137,26 @@ func TestLogger_InjectionPrevention(t *testing.T) { } } +func TestLogger_KeySanitization_Good(t *testing.T) { + var buf bytes.Buffer + l := New(Options{Level: LevelInfo, Output: &buf}) + + l.Info("message", "key\nwith newline", "value\nwith newline") + output := buf.String() + + if !strings.Contains(output, "key\\nwith newline") { + t.Errorf("expected sanitized key, got %q", output) + } + if !strings.Contains(output, "value\\nwith newline") { + t.Errorf("expected sanitized value, got %q", output) + } + + lines := strings.Split(strings.TrimSpace(output), "\n") + if len(lines) != 1 { + t.Errorf("expected 1 line, got %d", len(lines)) + } +} + func TestLogger_MessageSanitization_Good(t *testing.T) { var buf bytes.Buffer l := New(Options{Level: LevelInfo, Output: &buf})