diff --git a/src/webview/host.ts b/src/webview/host.ts index 3ad3cb7..78b1c8f 100644 --- a/src/webview/host.ts +++ b/src/webview/host.ts @@ -154,7 +154,14 @@ export function subscribeToHost(handler: (message: HostToWebview) => void): () = type: data.type, }); } else { - console.error("[quoll] host message rejected by validator", event.data); + // Symmetric with the host-side inbound-validation log (quoll-editor-panel.ts): + // preview only type + top-level keys, never the full payload, so an + // unvalidated message can't leak arbitrary content to the console. + const preview = + data !== null && typeof data === "object" + ? { type: data.type, keys: Object.keys(data) } + : { type: typeof event.data }; + console.error("[quoll] host message rejected by validator", preview); } return; } diff --git a/test/webview/host.test.ts b/test/webview/host.test.ts index 1a9ca59..d92819f 100644 --- a/test/webview/host.test.ts +++ b/test/webview/host.test.ts @@ -175,12 +175,18 @@ describe("subscribeToHost — boundary validation + diagnostics", () => { expect(detail).toMatchObject({ expected: PROTOCOL_VERSION, got: 2, type: "document" }); }); - it("rejects and logs a shapeless payload via the validator branch", () => { - window.dispatchEvent(new MessageEvent("message", { data: { hello: "world" } })); + it("rejects and logs a shapeless payload via the validator branch, previewing only type + keys", () => { + window.dispatchEvent( + new MessageEvent("message", { data: { hello: "world", secret: "sensitive-value" } }) + ); expect(handler).not.toHaveBeenCalled(); expect(errorSpy).toHaveBeenCalledTimes(1); - expect(String(errorSpy.mock.calls[0][0])).toContain("rejected by validator"); + const [message, detail] = errorSpy.mock.calls[0]; + expect(String(message)).toContain("rejected by validator"); + expect(detail).toEqual({ type: undefined, keys: ["hello", "secret"] }); + // The raw payload value must never reach the console — only its shape. + expect(JSON.stringify(detail)).not.toContain("sensitive-value"); }); it("logs an undefined payload as an empty payload, not a validator rejection", () => {