78 lines · 3.0 KB · TypeScript
RawDownload
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<string, unknown> {  const out: Record<string, unknown> = {};  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/);  });});