Files
reverse-engineering-anything/src/domain/javascript/electronStaticAnalysisScopedIpc.test.ts
T

251 lines
8.5 KiB
TypeScript

import { expect, it } from "vitest";
import { analyzeJavaScriptStaticSource as analyzeUnknownHostSource } from "../../../tests/fixtures/javascriptStaticAnalysis.js";
import { analyzeCommonJsStaticFixture as analyzeJavaScriptStaticSource } from "../../../tests/fixtures/javascriptCommonJsAnalysis.js";
it("retains an unknown-host Electron request as a candidate without claiming IPC identity", () => {
const analysis = analyzeUnknownHostSource(
'const { BrowserWindow } = require("electron"); new BrowserWindow().webContents.ipc.handle("x", () => {});',
);
expect(analysis.electron.ipc).toEqual([
expect.objectContaining({
channel: "x",
operation: "handle",
loader_authority: "candidate",
}),
]);
expect(analysis.analysis_coverage?.applicability.electron).toBe("unknown");
expect(analysis.references).toContainEqual(
expect.objectContaining({
specifier: "electron",
loader_authority: "candidate",
}),
);
expect(
analysis.analysis_coverage?.semantics.unknown_findings,
).toBeGreaterThan(0);
});
it.each([
["webContents", "window.webContents.ipc"],
["main frame", "window.webContents.mainFrame.ipc"],
] as const)(
"retains a proven %s IPC handler beside the global control",
(_, receiver) => {
const analysis = analyzeJavaScriptStaticSource(`
const { BrowserWindow, ipcMain } = require("electron");
const window = new BrowserWindow();
ipcMain.handle("global-control", () => "global");
${receiver}.handle("scoped-channel", () => "scoped");
`);
expect(analysis.parse_error_count).toBe(0);
expect(analysis.electron.browser_windows).toHaveLength(1);
expect(analysis.electron.ipc).toContainEqual(
expect.objectContaining({
side: "main",
operation: "handle",
channel: "global-control",
handler_kind: "inline-function",
}),
);
expect(analysis.electron.ipc).toContainEqual(
expect.objectContaining({
side: "main",
operation: "handle",
channel: "scoped-channel",
handler_kind: "inline-function",
}),
);
},
);
it("does not label lookalike or shadowed receivers as Electron IPC", () => {
const analysis = analyzeJavaScriptStaticSource(`
const { BrowserWindow, ipcMain } = require("electron");
ipcMain.handle("global-control", () => "global");
const fake = { webContents: { ipc: { handle() {} } } };
fake.webContents.ipc.handle("fake", () => {});
function shadow(BrowserWindow) {
const window = new BrowserWindow();
window.webContents.ipc.handle("shadowed", () => {});
}
`);
expect(analysis.parse_error_count).toBe(0);
expect(analysis.electron.ipc.map(({ channel }) => channel)).toEqual([
"global-control",
]);
});
it.each([
[
'const contents = window.webContents; contents.ipc.handle("same", () => {});',
"web-contents",
],
[
'const frame = window.webContents.mainFrame; frame.ipc.handle("same", () => {});',
"main-frame",
],
[
'const contents = window.webContents; const frame = contents.mainFrame; const ipc = frame.ipc; ipc.handle("same", () => {});',
"main-frame",
],
])("retains a proven scoped alias: %s", (call, kind) => {
const findings =
analyzeJavaScriptStaticSource(`const { BrowserWindow } = require("electron");
const window = new BrowserWindow(); ${call}`).electron.ipc;
expect(findings).toHaveLength(1);
expect(findings[0]?.receiver).toEqual({
kind,
owner_location: {
start: { line: 2, column: 19 },
end: { line: 2, column: 38 },
},
});
});
it("keeps the shared owner's conservative omission after an aliased receiver call", () => {
const findings =
analyzeJavaScriptStaticSource(`const { BrowserWindow } = require("electron");
const window = new BrowserWindow();
const ipc = window.webContents.mainFrame.ipc;
ipc.handle("first", () => {});
window.webContents.mainFrame.ipc.on("after-escape", () => {});`).electron
.ipc;
expect(findings.map(({ channel }) => channel)).toEqual(["first"]);
});
it.each([
"window.webContents = replacement;",
"window.webContents.ipc = replacement;",
"window.webContents.ipc.handle = replacement;",
"const alias = window; alias.webContents.ipc.handle = replacement;",
"const contents = window.webContents; contents.ipc = replacement;",
"BrowserWindow.prototype.webContents = replacement;",
])("omits a mutated receiver: %s", (mutation) => {
const analysis = analyzeJavaScriptStaticSource(`
const { BrowserWindow } = require("electron");
const window = new BrowserWindow();
${mutation}
window.webContents.ipc.handle("changed", () => {});
`);
expect(analysis.parse_error_count).toBe(0);
expect(analysis.electron.ipc).toEqual([]);
});
it.each(["webContents.ipc", "webContents.mainFrame.ipc"])(
"does not split one dotted member key into receiver segments: %s",
(key) => {
expect(
analyzeJavaScriptStaticSource(`const {BrowserWindow} = require("electron");
const window = new BrowserWindow(); window[${JSON.stringify(key)}].handle("fake", () => {});`)
.electron.ipc,
).toEqual([]);
},
);
it.each([
["BrowserView", "webContents.ipc", "web-contents"],
["WebContentsView", "webContents.mainFrame.ipc", "main-frame"],
] as const)(
"retains a proven %s %s handler through the view's own contents",
(constructor, surface, kind) => {
const analysis = analyzeJavaScriptStaticSource(`
const { ${constructor} } = require("electron");
const owner = new ${constructor}({});
owner.${surface}.handle("view-channel", () => {});
`);
expect(analysis.parse_error_count).toBe(0);
expect(analysis.electron.browser_windows).toMatchObject([
{ window_constructor: constructor, options_status: "object-literal" },
]);
expect(analysis.electron.ipc).toMatchObject([
{
side: "main",
operation: "handle",
channel: "view-channel",
receiver: { kind },
},
]);
},
);
it.each(["webContents.ipc", "webContents.mainFrame.ipc", "loadURL"])(
"omits a BaseWindow %s receiver that the runtime does not own",
(surface) => {
const analysis = analyzeJavaScriptStaticSource(`
const { BaseWindow } = require("electron");
const base = new BaseWindow({ width: 800 });
base.${surface}.handle("inert", () => {});
`);
expect(analysis.parse_error_count).toBe(0);
expect(analysis.electron.ipc).toEqual([]);
expect(analysis.electron.browser_windows).toMatchObject([
{
window_constructor: "BaseWindow",
options_status: "object-literal",
web_preferences_status: "missing",
preload_path: null,
},
]);
},
);
it("keeps a lookalike construction unproven for a view constructor", () => {
const analysis = analyzeJavaScriptStaticSource(`
const { WebContentsView } = require("electron");
function shadow(WebContentsView) {
const view = new WebContentsView({ webPreferences: { sandbox: true } });
view.webContents.ipc.handle("shadowed", () => {});
}
`);
expect(analysis.parse_error_count).toBe(0);
expect(analysis.electron.browser_windows).toEqual([]);
expect(analysis.electron.ipc).toEqual([]);
});
it.each(["BrowserView", "WebContentsView"])(
"reads literal webPreferences on a %s construction",
(constructor) => {
expect(
analyzeJavaScriptStaticSource(`
const { ${constructor} } = require("electron");
const owner = new ${constructor}({ webPreferences: { preload: "./p.js", sandbox: true } });
`).electron.browser_windows,
).toMatchObject([
{
window_constructor: constructor,
web_preferences_status: "object-literal",
web_preferences: [
{ name: "preload", value: { status: "literal", value: "./p.js" } },
{ name: "sandbox", value: { status: "literal", value: true } },
],
preload_path: "./p.js",
preload_resolution_context: "module-specifier",
},
]);
},
);
it.each(["BaseWindow"])(
"does not report a webPreferences surface a %s never accepts",
(constructor) => {
const analysis = analyzeJavaScriptStaticSource(`
const { ${constructor} } = require("electron");
const owner = new ${constructor}({ webPreferences: { preload: "./p.js", sandbox: true } });
`);
expect(analysis.parse_error_count).toBe(0);
expect(analysis.electron.browser_windows).toMatchObject([
{
window_constructor: constructor,
options_status: "object-literal",
web_preferences_status: "missing",
web_preferences: [],
preload_path: null,
},
]);
expect(
analysis.role_paths.filter(({ role }) => role === "preload"),
).toEqual([]);
},
);