Skip to content

MemoryEventStore.After panics (slice out of range) on an index past the last stored event #1131

Description

@PratikDhanave

Describe the bug

MemoryEventStore.After(ctx, sessionID, streamID, index) panics with slice bounds out of range when index is greater than the highest stored event index for the stream.

In mcp/event.go, After computes start := index + 1 and guards only the lower bound:

start := index + 1
if dl.first > start { // too old → ErrEventsPurged
    return nil, fmt.Errorf(... ErrEventsPurged)
}
return slices.Clone(dl.data[start-dl.first:]), nil

There is no upper-bound guard, so when start-dl.first > len(dl.data) the final slice expression panics. The boundary start-dl.first == len(dl.data) (resume from the last event seen) is a valid empty replay, so only strictly-greater indices fault.

This is reachable from untrusted input on the streamable HTTP server path: parseEventID (mcp/streamable.go) accepts any non-negative index from the client-supplied Last-Event-ID header, and serveGET passes it straight to eventStore.After. A client sending Last-Event-ID: <stream>_<N> with N beyond any event the server ever sent triggers the panic. Direct callers of the exported EventStore.After API crash outright.

To Reproduce

ctx := context.Background()
s := mcp.NewMemoryEventStore(nil)
s.Open(ctx, "sess", "st")
s.Append(ctx, "sess", "st", []byte("a")) // only index 0 exists

for _, err := range s.After(ctx, "sess", "st", 5) { // index past the end
    _ = err
}
// panic: runtime error: slice bounds out of range [6:1]

Expected behavior

An index at or beyond the latest stored event should yield an empty replay (there is nothing after it), consistent with the already-valid index == last case — not a panic. (ErrEventsPurged would be misleading here, since the events were never sent rather than purged.)

Version

main (reproduced against the current mcp package).

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Nice to haves, rare edge cases

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions