From f6438e550fe729aacbcc5d09d03b5a999f3db7e6 Mon Sep 17 00:00:00 2001 From: Maciek Kisiel Date: Fri, 23 Jan 2026 11:09:14 +0000 Subject: [PATCH] mcp: fix a race condition in logging Reading the buffer outside of a critical section can lead to data corruption. --- mcp/logging.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mcp/logging.go b/mcp/logging.go index 208427e2..96a96b82 100644 --- a/mcp/logging.go +++ b/mcp/logging.go @@ -10,6 +10,7 @@ import ( "context" "encoding/json" "log/slog" + "slices" "sync" "time" ) @@ -165,8 +166,7 @@ func (h *LoggingHandler) Handle(ctx context.Context, r slog.Record) error { func (h *LoggingHandler) handle(ctx context.Context, r slog.Record) error { // Observe the rate limit. - // TODO(jba): use golang.org/x/time/rate. (We can't here because it would require adding - // golang.org/x/time to the go.mod file.) + // TODO(jba): use golang.org/x/time/rate. h.mu.Lock() skip := time.Since(h.lastMessageSent) < h.opts.MinInterval h.mu.Unlock() @@ -175,6 +175,7 @@ func (h *LoggingHandler) handle(ctx context.Context, r slog.Record) error { } var err error + var data json.RawMessage // Make the buffer reset atomic with the record write. // We are careful here in the unlikely event that the handler panics. // We don't want to hold the lock for the entire function, because Notify is @@ -185,6 +186,8 @@ func (h *LoggingHandler) handle(ctx context.Context, r slog.Record) error { defer h.mu.Unlock() h.buf.Reset() err = h.handler.Handle(ctx, r) + // Clone the buffer as Bytes() references the internal buffer. + data = json.RawMessage(slices.Clone(h.buf.Bytes())) }() if err != nil { return err @@ -197,7 +200,7 @@ func (h *LoggingHandler) handle(ctx context.Context, r slog.Record) error { params := &LoggingMessageParams{ Logger: h.opts.LoggerName, Level: slogLevelToMCP(r.Level), - Data: json.RawMessage(h.buf.Bytes()), + Data: data, } // We pass the argument context to Notify, even though slog.Handler.Handle's // documentation says not to.