From 97f607d0306a49416a24eff02161ff9d9681f30e Mon Sep 17 00:00:00 2001 From: Christian Rey Villablanca Date: Thu, 6 Aug 2026 21:35:23 +0800 Subject: [PATCH 1/3] fix(chat): ignore malformed stream payloads --- .../malformed-stream-event-regression.test.mjs | 17 +++++++++++++++++ webview/shared/src/chat/lib/messageHandler.ts | 2 ++ 2 files changed, 19 insertions(+) create mode 100644 tests/regression/malformed-stream-event-regression.test.mjs diff --git a/tests/regression/malformed-stream-event-regression.test.mjs b/tests/regression/malformed-stream-event-regression.test.mjs new file mode 100644 index 0000000..f9e5d09 --- /dev/null +++ b/tests/regression/malformed-stream-event-regression.test.mjs @@ -0,0 +1,17 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { joinFromRoot, readSource } from "../helpers/source-utils.mjs"; + +const source = readSource( + [joinFromRoot("webview", "shared", "src", "chat", "lib", "messageHandler.ts")], + "messageHandler.ts", +); + +test("malformed stream events are ignored before state processing", () => { + assert.match( + source, + /function handleStreamEvent\([\s\S]*?\): void \{\s*if \(!payload \|\| typeof payload !== "object"\) return;/s, + "the stream handler must ignore null or missing event payloads", + ); +}); diff --git a/webview/shared/src/chat/lib/messageHandler.ts b/webview/shared/src/chat/lib/messageHandler.ts index 1c14aa2..4e10d5a 100644 --- a/webview/shared/src/chat/lib/messageHandler.ts +++ b/webview/shared/src/chat/lib/messageHandler.ts @@ -9251,6 +9251,8 @@ function handleStreamEvent( knownReasoningPartIDs?: Set, pendingRenderableTextPart?: { partID?: string; messageID?: string }, ): void { + if (!payload || typeof payload !== "object") return; + const dispatchProcessingTrue = () => { if (!shouldSuppressProcessingBootstrap) { dispatch({ type: "SET_IS_PROCESSING", payload: true }); From abeec7ef2ed6eefa0440f7363a817ad0b65c99e3 Mon Sep 17 00:00:00 2001 From: Christian Rey Villablanca Date: Thu, 6 Aug 2026 22:06:05 +0800 Subject: [PATCH 2/3] fix(chat): reject invalid stream envelopes --- .../src/chat/lib/messageHandler.test.ts | 19 +++++++++++++++++++ webview/shared/src/chat/lib/messageHandler.ts | 6 +++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/webview/shared/src/chat/lib/messageHandler.test.ts b/webview/shared/src/chat/lib/messageHandler.test.ts index 4bc7fe9..7bfc8f2 100644 --- a/webview/shared/src/chat/lib/messageHandler.test.ts +++ b/webview/shared/src/chat/lib/messageHandler.test.ts @@ -73,6 +73,25 @@ describe('extractEventMessageId', () => { }); }); +describe('malformed stream events', () => { + it('ignores null, undefined, and primitive event payloads', () => { + let state = { ...initialState, currentSessionId: 'ses-malformed' } as AppState; + const dispatch = (action: Parameters[1]) => { + state = appReducer(state, action); + }; + const handler = createMessageHandler(dispatch, () => state); + + for (const event of [null, undefined, 'invalid']) { + handler({ + data: { type: 'streamEvent', sessionId: 'ses-malformed', event }, + } as MessageEvent); + } + + assert.equal(state.isProcessing, false); + assert.equal(state.streaming, null); + }); +}); + describe('normalizeMessage - responseType handling', () => { it('ignores undefined interactive events from an untrusted payload', () => { const result = normalizeMessage({ diff --git a/webview/shared/src/chat/lib/messageHandler.ts b/webview/shared/src/chat/lib/messageHandler.ts index 4e10d5a..219c1dc 100644 --- a/webview/shared/src/chat/lib/messageHandler.ts +++ b/webview/shared/src/chat/lib/messageHandler.ts @@ -14305,7 +14305,11 @@ export function createMessageHandler(dispatch: Dispatch, getState: () } case "streamEvent": { const stateBeforeStreamEvent = getState(); - const payload = asRecord(data.event) ?? data; + const payload = asRecord(data.event); + if (!payload) { + logger.warn("Ignoring stream event with an invalid payload"); + break; + } const streamEventType = asString(payload.type) || "unknown"; const envelopeSessionId = asString(data.sessionId) || From 78821ffdec09b9f93575a3a0de770f82e1189e3a Mon Sep 17 00:00:00 2001 From: Christian Rey Villablanca Date: Thu, 6 Aug 2026 22:24:07 +0800 Subject: [PATCH 3/3] fix(chat): reject array stream payloads --- .../malformed-stream-event-regression.test.mjs | 17 ----------------- .../shared/src/chat/lib/messageHandler.test.ts | 4 ++-- webview/shared/src/chat/lib/messageHandler.ts | 2 +- 3 files changed, 3 insertions(+), 20 deletions(-) delete mode 100644 tests/regression/malformed-stream-event-regression.test.mjs diff --git a/tests/regression/malformed-stream-event-regression.test.mjs b/tests/regression/malformed-stream-event-regression.test.mjs deleted file mode 100644 index f9e5d09..0000000 --- a/tests/regression/malformed-stream-event-regression.test.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import test from "node:test"; -import assert from "node:assert/strict"; - -import { joinFromRoot, readSource } from "../helpers/source-utils.mjs"; - -const source = readSource( - [joinFromRoot("webview", "shared", "src", "chat", "lib", "messageHandler.ts")], - "messageHandler.ts", -); - -test("malformed stream events are ignored before state processing", () => { - assert.match( - source, - /function handleStreamEvent\([\s\S]*?\): void \{\s*if \(!payload \|\| typeof payload !== "object"\) return;/s, - "the stream handler must ignore null or missing event payloads", - ); -}); diff --git a/webview/shared/src/chat/lib/messageHandler.test.ts b/webview/shared/src/chat/lib/messageHandler.test.ts index 7bfc8f2..90e4e99 100644 --- a/webview/shared/src/chat/lib/messageHandler.test.ts +++ b/webview/shared/src/chat/lib/messageHandler.test.ts @@ -74,14 +74,14 @@ describe('extractEventMessageId', () => { }); describe('malformed stream events', () => { - it('ignores null, undefined, and primitive event payloads', () => { + it('ignores null, undefined, primitive, and array event payloads', () => { let state = { ...initialState, currentSessionId: 'ses-malformed' } as AppState; const dispatch = (action: Parameters[1]) => { state = appReducer(state, action); }; const handler = createMessageHandler(dispatch, () => state); - for (const event of [null, undefined, 'invalid']) { + for (const event of [null, undefined, 'invalid', []]) { handler({ data: { type: 'streamEvent', sessionId: 'ses-malformed', event }, } as MessageEvent); diff --git a/webview/shared/src/chat/lib/messageHandler.ts b/webview/shared/src/chat/lib/messageHandler.ts index 219c1dc..999fabb 100644 --- a/webview/shared/src/chat/lib/messageHandler.ts +++ b/webview/shared/src/chat/lib/messageHandler.ts @@ -14306,7 +14306,7 @@ export function createMessageHandler(dispatch: Dispatch, getState: () case "streamEvent": { const stateBeforeStreamEvent = getState(); const payload = asRecord(data.event); - if (!payload) { + if (!payload || Array.isArray(payload)) { logger.warn("Ignoring stream event with an invalid payload"); break; }