From 6705ec565181518293db6e079594007cb432a087 Mon Sep 17 00:00:00 2001 From: Gabriele Della Malva Date: Fri, 9 Oct 2026 16:59:41 +0200 Subject: [PATCH 1/4] fix(transport): make debug diagnostics opt-in --- src/message-transport.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/message-transport.ts b/src/message-transport.ts index afc85e935..c813ad8e2 100644 --- a/src/message-transport.ts +++ b/src/message-transport.ts @@ -41,6 +41,12 @@ import { TOOL_INPUT_PARTIAL_METHOD } from "./spec.types.js"; * @see {@link app!App.connect `App.connect`} for View usage * @see {@link app-bridge!AppBridge.connect `AppBridge.connect`} for Host usage */ +/** Optional, explicitly enabled diagnostics for postMessage transports. */ +export interface PostMessageTransportOptions { + /** Debug logging is disabled unless a logger is supplied. */ + logger?: Pick; +} + export class PostMessageTransport implements Transport { private messageListener: ( this: Window, @@ -53,6 +59,7 @@ export class PostMessageTransport implements Transport { * @param eventTarget - Target window to send messages to (default: `window.parent`) * @param eventSource - Source window for message validation. For views, pass * `window.parent`. For hosts, pass `iframe.contentWindow`. + * @param options - Optional diagnostics logger; no debug output by default. * * @example View connecting to parent * ```ts source="./message-transport.examples.ts#PostMessageTransport_constructor_view" @@ -71,20 +78,21 @@ export class PostMessageTransport implements Transport { constructor( private eventTarget: Window = window.parent, private eventSource: MessageEventSource, + private options: PostMessageTransportOptions = {}, ) { this.messageListener = (event) => { if (eventSource && event.source !== this.eventSource) { - console.debug("Ignoring message from unknown source", event); + this.options.logger?.debug("Ignoring message from unknown source", event); return; } const parsed = JSONRPCMessageSchema.safeParse(event.data); if (parsed.success) { - console.debug("Parsed message", parsed.data); + this.options.logger?.debug("Parsed message", parsed.data); this.onmessage?.(parsed.data); } else if (event.data?.jsonrpc !== "2.0") { // Not a JSON-RPC message at all (e.g. internal frames injected by // the host environment). Ignore silently so the transport stays alive. - console.debug( + this.options.logger?.debug( "Ignoring non-JSON-RPC message", parsed.error.message, event, @@ -125,7 +133,7 @@ export class PostMessageTransport implements Transport { // Skip debug log for high-frequency streaming notifications — these // can fire dozens of times per second and flood the console. if ((message as { method?: string }).method !== TOOL_INPUT_PARTIAL_METHOD) { - console.debug("Sending message", message); + this.options.logger?.debug("Sending message", message); } this.eventTarget.postMessage(message, "*"); } From 1840268204a63f253625c075717a7871140a0c9b Mon Sep 17 00:00:00 2001 From: Gabriele Della Malva Date: Fri, 9 Oct 2026 16:59:43 +0200 Subject: [PATCH 2/4] test(transport): cover silent defaults and explicit diagnostics --- src/message-transport.test.ts | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/message-transport.test.ts b/src/message-transport.test.ts index e2518cce4..93000c95b 100644 --- a/src/message-transport.test.ts +++ b/src/message-transport.test.ts @@ -93,6 +93,43 @@ describe("PostMessageTransport", () => { return transport; } + describe("opt-in debug logging", () => { + it("debug logging is off by default", async () => { + const debug = mock(() => {}); + const originalDebug = console.debug; + console.debug = debug; + try { + const transport = await createStartedTransport(); + await transport.send(validRequest); + fakeWindow.dispatch("message", { source: trustedSource, data: validRequest }); + fakeWindow.dispatch("message", { source: untrustedSource, data: validRequest }); + fakeWindow.dispatch("message", { source: trustedSource, data: "not JSON-RPC" }); + + expect(debug).not.toHaveBeenCalled(); + } finally { + console.debug = originalDebug; + } + }); + + it("logs only when an explicit logger is supplied", async () => { + const debug = mock(() => {}); + const transport = new PostMessageTransport( + eventTarget as unknown as Window, + trustedSource as MessageEventSource, + { logger: { debug } }, + ); + await transport.start(); + await transport.send(validRequest); + fakeWindow.dispatch("message", { source: trustedSource, data: validRequest }); + fakeWindow.dispatch("message", { source: untrustedSource, data: validRequest }); + + expect(debug).toHaveBeenCalledTimes(3); + expect(debug.mock.calls[0][0]).toBe("Sending message"); + expect(debug.mock.calls[1][0]).toBe("Parsed message"); + expect(debug.mock.calls[2][0]).toBe("Ignoring message from unknown source"); + }); + }); + // ========================================================================== // Source validation — the security boundary at this layer. // The transport validates `event.source` (window identity), not `event.origin`. From 8082ea6287c10d76e029be97ddc3c839da475631 Mon Sep 17 00:00:00 2001 From: Gabriele Della Malva Date: Fri, 9 Oct 2026 17:06:03 +0200 Subject: [PATCH 3/4] test(transport): fix variadic logger mock typing --- src/message-transport.test.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/message-transport.test.ts b/src/message-transport.test.ts index 93000c95b..7c791e977 100644 --- a/src/message-transport.test.ts +++ b/src/message-transport.test.ts @@ -112,7 +112,7 @@ describe("PostMessageTransport", () => { }); it("logs only when an explicit logger is supplied", async () => { - const debug = mock(() => {}); + const debug = mock((..._args: unknown[]) => {}); const transport = new PostMessageTransport( eventTarget as unknown as Window, trustedSource as MessageEventSource, @@ -124,9 +124,11 @@ describe("PostMessageTransport", () => { fakeWindow.dispatch("message", { source: untrustedSource, data: validRequest }); expect(debug).toHaveBeenCalledTimes(3); - expect(debug.mock.calls[0][0]).toBe("Sending message"); - expect(debug.mock.calls[1][0]).toBe("Parsed message"); - expect(debug.mock.calls[2][0]).toBe("Ignoring message from unknown source"); + expect(debug.mock.calls.map((call) => call[0])).toEqual([ + "Sending message", + "Parsed message", + "Ignoring message from unknown source", + ]); }); }); From 0936aa13c4079862220d57f62de2e3cab29a442f Mon Sep 17 00:00:00 2001 From: Gabriele Della Malva Date: Fri, 9 Oct 2026 23:23:12 +0200 Subject: [PATCH 4/4] style: format transport privacy hardening patch --- src/message-transport.test.ts | 25 ++++++++++++++++++++----- src/message-transport.ts | 5 ++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/message-transport.test.ts b/src/message-transport.test.ts index 7c791e977..3b19c55cf 100644 --- a/src/message-transport.test.ts +++ b/src/message-transport.test.ts @@ -101,9 +101,18 @@ describe("PostMessageTransport", () => { try { const transport = await createStartedTransport(); await transport.send(validRequest); - fakeWindow.dispatch("message", { source: trustedSource, data: validRequest }); - fakeWindow.dispatch("message", { source: untrustedSource, data: validRequest }); - fakeWindow.dispatch("message", { source: trustedSource, data: "not JSON-RPC" }); + fakeWindow.dispatch("message", { + source: trustedSource, + data: validRequest, + }); + fakeWindow.dispatch("message", { + source: untrustedSource, + data: validRequest, + }); + fakeWindow.dispatch("message", { + source: trustedSource, + data: "not JSON-RPC", + }); expect(debug).not.toHaveBeenCalled(); } finally { @@ -120,8 +129,14 @@ describe("PostMessageTransport", () => { ); await transport.start(); await transport.send(validRequest); - fakeWindow.dispatch("message", { source: trustedSource, data: validRequest }); - fakeWindow.dispatch("message", { source: untrustedSource, data: validRequest }); + fakeWindow.dispatch("message", { + source: trustedSource, + data: validRequest, + }); + fakeWindow.dispatch("message", { + source: untrustedSource, + data: validRequest, + }); expect(debug).toHaveBeenCalledTimes(3); expect(debug.mock.calls.map((call) => call[0])).toEqual([ diff --git a/src/message-transport.ts b/src/message-transport.ts index c813ad8e2..0531d177e 100644 --- a/src/message-transport.ts +++ b/src/message-transport.ts @@ -82,7 +82,10 @@ export class PostMessageTransport implements Transport { ) { this.messageListener = (event) => { if (eventSource && event.source !== this.eventSource) { - this.options.logger?.debug("Ignoring message from unknown source", event); + this.options.logger?.debug( + "Ignoring message from unknown source", + event, + ); return; } const parsed = JSONRPCMessageSchema.safeParse(event.data);