fix(electron): retain scoped IPC registration provenance (#7031)

* fix(electron): retain scoped IPC registration provenance

* fix(electron): defer mutation analysis until scoped receiver admission
This commit is contained in:
Wenyu Chiou authored and GitHub committed 2026-10-25 03:57:02 +08:00
1 parent 2acdd54130
commit c7ae27807f
12 files changed
+546 -36

No files matched your search

@@ -3,6 +3,7 @@ import * as t from "@babel/types";
import type {
ElectronIpcDescriptor,
ElectronIpcFinding,
ElectronIpcReceiver,
ElectronSenderValidationFinding,
} from "./electronStaticAnalysisTypes.js";
import {
@@ -37,7 +38,10 @@ const inspectIpcCall = (
node: t.CallExpression | t.OptionalCallExpression,
context: JavaScriptFindingContext,
): void => {
const descriptor = ipcDescriptor(electronCalleePath(node.callee, context));
const scoped = scopedIpcDescriptor(node, context);
const descriptor =
scoped?.descriptor ??
ipcDescriptor(electronCalleePath(node.callee, context));
if (descriptor === undefined) return;
const channelNode = argumentNode(node.arguments[0]);
const channelValue = electronStaticValue(context.source, channelNode);
@@ -88,6 +92,7 @@ const inspectIpcCall = (
context.accumulator.unknownFindings += 1;
const finding: ElectronIpcFinding = {
...findingState,
...(scoped === undefined ? {} : { receiver: scoped.receiver }),
...channelReference,
module_key: null,
location: range(node),
@@ -100,6 +105,48 @@ const inspectIpcCall = (
});
};
const scopedIpcDescriptor = (
node: t.CallExpression | t.OptionalCallExpression,
context: JavaScriptFindingContext,
):
| {
readonly descriptor: ElectronIpcDescriptor;
readonly receiver: ElectronIpcReceiver;
}
| undefined => {
if (
!t.isMemberExpression(node.callee) &&
!t.isOptionalMemberExpression(node.callee)
)
return undefined;
const operation = semanticStaticPropertyName(
node.callee.property,
node.callee.computed,
);
const descriptor = ipcDescriptor(["ipcMain", operation]);
if (descriptor === undefined) return undefined;
const receiver = context.browserWindowReceiver?.(node.callee.object, node, [
operation,
]);
if (receiver === undefined) return undefined;
const path = receiver.path;
const kind =
path.length === 3 && path[0] === "webContents" && path[1] === "ipc"
? "web-contents"
: path.length === 4 &&
path[0] === "webContents" &&
path[1] === "mainFrame" &&
path[2] === "ipc"
? "main-frame"
: undefined;
return kind === undefined
? undefined
: {
descriptor,
receiver: { kind, owner_location: range(receiver.owner) },
};
};
const inspectValidationComparison = (
node: t.BinaryExpression,
context: JavaScriptFindingContext,