import { describe, expect, it } from "vite-plus/test"; import { PREVIEW_WEBVIEW_PREFERENCES } from "./WebviewPreferences.ts"; /** * Mirrors Electron's webview attribute parser closely enough to catch the * regressions we've already hit: * * - whitespace inside the comma-separated list silently drops keys (so * `" sandbox=true"` becomes an unknown key and Electron falls back to * defaults — re-opening the Node-leak window we closed), * - non-`true`/`false` values (`"yes"`, `"no"`, etc.) are kept as truthy * strings and assigned to a boolean preference, which silently flips * `contextIsolation=no` to ENABLED (then react-grab can't see the React * DevTools hook and componentName resolution always returns null). * * The actual Electron parser does roughly: * * for (const pair of webpreferences.split(',')) { * const [key, value] = pair.split('='); * prefs[key] = value; // value left as a string * } * * then later coerces booleans via `Boolean(value)`. Replicating that here * keeps the test independent of Electron internals while still failing if * we accidentally ship `"contextIsolation=no"` again. */ function parseWebPreferences(input: string): Record { const out: Record = {}; for (const pair of input.split(",")) { if (pair !== pair.trim()) { // Electron's parser doesn't trim; surface the bug as undefined-key. out[pair] = pair.split("=")[1]; continue; } const [key, value] = pair.split("="); if (!key) continue; out[key] = value; } return out; } describe("PREVIEW_WEBVIEW_PREFERENCES", () => { const parsed = parseWebPreferences(PREVIEW_WEBVIEW_PREFERENCES); it("contains exactly the three security-critical keys", () => { expect(Object.keys(parsed).toSorted()).toEqual( ["contextIsolation", "nodeIntegration", "sandbox"].toSorted(), ); }); it("uses canonical JS-boolean string literals (not yes/no, on/off, 1/0)", () => { // `value="no"` is a TRUTHY string when assigned to webPreferences.X — so // `contextIsolation="no"` would silently leave isolation ENABLED. Lock // the values to `"true"` / `"false"` so the parser does the right thing. for (const value of Object.values(parsed)) { expect(value).toMatch(/^(true|false)$/); } }); it("disables context isolation (so react-grab can see the page's React DevTools hook)", () => { expect(parsed["contextIsolation"]).toBe("false"); }); it("keeps the renderer sandbox enabled (so the page cannot reach Node APIs)", () => { expect(parsed["sandbox"]).toBe("true"); }); it("disables nodeIntegration (defense in depth — page never gets Node)", () => { expect(parsed["nodeIntegration"]).toBe("false"); }); it("contains no whitespace (Electron's parser does not trim)", () => { // Electron splits on `,` without trimming, so any whitespace would turn // a key into an unknown one and silently drop the security flag. expect(PREVIEW_WEBVIEW_PREFERENCES).not.toMatch(/\s/); }); });