[GH-ISSUE #2668] fix(javascript): IPC join edges omit bridge path conditions #969

Closed
opened 2026-10-15 18:29:58 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 13, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/2668

Summary

Analyzing an Electron application directory preserves a bridge path's branch condition on its endpoint node but omits that condition from the edge joining the path to its IPC handler. Callers inspecting the join edge's conditions receive only handler-side conditions. This affects the metadata of inferred paths; the graph still retains bridge conditions on its nodes and still marks the relationships as static candidates.

Steps to reproduce

The two-file conditional-member fixture below reproduced the failure in a focused source test through reconstructJavaScriptArtifact, followed by parseJavaScriptApplicationGraph. A factory-returned closure fixture also reproduced it. The CLI command below provides the corresponding public entry point, but that adapter command was not executed during this verification. Run it with a CLI built from the source baseline listed under Runtime or environment.

repro_dir=$(mktemp -d)
cat > "$repro_dir/preload.js" <<'JS'
const e = require("electron");
e.contextBridge.exposeInMainWorld("desktop", {
  choose: flag
    ? () => e.ipcRenderer.send("test:copy")
    : () => e.ipcRenderer.send("test:cut")
});
JS
cat > "$repro_dir/main.js" <<'JS'
const e = require("electron");
e.ipcMain.on("test:copy", event => event.sender.copy());
e.ipcMain.on("test:cut", event => event.sender.cut());
JS
node scripts/rea.mjs analyze-javascript-application "$repro_dir" --json > result.json
node --input-type=module <<'JS'
import { readFileSync } from "node:fs";
const { normalized_result: { graph } } = JSON.parse(readFileSync("result.json", "utf8"));
const nodes = new Map(graph.nodes.map(node => [node.node_id, node]));
const expressions = conditions => conditions.map(condition => condition.expression);
const rows = graph.edges
  .filter(edge => edge.evidence.extractor.operation === "join-electron-wrapper-paths")
  .map(edge => ({
    channel: edge.properties.channel,
    join_conditions: expressions(edge.properties.conditions),
    source_conditions: expressions(nodes.get(edge.source_node_id).observations[0].properties.conditions)
  }))
  .sort((a, b) => a.channel.localeCompare(b.channel));
console.log(JSON.stringify(rows, null, 2));
JS

The files are inputs to static analysis and do not need Electron installed or an application process started. The unknown flag selects two candidate implementations of the same exposed member.

Expected behavior

The joined candidate's condition metadata should retain the bridge path's flag condition for test:copy and !(flag) condition for test:cut. If the handler path also has a condition, the join should retain both sides without claiming that either condition passed at runtime.

Actual behavior

The focused source reproduction produced two join edges with empty conditions while their source endpoint observations retained the respective branch conditions. Both files parsed without failures. The observed expression-only output was:

[
  {"channel":"test:copy","join_conditions":[],"source_conditions":["flag"]},
  {"channel":"test:cut","join_conditions":[],"source_conditions":["!(flag)"]}
]

A second reconstruction with event => allowed(event) && event.sender.copy() and the equivalent cut handler produced join_conditions: ["allowed(event)"] for each edge. The source endpoints still retained flag and !(flag). A handler-only control with an unguarded bridge path preserved allowed(event) correctly. All four fixture reconstructions succeeded; one focused observation test passed. The edges retained inferred evidence and runtime limitations. No runtime availability or dispatch outcome was observed.

Evidence

  • Expected source: docs/javascript-artifact-reconstruction.md:307-324 documents retention of branch conditions on recovered Electron candidate paths and tracing exposed members through unique IPC pairings. tests/boundary/cli/electronWrappedCallPaths.test.ts:58 checks handler conditions on join edges; its later public adapter fixture supports guarded factory-returned bridge paths.
  • Failure source: The focused production reconstruction and canonical graph parsing emitted the output above. src/application/javascript/ElectronBoundaryGraphCallPaths.ts:131-159 joins compatible unique literal channels but records only the handler path's conditions, while lines 61 and 124 retain each individual path's conditions on nodes and intra-path edges.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Verification covered four sequential reconstructions of owned two-file fixtures through production directory reconstruction and canonical graph parsing. Each fixture produced the expected joins for its unique literal send/on channels. CLI and MCP execution were not run.

Related: #2560 concerns returned-member recovery, #2174 concerns isolated-world exposure recovery, and #1656 concerns Electron alias recovery; none covers conditions lost from an already recovered IPC join.

Restoration check

Analyze the same conditional-member fixture and confirm that each join retains its own bridge condition. Repeat with guarded handlers and confirm that both the matching bridge condition and allowed(event) remain available on each joined edge. Keep the unguarded-bridge control reporting only the handler condition. Keep the paths marked as inferred candidates, retain their source locations and preserve the conditions already present on endpoint observations.

Runtime or environment

Verified source revision: a2d8b94dd50dab8d966239dd63ab84718aa8b4b4, using Node 24.18.0 and npm 11.16.0 with the repository's existing locked dependencies. The focused source runner used Vitest 5.0.3. Published rea-agents 6.4.0 was not tested, so this report establishes source behavior only.

Current source recheck at 3ae826ceba6f25b08bce50bcb51001209bc16b57 confirms that the relevant condition producers, join guards, join projection and canonical graph normalization remain unchanged; the supplied fixture has no source maps and is unaffected by the adjacent source-map resolution changes, and no new runtime reproduction, CLI or MCP execution, or published-package test was run.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A bounded supported static-analysis result gives incomplete condition metadata to callers inspecting IPC join edges. The defect affects guarded bridge paths; endpoint evidence and candidate limitations remain intact. No application execution failure was observed.
  • Workaround status: available
  • Workaround basis: The reproduction directly confirmed that the join's source endpoint observations retain the bridge conditions and the target observations retain handler conditions. A graph consumer can read both endpoints to recover the condition information without modifying the analyzed artifact or the tool.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2668 ## Summary Analyzing an Electron application directory preserves a bridge path's branch condition on its endpoint node but omits that condition from the edge joining the path to its IPC handler. Callers inspecting the join edge's `conditions` receive only handler-side conditions. This affects the metadata of inferred paths; the graph still retains bridge conditions on its nodes and still marks the relationships as static candidates. ## Steps to reproduce The two-file conditional-member fixture below reproduced the failure in a focused source test through `reconstructJavaScriptArtifact`, followed by `parseJavaScriptApplicationGraph`. A factory-returned closure fixture also reproduced it. The CLI command below provides the corresponding public entry point, but that adapter command was not executed during this verification. Run it with a CLI built from the source baseline listed under Runtime or environment. ```sh repro_dir=$(mktemp -d) cat > "$repro_dir/preload.js" <<'JS' const e = require("electron"); e.contextBridge.exposeInMainWorld("desktop", { choose: flag ? () => e.ipcRenderer.send("test:copy") : () => e.ipcRenderer.send("test:cut") }); JS cat > "$repro_dir/main.js" <<'JS' const e = require("electron"); e.ipcMain.on("test:copy", event => event.sender.copy()); e.ipcMain.on("test:cut", event => event.sender.cut()); JS node scripts/rea.mjs analyze-javascript-application "$repro_dir" --json > result.json node --input-type=module <<'JS' import { readFileSync } from "node:fs"; const { normalized_result: { graph } } = JSON.parse(readFileSync("result.json", "utf8")); const nodes = new Map(graph.nodes.map(node => [node.node_id, node])); const expressions = conditions => conditions.map(condition => condition.expression); const rows = graph.edges .filter(edge => edge.evidence.extractor.operation === "join-electron-wrapper-paths") .map(edge => ({ channel: edge.properties.channel, join_conditions: expressions(edge.properties.conditions), source_conditions: expressions(nodes.get(edge.source_node_id).observations[0].properties.conditions) })) .sort((a, b) => a.channel.localeCompare(b.channel)); console.log(JSON.stringify(rows, null, 2)); JS ``` The files are inputs to static analysis and do not need Electron installed or an application process started. The unknown `flag` selects two candidate implementations of the same exposed member. ## Expected behavior The joined candidate's condition metadata should retain the bridge path's `flag` condition for `test:copy` and `!(flag)` condition for `test:cut`. If the handler path also has a condition, the join should retain both sides without claiming that either condition passed at runtime. ## Actual behavior The focused source reproduction produced two join edges with empty conditions while their source endpoint observations retained the respective branch conditions. Both files parsed without failures. The observed expression-only output was: ```json [ {"channel":"test:copy","join_conditions":[],"source_conditions":["flag"]}, {"channel":"test:cut","join_conditions":[],"source_conditions":["!(flag)"]} ] ``` A second reconstruction with `event => allowed(event) && event.sender.copy()` and the equivalent cut handler produced `join_conditions: ["allowed(event)"]` for each edge. The source endpoints still retained `flag` and `!(flag)`. A handler-only control with an unguarded bridge path preserved `allowed(event)` correctly. All four fixture reconstructions succeeded; one focused observation test passed. The edges retained inferred evidence and runtime limitations. No runtime availability or dispatch outcome was observed. ## Evidence - Expected source: `docs/javascript-artifact-reconstruction.md:307-324` documents retention of branch conditions on recovered Electron candidate paths and tracing exposed members through unique IPC pairings. `tests/boundary/cli/electronWrappedCallPaths.test.ts:58` checks handler conditions on join edges; its later public adapter fixture supports guarded factory-returned bridge paths. - Failure source: The focused production reconstruction and canonical graph parsing emitted the output above. `src/application/javascript/ElectronBoundaryGraphCallPaths.ts:131-159` joins compatible unique literal channels but records only the handler path's conditions, while lines 61 and 124 retain each individual path's conditions on nodes and intra-path edges. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete Verification covered four sequential reconstructions of owned two-file fixtures through production directory reconstruction and canonical graph parsing. Each fixture produced the expected joins for its unique literal send/on channels. CLI and MCP execution were not run. Related: #2560 concerns returned-member recovery, #2174 concerns isolated-world exposure recovery, and #1656 concerns Electron alias recovery; none covers conditions lost from an already recovered IPC join. ## Restoration check Analyze the same conditional-member fixture and confirm that each join retains its own bridge condition. Repeat with guarded handlers and confirm that both the matching bridge condition and `allowed(event)` remain available on each joined edge. Keep the unguarded-bridge control reporting only the handler condition. Keep the paths marked as inferred candidates, retain their source locations and preserve the conditions already present on endpoint observations. ## Runtime or environment Verified source revision: `a2d8b94dd50dab8d966239dd63ab84718aa8b4b4`, using Node 24.18.0 and npm 11.16.0 with the repository's existing locked dependencies. The focused source runner used Vitest 5.0.3. Published `rea-agents` 6.4.0 was not tested, so this report establishes source behavior only. Current source recheck at `3ae826ceba6f25b08bce50bcb51001209bc16b57` confirms that the relevant condition producers, join guards, join projection and canonical graph normalization remain unchanged; the supplied fixture has no source maps and is unaffected by the adjacent source-map resolution changes, and no new runtime reproduction, CLI or MCP execution, or published-package test was run. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A bounded supported static-analysis result gives incomplete condition metadata to callers inspecting IPC join edges. The defect affects guarded bridge paths; endpoint evidence and candidate limitations remain intact. No application execution failure was observed. - Workaround status: available - Workaround basis: The reproduction directly confirmed that the join's source endpoint observations retain the bridge conditions and the target observations retain handler conditions. A graph consumer can read both endpoints to recover the condition information without modifying the analyzed artifact or the tool.
bsakaguchi 2026-10-15 18:29:58 +00:00
  • closed this issue
  • added the
    bug
    label
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#969
No description provided.