From 8b180fe829b723ac514b5f141cb2b395b3f5743e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 28 May 2026 15:50:36 -0400 Subject: [PATCH] fix: reject malformed tlon sse event ids --- extensions/tlon/src/urbit/sse-client.test.ts | 16 ++++++++++++++++ extensions/tlon/src/urbit/sse-client.ts | 11 ++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/extensions/tlon/src/urbit/sse-client.test.ts b/extensions/tlon/src/urbit/sse-client.test.ts index 10adbabd537..673d8cd0d02 100644 --- a/extensions/tlon/src/urbit/sse-client.test.ts +++ b/extensions/tlon/src/urbit/sse-client.test.ts @@ -182,6 +182,22 @@ describe("UrbitSSEClient", () => { ); }); + it("ignores malformed event ids when deciding whether to ack", async () => { + const mockUrbitFetch = vi.mocked(urbitFetch); + mockUrbitFetch.mockResolvedValue({ + response: { ok: true, status: 200 } as unknown as Response, + finalUrl: "https://example.com", + release: vi.fn().mockResolvedValue(undefined), + }); + const client = new UrbitSSEClient("https://example.com", "urbauth-~zod=123"); + + client.processEvent('id: 25abc\ndata: {"json":{"ok":true}}'); + await Promise.resolve(); + + expect(mockUrbitFetch).not.toHaveBeenCalled(); + expect((client as unknown as { lastHeardEventId: number }).lastHeardEventId).toBe(-1); + }); + it("tracks lastHeardEventId and ackThreshold", () => { const client = new UrbitSSEClient("https://example.com", "urbauth-~zod=123"); diff --git a/extensions/tlon/src/urbit/sse-client.ts b/extensions/tlon/src/urbit/sse-client.ts index 2d8494ebbe5..d53c409096b 100644 --- a/extensions/tlon/src/urbit/sse-client.ts +++ b/extensions/tlon/src/urbit/sse-client.ts @@ -31,6 +31,15 @@ function parseUrbitSsePayload(data: string): { id?: number; json?: unknown; resp } } +function parseUrbitSseEventId(value: string): number | null { + const trimmed = value.trim(); + if (!/^\d+$/.test(trimmed)) { + return null; + } + const parsed = Number(trimmed); + return Number.isSafeInteger(parsed) ? parsed : null; +} + export class UrbitSSEClient { url: string; cookie: string; @@ -257,7 +266,7 @@ export class UrbitSSEClient { for (const line of lines) { if (line.startsWith("id: ")) { - eventId = Number.parseInt(line.slice(4), 10); + eventId = parseUrbitSseEventId(line.slice(4)); } if (line.startsWith("data: ")) { data = line.slice(6);