From be92d3502181f06361688ec31905ffb0dbab54a6 Mon Sep 17 00:00:00 2001 From: matthew-pilot Date: Sat, 30 May 2026 06:12:04 +0000 Subject: [PATCH] fix(eventstream): reject events with invalid UTF-8 topic (PILOT-277) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ReadEvent now validates topic bytes with utf8.Valid() before casting to string. Without this check, an attacker can craft a topic with invalid UTF-8 that survives transport unchanged but gets silently mangled by json.Marshal (replacing invalid sequences with U+FFFD). This can be exploited to escape audit redaction (PILOT-284) — the wire-observed bytes differ from the JSON-logged value, yet both map to the same Go map key. Closes PILOT-277 (eventstream half) --- eventstream.go | 5 +++++ zz_event_wire_test.go | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/eventstream.go b/eventstream.go index 16c7f2e..ee8ae80 100644 --- a/eventstream.go +++ b/eventstream.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "fmt" "io" + "unicode/utf8" ) // Event is a typed message published to the event stream. @@ -58,5 +59,9 @@ func ReadEvent(r io.Reader) (*Event, error) { return nil, err } + if !utf8.Valid(topic) { + return nil, fmt.Errorf("topic contains invalid UTF-8") + } + return &Event{Topic: string(topic), Payload: payload}, nil } diff --git a/zz_event_wire_test.go b/zz_event_wire_test.go index 934b6ab..dc919d2 100644 --- a/zz_event_wire_test.go +++ b/zz_event_wire_test.go @@ -143,3 +143,19 @@ func TestEventPayloadTooLarge(t *testing.T) { t.Fatal("expected error for oversized payload, got nil") } } + +func TestEventTopicInvalidUTF8(t *testing.T) { + t.Parallel() + var buf bytes.Buffer + // Craft topic with invalid UTF-8: byte 0xFF is never valid + invalidTopic := []byte{0x48, 0xFF, 0x69} // H + invalid + i + binary.Write(&buf, binary.BigEndian, uint16(len(invalidTopic))) + buf.Write(invalidTopic) + // Valid payload + binary.Write(&buf, binary.BigEndian, uint32(0)) + + _, err := eventstream.ReadEvent(&buf) + if err == nil { + t.Fatal("expected error for invalid UTF-8 topic, got nil") + } +}