Files
rea/src/domain/javascript/electronStaticAnalysisIdentity.test.ts
T

329 lines
11 KiB
TypeScript

import { expect, it } from "vitest";
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
it.each([
"module.require = replacement;",
"const holder = { loader: module }; holder.loader.require = replacement;",
"const holder = {}; holder.loader = module; holder.loader.require = replacement;",
])(
"does not prove Electron exports after a delegate replacement: %s",
(write) => {
const analysis = analyzeJavaScriptStaticSource(
`${write} const { BrowserWindow } = require("electron"); new BrowserWindow();`,
);
expect(analysis.electron.browser_windows).toEqual([]);
expect(analysis.references).toEqual([
expect.objectContaining({ kind: "require", specifier: null }),
]);
},
);
it.each([
['const { app } = require("electron"); app.setAppPath("./other");', true],
[
'import { app as runtime } from "electron/main"; runtime.setAppPath(path);',
true,
],
[
'import * as electron from "electron"; electron.app.setAppPath("./other");',
true,
],
[
'const { app } = require("electron"); const runtime = app; runtime.setAppPath("./other");',
true,
],
[
'const { app } = require("electron"); const copy = { ...{ app } }; copy.app.setAppPath("./other");',
true,
],
[
'const { app } = require("electron"); const change = app.setAppPath; change("./other");',
true,
],
['const app = { setAppPath() {} }; app.setAppPath("./other");', false],
[
'import { app } from "electron"; function local(app) { app.setAppPath("./other"); }',
false,
],
[
'const electron = { app: { setAppPath() {} } }; electron.app.setAppPath("./other");',
false,
],
])(
"retains Electron application path changes by binding identity: %s",
(source, expected) => {
const analysis = analyzeJavaScriptStaticSource(source);
expect(analysis.parse_error_count).toBe(0);
expect(analysis.electron.app_path_changes ?? []).toEqual(
expected
? [
expect.objectContaining({
module_key: null,
location: expect.any(Object),
}),
]
: [],
);
},
);
it.each([
['app.setAppPath.call(app, "./other");', true],
['app.setAppPath.apply(app, ["./other"]);', true],
['Reflect.apply(app.setAppPath, app, ["./other"]);', true],
[
'const invoke = Reflect.apply; invoke(app.setAppPath, app, ["./other"]);',
true,
],
[
'const invoke = Reflect.apply.bind(Reflect); invoke(app.setAppPath, app, ["./other"]);',
true,
],
[
'const invoke = Reflect.apply; Reflect.apply = () => {}; invoke(app.setAppPath, app, ["./other"]);',
true,
],
['const change = app.setAppPath.bind(app); change("./other");', true],
['app["setAppPath"][`call`](app, "./other");', true],
[
'const runtime = app; runtime.setAppPath.apply(runtime, ["./other"]);',
true,
],
['const change = app.setAppPath; change.call(app, "./other");', true],
['function local(app) { app.setAppPath.call(app, "./other"); }', false],
[
'const local = { setAppPath() {} }; local.setAppPath.bind(local)("./other");',
false,
],
['app.setAppPath.other(app, "./other");', false],
[
'const Reflect = { apply() {} }; Reflect.apply(app.setAppPath, app, ["./other"]);',
false,
],
[
'Reflect.apply = () => {}; Reflect.apply(app.setAppPath, app, ["./other"]);',
false,
],
[
'const local = { setAppPath() {} }; Reflect.apply(local.setAppPath, local, ["./other"]);',
false,
],
])(
"retains indirect application path changes by Electron identity: %s",
(call, expected) => {
const analysis = analyzeJavaScriptStaticSource(`
const { app } = require("electron");
${call}
`);
expect(analysis.parse_error_count).toBe(0);
expect(analysis.electron.app_path_changes ?? []).toEqual(
expected
? [
expect.objectContaining({
module_key: null,
location: expect.any(Object),
}),
]
: [],
);
},
);
it("recovers APIs through an ES module default import of Electron", () => {
const analysis = analyzeJavaScriptStaticSource(`
import electron from "electron";
const { ipcMain } = electron;
new electron.BrowserWindow({ webPreferences: { preload: "./preload.js" } });
electron.contextBridge.exposeInMainWorld("api", { ping: true });
electron.ipcRenderer.invoke("default:ping");
ipcMain.handle("destructured:ping", () => "pong");
electron.default.ipcMain.handle("nested-default", () => "local");
`);
expect(analysis.electron.browser_windows).toMatchObject([
{ preload_path: "./preload.js" },
]);
expect(analysis.electron.context_bridge_apis).toMatchObject([
{ api_key: "api", members: ["ping"] },
]);
expect(
analysis.electron.ipc.map(({ side, operation, channel }) => ({
side,
operation,
channel,
})),
).toEqual([
{ side: "renderer", operation: "invoke", channel: "default:ping" },
{ side: "main", operation: "handle", channel: "destructured:ping" },
]);
});
it("recovers APIs and sender checks written with optional chaining", () => {
const analysis = analyzeJavaScriptStaticSource(`
const { contextBridge, ipcMain, utilityProcess } = require("electron");
contextBridge?.exposeInMainWorld("api", {});
utilityProcess?.fork("./worker.js");
ipcMain?.handle("optional:ping", (event) => {
if (!event.senderFrame?.url.startsWith("file://")) return null;
return event.sender?.getURL() === "file:///index.html";
});
`);
expect(analysis.electron.context_bridge_apis).toMatchObject([
{ api_key: "api" },
]);
expect(analysis.electron.utility_processes).toMatchObject([
{ module_path: "./worker.js" },
]);
expect(analysis.electron.ipc).toMatchObject([
{ side: "main", operation: "handle", channel: "optional:ping" },
]);
expect(
analysis.electron.sender_validations.map(
({ subject, mechanism, expected }) => ({
subject,
mechanism,
expected: expected.value,
}),
),
).toEqual([
{
subject: "sender-url",
mechanism: "call:startsWith",
expected: "file://",
},
{
subject: "sender-url",
mechanism: "comparison:===",
expected: "file:///index.html",
},
]);
});
it("preserves empty and computed literal contextBridge member keys", () => {
const analysis = analyzeJavaScriptStaticSource(`
const { contextBridge } = require("electron");
const dynamicKey = getKey();
contextBridge.exposeInMainWorld("api", {
"": { nested: { "": true } },
["computed"]: true,
});
contextBridge.exposeInMainWorld("dynamic", {
[dynamicKey]: true,
});
`);
const finding = analysis.electron.context_bridge_apis.find(
({ api_key }) => api_key === "api",
);
expect(finding).toMatchObject({
api_status: "object-literal",
members: ["", ".nested", ".nested.", "computed"],
unknown_members: 0,
});
expect(finding?.resolution_basis).toBeUndefined();
expect(analysis.electron.context_bridge_apis).toContainEqual(
expect.objectContaining({
api_key: "dynamic",
unknown_members: 1,
}),
);
});
it("follows bundler namespace interop helpers without accepting other writes", () => {
const analysis = analyzeJavaScriptStaticSource(`
var __toESM = (mod) => mod;
var __importStar = (mod) => mod;
let electron = require("electron");
electron = __toESM(electron);
new electron.BrowserWindow({ webPreferences: { preload: "./rolldown.js" } });
var import_electron = __toESM(require("electron"));
new import_electron.BrowserWindow({ webPreferences: { preload: "./esbuild.js" } });
function _interopRequireWildcard(mod) { return mod; }
const Electron = __importStar(require("electron"));
new Electron.BrowserWindow({ webPreferences: { preload: "./tsc.js" } });
let replaced = require("electron");
replaced = __toESM(require("./fake"));
new replaced.BrowserWindow({ webPreferences: { preload: "./replaced.js" } });
let rewritten = require("electron");
rewritten = __toESM(rewritten);
rewritten = fake;
new rewritten.BrowserWindow({ webPreferences: { preload: "./rewritten.js" } });
const babel = _interopRequireWildcard(require("electron"));
new babel.BrowserWindow({ webPreferences: { preload: "./babel.js" } });
function nested() {
const __toESM = (mod) => mod;
const scoped = __toESM(require("electron"));
new scoped.BrowserWindow({ webPreferences: { preload: "./nested.js" } });
}
`);
expect(
analysis.electron.browser_windows.map(({ preload_path }) => preload_path),
).toEqual(["./rolldown.js", "./esbuild.js", "./tsc.js", "./babel.js"]);
});
it("does not trust unbound helper names or non-function helper values", () => {
const analysis = analyzeJavaScriptStaticSource(`
const __toESM = makeLookalike();
const electron = __toESM(require("electron"));
new electron.BrowserWindow({});
let unbound = require("electron");
unbound = __importStar(unbound);
new unbound.BrowserWindow({});
`);
expect(analysis.electron.browser_windows).toEqual([]);
});
it("follows stable declaration aliases but not an assigned default parameter", () => {
const analysis = analyzeJavaScriptStaticSource(`
const direct = require("electron");
const constAlias = direct;
new constAlias.BrowserWindow({webPreferences:{preload:"./const.js"}});
let letAlias = require("electron");
new letAlias.BrowserWindow({webPreferences:{preload:"./let.js"}});
const { BrowserWindow } = require("electron");
new BrowserWindow({webPreferences:{preload:"./destructured.js"}});
function withDefault(electron = require("./not-electron")) {
if (flag) electron = require("electron");
new electron.BrowserWindow({webPreferences:{preload:"./default.js"}});
}
`);
expect(
analysis.electron.browser_windows.map(({ preload_path }) => preload_path),
).toEqual(["./const.js", "./let.js", "./destructured.js"]);
});
it("preserves one initializer across variable redeclarations without accepting writes", () => {
const analysis = analyzeJavaScriptStaticSource(`
var electron;
var electron = require("electron");
var electron;
new electron.BrowserWindow({webPreferences:{preload:"./redeclared.js"}});
var replaced;
var replaced = require("electron");
replaced = fake;
new replaced.BrowserWindow({webPreferences:{preload:"./replaced.js"}});
var initializedTwice = require("electron");
var initializedTwice = fake;
new initializedTwice.BrowserWindow({});
var conditional;
if (flag) { var conditional = require("electron"); }
new conditional.BrowserWindow({});
`);
expect(
analysis.electron.browser_windows.map(({ preload_path }) => preload_path),
).toEqual(["./redeclared.js"]);
});