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
2026-10-24 15:57:02 -04:00
committed by GitHub
parent 2acdd54130
commit c7ae27807f
12 changed files with 546 additions and 36 deletions
@@ -54,16 +54,18 @@ export const classifyElectronIpcPairings = (
? []
: handlers.filter(
(handler) =>
handler.finding.channel === transmission.finding.channel &&
(handler.finding.channel === transmission.finding.channel ||
(handler.finding.receiver !== undefined &&
handler.finding.channel === null)) &&
compatibleIpcModes(transmission.finding, handler.finding),
);
return {
transmission,
candidates,
status:
candidates.length === 1
candidates.length === 1 && candidates[0]?.finding.receiver === undefined
? "paired"
: candidates.length > 1
: candidates.length > 0
? "ambiguous"
: "unpaired",
};
@@ -119,10 +119,22 @@ const addChannelNode = (
coverage: JavaScriptArtifactGraphCoverage,
): ApplicationNode => {
const { finding, file } = record;
const identityKey =
const channelKey =
finding.channel === null
? `dynamic:${record.key}`
: `literal:${finding.channel}`;
const identityKey =
finding.receiver === undefined
? channelKey
: JSON.stringify([
file.path,
file.sha256,
finding.module_runtime ?? null,
finding.module_key,
finding.receiver.kind,
finding.receiver.owner_location,
channelKey,
]);
return context.accumulator.addNode({
kind: "ipc-channel",
identity:
@@ -141,6 +153,9 @@ const addChannelNode = (
{
label: finding.channel ?? finding.channel_expression,
properties: {
...(finding.receiver === undefined
? {}
: { receiver: finding.receiver }),
side: finding.side,
operation: finding.operation,
mode: finding.mode,
@@ -159,16 +174,23 @@ const addChannelNode = (
range: finding.location,
operation: "observe-ipc-channel-expression",
coverage,
limitations:
finding.resolution_basis === "wrapper-substitution"
? [
limitations: [
...(finding.receiver === undefined
? []
: [
"The receiver is a source-level allocation, not a runtime frame identity; registration and dispatch remain unverified.",
]),
...(finding.resolution_basis !== "wrapper-substitution"
? []
: [
"The channel is inferred through lexical wrapper substitution; runtime registration and dispatch remain unverified.",
]
: finding.channel === null
? [
"The channel expression is dynamic; its runtime value and pairings remain unknown.",
]
: [],
]),
...(finding.channel !== null
? []
: [
"The channel expression is dynamic; its runtime value and pairings remain unknown.",
]),
],
}),
},
],
@@ -179,17 +201,31 @@ const addHandlerNode = (input: IpcHandlerInput): ApplicationNode => {
const { context, record, source, channel, coverage } = input;
const { finding, file } = record;
const handlerRange = finding.handler_location ?? finding.location;
const handlerKey = `${finding.side}:${finding.operation}:${finding.channel ?? finding.channel_expression}:${javascriptSourceRangeKey(handlerRange)}`;
const identityKey =
finding.receiver === undefined
? handlerKey
: JSON.stringify([
file.path,
finding.module_runtime ?? null,
finding.module_key,
finding.receiver,
handlerKey,
]);
const handler = context.accumulator.addNode({
kind: "ipc-handler",
identity: artifactLocalIdentity(
file.sha256,
"electron-ipc-handler",
`${finding.side}:${finding.operation}:${finding.channel ?? finding.channel_expression}:${javascriptSourceRangeKey(handlerRange)}`,
identityKey,
),
observations: [
{
label: finding.channel,
properties: {
...(finding.receiver === undefined
? {}
: { receiver: finding.receiver }),
side: finding.side,
operation: finding.operation,
mode: finding.mode,