From 9c8d4164abd4e3dd585b97ab2f7efcf11b2fc141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Bellot=20Mic=C3=B3?= Date: Tue, 8 Sep 2026 10:25:55 +0200 Subject: [PATCH] MM-70074: Fix output formatting issue in mmctl (#37794) --- server/cmd/mmctl/printer/human/entry.go | 3 +- server/cmd/mmctl/printer/human/entry_test.go | 95 ++++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 server/cmd/mmctl/printer/human/entry_test.go diff --git a/server/cmd/mmctl/printer/human/entry.go b/server/cmd/mmctl/printer/human/entry.go index 4a1bf2542e8..1806605b6f9 100644 --- a/server/cmd/mmctl/printer/human/entry.go +++ b/server/cmd/mmctl/printer/human/entry.go @@ -9,6 +9,7 @@ import ( "time" "github.com/mattermost/mattermost/server/public/shared/mlog" + "github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer" ) type LogEntry struct { @@ -48,5 +49,5 @@ func (f LogEntry) String() string { sb.WriteString(f.Message) } - return sb.String() + return printer.SanitizeForTerminal(sb.String()) } diff --git a/server/cmd/mmctl/printer/human/entry_test.go b/server/cmd/mmctl/printer/human/entry_test.go new file mode 100644 index 00000000000..549e920ff4e --- /dev/null +++ b/server/cmd/mmctl/printer/human/entry_test.go @@ -0,0 +1,95 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package human + +import ( + "testing" + "time" + + "github.com/mattermost/mattermost/server/public/shared/mlog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLogEntryString(t *testing.T) { + baseTime := time.Date(2024, time.January, 2, 3, 4, 5, 0, time.UTC) + + t.Run("plain entry renders as expected", func(t *testing.T) { + entry := LogEntry{ + Time: baseTime, + Level: "info", + Message: "hello world", + Caller: "main.go:10", + Fields: []mlog.Field{{Key: "key", Interface: "value"}}, + } + + got := entry.String() + assert.Contains(t, got, "info") + assert.Contains(t, got, "main.go:10") + assert.Contains(t, got, "key=value") + assert.Contains(t, got, "hello world") + }) + + t.Run("strips ANSI CSI sequences from message", func(t *testing.T) { + entry := LogEntry{ + Time: baseTime, + Level: "info", + Message: "\x1b[31mRed Text\x1b[0m", + } + + got := entry.String() + assert.NotContains(t, got, "\x1b") + assert.Contains(t, got, "Red Text") + }) + + t.Run("strips OSC sequences (clipboard hijacking) from message", func(t *testing.T) { + entry := LogEntry{ + Time: baseTime, + Level: "error", + Message: "invalid token: Normal text\x1b]52;c;SGVsbG8gV29ybGQ=\x07more text", + } + + got := entry.String() + assert.NotContains(t, got, "\x1b") + assert.NotContains(t, got, "\x07") + assert.Contains(t, got, "Normal textmore text") + }) + + t.Run("strips escape sequences injected via fields", func(t *testing.T) { + entry := LogEntry{ + Time: baseTime, + Level: "info", + Fields: []mlog.Field{{Key: "token", Interface: "\x1b]0;Fake Title\x07abc123"}}, + } + + got := entry.String() + assert.NotContains(t, got, "\x1b") + require.Contains(t, got, "token=") + assert.Contains(t, got, "abc123") + }) + + t.Run("strips other control characters", func(t *testing.T) { + entry := LogEntry{ + Time: baseTime, + Level: "info", + Message: "some\x00bytes\x7fhere", + } + + got := entry.String() + assert.NotContains(t, got, "\x00") + assert.NotContains(t, got, "\x7f") + assert.Contains(t, got, "somebyteshere") + }) + + t.Run("preserves multi-line messages", func(t *testing.T) { + entry := LogEntry{ + Time: baseTime, + Level: "info", + Message: "line 1\nline 2", + } + + got := entry.String() + assert.Contains(t, got, "line 1\nline 2") + }) +}